Public/Start-NSPProcmonCapture.ps1

function Start-NSPProcmonCapture {
    <#
    .SYNOPSIS
        Starts a background Process Monitor capture to a backing file (.pml), optionally with a
        display filter and a run time. Returns a capture object for Stop-NSPProcmonCapture.
 
    .DESCRIPTION
        ProcMon runs minimized and quiet (/AcceptEula /Quiet /Minimized /BackingFile). It is a
        window of its own - it can't run inside the console - but nothing needs doing in it: the
        tech reproduces the problem in the app, then stops the capture from the console.
 
        Needs elevation (ProcMon loads a driver). Only one ProcMon can capture at a time, so this
        refuses to start while another is running. The capture keeps every event; -FilterPath only
        sets what ProcMon shows if someone opens it. A capture grows by hundreds of MB a minute on
        a busy machine - keep it short, or use -RuntimeSeconds.
 
    .PARAMETER ProcmonPath
        Process Monitor executable (Find-NSPProcmon).
 
    .PARAMETER OutputDirectory
        Folder for the backing file.
 
    .PARAMETER FilterPath
        A .pmc filter to load (New-NSPProcmonFilter).
 
    .PARAMETER RuntimeSeconds
        Stop by itself after this many seconds (0: run until Stop-NSPProcmonCapture).
 
    .PARAMETER MinimumFreeGB
        Warn when the output drive has less free space than this.
 
    .EXAMPLE
        $c = Start-NSPProcmonCapture -ProcmonPath (Find-NSPProcmon) -OutputDirectory C:\Temp -RuntimeSeconds 300
 
    .EXAMPLE
        Start-NSPProcmonCapture -ProcmonPath C:\Tools\Procmon64.exe -OutputDirectory D:\Captures -FilterPath D:\Captures\Violations.pmc
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(Mandatory)][string]$ProcmonPath,
        [Parameter(Mandatory)][string]$OutputDirectory,
        [string]$FilterPath,
        [ValidateRange(0, 86400)][int]$RuntimeSeconds = 0,
        [int]$MinimumFreeGB = 5
    )

    if (-not (Test-NSPProcmonSignature -Path $ProcmonPath)) { throw "$ProcmonPath is not validly signed by Microsoft - not starting it." }
    $running = @(Get-Process -Name 'Procmon', 'Procmon64', 'Procmon64a' -ErrorAction SilentlyContinue)
    if ($running) { throw "Process Monitor is already running (PID $(@($running.Id) -join ', ')). Close it, or stop it with Stop-NSPProcmonCapture, first." }
    if ($FilterPath -and -not (Test-Path -LiteralPath $FilterPath -PathType Leaf)) { throw "Filter file not found: $FilterPath" }

    if (-not (Test-Path -LiteralPath $OutputDirectory)) { New-Item -ItemType Directory -Path $OutputDirectory -Force | Out-Null }
    $OutputDirectory = (Resolve-Path -LiteralPath $OutputDirectory).ProviderPath
    $root = [IO.Path]::GetPathRoot($OutputDirectory)
    try {
        $free = (New-Object IO.DriveInfo($root)).AvailableFreeSpace / 1GB
        if ($free -lt $MinimumFreeGB) { Write-Warning ("Only {0:N1} GB free on {1} - a capture can fill it quickly." -f $free, $root) }
    } catch { Write-Verbose "Free space on $root unknown: $($_.Exception.Message)" }

    $backing = Join-Path $OutputDirectory ("capture_{0}.pml" -f (Get-Date -Format 'yyyyMMdd-HHmmss'))
    $arguments = @('/AcceptEula', '/Quiet', '/Minimized', '/BackingFile', $backing)
    if ($FilterPath) { $arguments += @('/LoadConfig', (Resolve-Path -LiteralPath $FilterPath).ProviderPath) }
    if ($RuntimeSeconds) { $arguments += @('/Runtime', [string]$RuntimeSeconds) }

    if (-not $PSCmdlet.ShouldProcess($backing, 'Start a ProcMon capture')) { return }
    $process = Invoke-NSPProcmonExe -FilePath $ProcmonPath -ArgumentList $arguments
    [pscustomobject][ordered]@{
        PSTypeName     = 'NSP.MDE.Capture'
        ProcmonPath    = $ProcmonPath
        ProcmonVersion = [string](Get-Item -LiteralPath $ProcmonPath).VersionInfo.ProductVersion
        BackingFile    = $backing
        FilterPath     = [string]$FilterPath
        RuntimeSeconds = $RuntimeSeconds
        ProcessId      = if ($process) { $process.Id } else { $null }
        Started        = Get-Date
        Ended          = $null
    }
}