如何在Powershell中解析和删除存档的事件日志

时间:2014-09-02 22:23:12

标签: powershell event-viewer windows-server-2012-r2

我尝试解析存档的安全日志,以追踪更改权限的问题。此脚本通过+ 10天之前的.evtx文件进行greps。它目前输出我想要的东西,但是当它清理旧日志时(大约50GB /每天,未压缩,每个都通过另一个在午夜运行的脚本存档到他们自己的每日文件夹中)它开始抱怨日志是在使用中,不能删除。当我尝试通过资源管理器删除文件时似乎正在使用的过程交替使用DHCP客户端或事件查看器,停止这两种服务都有效,但显然我无法在没有eventvwr的情况下运行。 DHCP客户端用于网络友好,但不需要。

唯一触及.evtx文件的是这个脚本,它们没有备份,它们没有被其他任何东西监视,它们不会被事件日志服务自动解析,它们只是存储在磁盘上等待。

该脚本最初删除了一些内容,但随后失败,所有删除操作都移到了最后,然后移到了KillLogWithFire()函数。即使是计时器似乎也无济于事。我也尝试将文件移动到已处理的子文件夹,但出于同样的原因,它确实无效。

我认为有一些方法可以释放这个脚本在任何文件上打开的任何句柄,但是在循环中尝试使用EventCl变量的.close()或.dispose()并不起作用。

$XPath = @'
*[System[Provider/@Name='Microsoft-Windows-Security-Auditing']]
and
*[System/EventID=4670]
'@

$DeletableLogs = @()
$logfile = "L:\PermChanges.txt"
$AdminUsers = ("List","of","Admin","Users")

$today = Get-Date
$marker = "
-------------
$today
-------------
"
write-output $marker >> $logfile 

Function KillLogWithFire($log){
    Try {
        remove-item $log
    }
    Catch [writeerror]{
        $Timer += 1
        sleep $timer
        write-output "Killing log $log in $timer seconds"
        KillLogWithFire($log)
    }
}

Function LogPermissionChange($PermChanges){
    ForEach($PermChange in $PermChanges){
        $Change = @{}
        $Change.ChangedBy = $PermChange.properties[1].value.tostring()

        #Filter out normal non-admin users
        if ($AdminUsers -notcontains $Change.ChangedBy){continue} 
        $Change.FileChanged = $PermChange.properties[6].value.tostring()
        #Ignore temporary files
        if ($Change.FileChanged.EndsWith(".tmp")){continue}
        elseif ($Change.FileChanged.EndsWith(".partial")){continue}

        $Change.MadeOn = $PermChange.TimeCreated.tostring()
        $Change.OriginalPermissions = $PermChange.properties[8].value.tostring()
        $Change.NewPermissions = $PermChange.properties[9].value.tostring()

        write-output "{" >> $logfile
        write-output ("Changed By           : "+ $Change.ChangedBy) >> $logfile
        write-output ("File Changed         : "+ $Change.FileChanged) >> $logfile
        write-output ("Change Made          : "+ $Change.MadeOn) >> $logfile
        write-output ("Original Permissions : 
            "+ $Change.OriginalPermissions) >> $logfile
        write-output ("New Permissions      : 
            "+ $Change.NewPermissions) >> $logfile
        "}
" >> $logfile
    }
}

GCI -include Archive-Security*.evtx -path L:\Security\$Today.AddDays(-10) -recurse | ForEach-Object{
    Try{
        $PermChanges = Get-WinEvent -Path $_ -FilterXPath $XPath -ErrorAction Stop
    }
    Catch [Exception]{
        if ($_.Exception -match "No events were found that match the specified selection criteria."){
        }
        else {
            Throw $_
        }
    }
    LogPermissionChange($PermChanges)
    $PermChanges = $Null 
    $DeletableLogs += $_
}

foreach ($log in $DeletableLogs){
    $Timer = 0
    Try{
        remove-item $log
        }
    Catch [IOException]{
        KillLogWithFire($log)
    }
}

更新

我想要发布现在正在使用的完整代码作为一个单独的答案,而不是编辑原始代码,因为我已经被告知不要这样做。解析日志并每30分钟运行一次的初始部分与上面大致相同:

$XPath = @'
*[System[Provider/@Name='Microsoft-Windows-Security-Auditing']]
and
*[System/EventID=4670]
'@

$DeletableLogs = @()
$logfile = "L:\PermChanges.txt"
$DeleteList = "L:\DeletableLogs.txt"
$AdminUsers = ("List","Of","Admins")

$today = Get-Date
$marker = "
-------------
$today
-------------
"
write-output $marker >> $logfile 

Function LogPermissionChange($PermChanges){
    ForEach($PermChange in $PermChanges){
        $Change = @{}
        $Change.ChangedBy = $PermChange.properties[1].value.tostring()

        #Filter out normal non-admin users
        if ($AdminUsers -notcontains $Change.ChangedBy){continue} 
        $Change.FileChanged = $PermChange.properties[6].value.tostring()
        #Ignore temporary files
        if ($Change.FileChanged.EndsWith(".tmp")){continue}
        elseif ($Change.FileChanged.EndsWith(".partial")){continue}

        $Change.MadeOn = $PermChange.TimeCreated.tostring()
        $Change.OriginalPermissions = $PermChange.properties[8].value.tostring()
        $Change.NewPermissions = $PermChange.properties[9].value.tostring()

        write-output "{" >> $logfile
        write-output ("Changed By           : "+ $Change.ChangedBy) >> $logfile
        write-output ("File Changed         : "+ $Change.FileChanged) >> $logfile
        write-output ("Change Made          : "+ $Change.MadeOn) >> $logfile
        write-output ("Original Permissions : 
            "+ $Change.OriginalPermissions) >> $logfile
        write-output ("New Permissions      : 
            "+ $Change.NewPermissions) >> $logfile
        "}
" >> $logfile
    }
}

GCI -include Archive-Security*.evtx -path L:\Security\ -recurse | ForEach-Object{
    Try{
        $PermChanges = Get-WinEvent -Path $_ -FilterXPath $XPath -ErrorAction Stop
    }
    Catch [Exception]{
        if ($_.Exception -match "No events were found that match the specified selection criteria."){
        }
        else {
            Throw $_
        }
    }
    LogPermissionChange($PermChanges)
    $PermChanges = $Null 
    $DeletableLogs += $_
}

foreach ($log in $DeletableLogs){
    write-output $log.FullName >> $DeleteList
    }

第二部分删除,包括由TheMadTechnician慷慨提供的帮助函数。代码仍然循环,因为直接删除比函数更快,但即使在未触及文件之后也不会总是成功。:

# Log Cleanup script. Works around open log issues caused by PS parsing of 
# saved logs in EventLogParser.ps1

$DeleteList = "L:\DeletableLogs.txt"
$DeletableLogs = get-content $DeleteList

Function Close-LockedFile{
Param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)][String[]]$Filename
)
Begin{
    $HandleApp = 'C:\sysinternals\Handle.exe'
    If(!(Test-Path $HandleApp)){Write-Host "Handle.exe not found at $HandleApp`nPlease download it from www.sysinternals.com and save it in the afore mentioned location.";break}
}
Process{
    $HandleOut = Invoke-Expression ($HandleApp+' '+$Filename)
    $Locks = $HandleOut |?{$_ -match "(.+?)\s+pid: (\d+?)\s+type: File\s+(\w+?): (.+)\s*$"}|%{
        [PSCustomObject]@{
            'AppName' = $Matches[1]
            'PID' = $Matches[2]
            'FileHandle' = $Matches[3]
            'FilePath' = $Matches[4]
        }
    }
    ForEach($Lock in $Locks){
        Invoke-Expression ($HandleApp + " -p " + $Lock.PID + " -c " + $Lock.FileHandle + " -y") | Out-Null
    If ( ! $LastexitCode ) { "Successfully closed " + $Lock.AppName + "'s lock on " + $Lock.FilePath}
    }
}
}

Function KillLogWithFire($log){
    Try {
        Close-LockedFile $Log - 
    }
    Catch [System.IO.IOException]{
        $Timer += 1
        sleep $timer
    write-host "Killing $Log in $Timer seconds with fire."
    KillLogWithFire($Log)
    }
}

foreach ($log in $DeletableLogs){
    Try {
        remove-item $log -ErrorAction Stop
        }
    Catch [System.IO.IOException]{
        $Timer = 0
        KillLogWithFire($Log)
        }
    }

remove-item $DeleteList

3 个答案:

答案 0 :(得分:1)

一种解决方案是获取HANDLE.EXE并使用它来关闭任何打开的句柄。这是我大致基于this script使用的函数。它使用handle.exe,查找锁定文件的内容,然后关闭锁定该文件打开的句柄。

Function Close-LockedFile{
Param(
    [Parameter(Mandatory=$true,ValueFromPipeline=$true)][String[]]$Filename
)
Begin{
    $HandleApp = 'C:\sysinternals\Handle.exe'
    If(!(Test-Path $HandleApp)){Write-Host "Handle.exe not found at $HandleApp`nPlease download it from www.sysinternals.com and save it in the afore mentioned location.";break}
}
Process{
    $HandleOut = Invoke-Expression ($HandleApp+' '+$Filename)
    $Locks = $HandleOut |?{$_ -match "(.+?)\s+pid: (\d+?)\s+type: File\s+(\w+?): (.+)\s*$"}|%{
        [PSCustomObject]@{
            'AppName' = $Matches[1]
            'PID' = $Matches[2]
            'FileHandle' = $Matches[3]
            'FilePath' = $Matches[4]
        }
    }
    ForEach($Lock in $Locks){
        Invoke-Expression ($HandleApp + " -p " + $Lock.PID + " -c " + $Lock.FileHandle + " -y") | Out-Null
    If ( ! $LastexitCode ) { "Successfully closed " + $Lock.AppName + "'s lock on " + $Lock.FilePath}
    }
}
}

我将handle.exe保存在C:\ Sysinternals中,您可能需要调整函数中的路径,或者将可执行文件保存在那里。

答案 1 :(得分:0)

我遇到了一个非常类似的问题,经过大量搜索后发现了这篇文章。虽然handle.exe在我第一次尝试时工作但我注意到-c带有警告“关闭句柄会导致应用程序或系统不稳定”

我也在使用get-winevent,它似乎(有时)锁定正在处理的.evtx文件。我写了一个循环等待5秒重试。有时它需要2分钟或者要发布的文件,我有一次运行过夜,它有数百次重试。

当我第一次使用手柄时,它完美地工作。然后我将它实现到脚本中,后来发现它循环了一个“无法解释的错误”。我最终不得不重新启动服务器以使事情再次运行,因此从脚本中删除了handle.exe并返回等待文件关闭。

我可以通过停止脚本并关闭powershell ise来可靠地释放文件。一旦ISE关闭,就可以毫无问题地删除文件。

不幸的是,我需要这个脚本继续运行,并且不会因文件保持打开而被阻止。我很惊讶不得不求助于sysinternals来释放文件,而powershell并没有提供一种简单的方法来关闭文件。

答案 2 :(得分:0)

我遇到了与GTEM相同的问题,在处理数百个事件日志文件时关闭句柄会导致损坏。最终Get-WinEvent无法正常工作。它会冻结或给我相同的“无法解释的错误”。  所以我用MS开了一个首要案例。他们引导我到存储Get-WinEvent事件的实际变量是锁定文件的内容。我想如果你还在使用那个变量,它实际上并没有解锁文件。因此,为了解决这个问题,我在将变量传输到新变量后向我的脚本添加了一些代码。您可以在下面列出的第三个区域中看到我添加的代码。

#***************************************************************************
#region *** Get the log entries.
# clear the log entry for each pass
$LogEntry = @()

# Get the vent from the log file and export it to the logentry variable and output to the screen
Get-WinEvent -Path $NewPath -FilterXPath $XPathFilter -ErrorAction SilentlyContinue | Tee-Object -Variable LogEntry
#endregion *** End get the log entries
#***************************************************************************

#***************************************************************************
#region *** This is where I copy it to the new variable for later output.
# if there are any log entries
if ($LogEntry.Count -gt 0) {

    # Add the log entries to the log file
    $LogEntries += $LogEntry

} # if there are any log entries
#endregion *** End were I copy to the new variable.
#***************************************************************************

#***************************************************************************
#region *** This is where I added code to allow me to remove the file lock.
# Remove the variable to release the evtx file lock
Remove-Variable -Name LogEntry

# Garbage collect to remove any additional memory tied to the file lock.
[GC]::Collect()

# sleep for 1 seconds
Sleep -Seconds 1
#endregion **** Code to remove the file lock.
#***************************************************************************

完成此操作后,我不再需要使用Handle.exe来关闭文件了。