【问题标题】:How to parse and delete archived event logs in Powershell如何在 Powershell 中解析和删除归档的事件日志
【发布时间】:2014-09-02 22:23:12
【问题描述】:

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

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

脚本最初删除了一些东西,但由于失败,所有删除都移到了最后,然后移到了 KillLogWithFire() 函数。甚至计时器似乎也无济于事。我也尝试将文件移动到 Processed 子文件夹,但出于同样的原因,这不起作用。

我假设有某种方法可以释放此脚本在任何文件上打开的任何句柄,但尝试在循环中对 EventLog 变量进行 .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 分钟运行一次的 Initial 部分与上述基本相同:

$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

【问题讨论】:

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


    【解决方案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,你可能想在函数里调整路径,或者把可执行文件保存在那里。

    【讨论】:

    • 谢谢,不幸的是根据处理有问题的文件没有打开,所以这对我没有帮助。 :( 但是它确实向我展示了如何以交互式/非严格可读的输出方式使用 Handle,因此对此表示赞同。再次感谢。
    • 所以,为了清楚起见,您输入了函数,然后在您的Remove-Item $log 行之前添加了一行Close-LockedFile $log?或者您只是在脚本运行之外查看了带有 Handle 的文件,却没有看到任何锁定它们的内容?不批评,只是想在同一页上。
    • 我昨天在玩Handle,它没有显示任何文件被锁定,而PS窗口反复吐出文件使用错误;我没有尝试将此代码添加到上述脚本中。但是,我确实在不同的脚本中尝试了此代码,并且还没有遇到与打开文件有关的任何阻塞错误,这是我以前一直遇到的。如果您想知道,我不介意测试上述内容?
    • 拜托,因为如果文件被锁定但句柄看不到任何保持文件打开的东西,我会很好奇你为什么不能删除它。
    • 我最终将代码拆分为两个脚本,一个用于解析事件日志,另一个用于删除它们。您的代码在删除部分中使用,据我根据测试可以判断,它是完全有效的。删除设置现在每两天运行一次,所以我们应该能够获得我们需要的信息,确保日志已经被解析,然后看看清理之后会发生什么。非常感谢您的帮助!
    【解决方案2】:

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

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

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

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

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

    【讨论】:

      【解决方案3】:

      我遇到了与 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 关闭文件了。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-28
        • 1970-01-01
        • 2018-10-06
        • 2019-05-11
        • 1970-01-01
        相关资源
        最近更新 更多