0

我正在尝试解析存档的安全日志以追踪更改权限的问题。此脚本通过 .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
4

3 回答 3

1

一种解决方案是获取 HANDLE.EXE 并使用它来关闭任何打开的句柄。这是我根据这个脚本粗略使用的一个函数。它使用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}
    }
}
}

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

于 2014-09-03T15:53:55.840 回答
0

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

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

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

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

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

于 2017-04-23T08:36:41.660 回答
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 来关闭文件。

于 2017-05-10T19:52:01.613 回答