Public/Stop-NSPProcmonCapture.ps1
|
function Stop-NSPProcmonCapture { <# .SYNOPSIS Stops a running Process Monitor capture (Procmon /Terminate) and waits until the backing file is complete. Returns the capture with Ended set. .DESCRIPTION Waits for every ProcMon process to exit and the backing file to be released, up to -TimeoutSeconds. A capture started with -RuntimeSeconds may already have stopped; that's fine. Then measures how many seconds the backing file really covers (CoveredSeconds) and warns when that is well short of the time it ran (Incomplete = $true): ProcMon occasionally writes a file that holds only its first second, which would otherwise read as "no sharing violations". Capture again when that happens. .PARAMETER Capture The object from Start-NSPProcmonCapture. .PARAMETER ProcmonPath Process Monitor executable, when there is no capture object (stops whatever is running). .PARAMETER TimeoutSeconds How long to wait for ProcMon to finish writing. .EXAMPLE $c = $c | Stop-NSPProcmonCapture .EXAMPLE Stop-NSPProcmonCapture -ProcmonPath (Find-NSPProcmon) #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'Capture')] param( [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Capture')][object]$Capture, [Parameter(Mandatory, ParameterSetName = 'Path')][string]$ProcmonPath, [int]$TimeoutSeconds = 120 ) process { $exe = if ($Capture) { $Capture.ProcmonPath } else { $ProcmonPath } if (-not $PSCmdlet.ShouldProcess($exe, 'Stop the ProcMon capture')) { return } $running = { @(Get-Process -Name 'Procmon', 'Procmon64', 'Procmon64a' -ErrorAction SilentlyContinue) } if ((& $running).Count) { if (-not (Test-NSPProcmonSignature -Path $exe)) { throw "$exe is not validly signed by Microsoft - not running it." } $null = Invoke-NSPProcmonExe -FilePath $exe -ArgumentList '/Terminate' -Wait } $deadline = (Get-Date).AddSeconds($TimeoutSeconds) while ((& $running).Count -and (Get-Date) -lt $deadline) { Start-Sleep -Milliseconds 500 } if ((& $running).Count) { Write-Warning "ProcMon is still running after $TimeoutSeconds seconds." } if ($Capture -and $Capture.BackingFile) { # ProcMon may still be flushing for a moment after it exits. while ((Get-Date) -lt $deadline) { if (-not (Test-Path -LiteralPath $Capture.BackingFile)) { Start-Sleep -Milliseconds 500; continue } try { [IO.File]::Open($Capture.BackingFile, 'Open', 'Read', 'None').Dispose(); break } catch { Start-Sleep -Milliseconds 500 } } $Capture.Ended = Get-Date if (-not (Test-Path -LiteralPath $Capture.BackingFile)) { Write-Warning "No backing file at $($Capture.BackingFile) - ProcMon may not have captured (was it elevated?)." return $Capture } $covered = Get-NSPProcmonCoverage -ProcmonPath $exe -Path $Capture.BackingFile $ran = if ($Capture.Started) { ($Capture.Ended - [datetime]$Capture.Started).TotalSeconds } else { $null } $incomplete = ($null -ne $covered -and $null -ne $ran -and $ran - $covered -gt 10 -and $covered -lt $ran * 0.5) foreach ($p in @{ CoveredSeconds = $covered; Incomplete = $incomplete }.GetEnumerator()) { $Capture | Add-Member -NotePropertyName $p.Key -NotePropertyValue $p.Value -Force } if ($incomplete) { Write-Warning ("The capture covers only {0:N0} of the {1:N0} seconds ProcMon ran - ProcMon wrote an incomplete file. Capture again." -f $covered, $ran) } return $Capture } } } |