Private/Helpers.ps1

function Get-NSPSysmonRegistry {
    # What the installed service and driver record, readable without elevation. ConfigHash is
    # "<ALGORITHM>=<hex>" of the config file's bytes as Sysmon loaded them.
    param([Parameter(Mandatory)][string]$ServiceName)
    $services = 'HKLM:\SYSTEM\CurrentControlSet\Services'
    $service = Get-ItemProperty -LiteralPath (Join-Path $services $ServiceName) -ErrorAction SilentlyContinue
    $driver = (Get-ItemProperty -LiteralPath (Join-Path $services "$ServiceName\Parameters") -ErrorAction SilentlyContinue).DriverName
    if (-not $driver) { $driver = 'SysmonDrv' }
    $parameters = Get-ItemProperty -LiteralPath (Join-Path $services "$driver\Parameters") -ErrorAction SilentlyContinue
    $image = $null
    if ($service.ImagePath) { $image = [Environment]::ExpandEnvironmentVariables(($service.ImagePath -replace '^"([^"]+)".*$', '$1')) }
    [pscustomobject]@{
        ImagePath = $image
        DriverName = $driver
        ConfigFile = $parameters.ConfigFile
        ConfigHash = $parameters.ConfigHash
    }
}

function ConvertTo-NSPSysmonConfigBytes {
    # The bytes Install writes and Test hashes: UTF-8 without BOM, CRLF.
    param([Parameter(Mandatory)][string]$Configuration)
    $text = ($Configuration -replace "`r?`n", "`r`n")
    [Text.UTF8Encoding]::new($false).GetBytes($text)
}

function Get-NSPSysmonHash {
    # Same notation as the driver's ConfigHash value, in the algorithm it names (SHA256 by default).
    param([Parameter(Mandatory)][byte[]]$Bytes, [string]$Algorithm = 'SHA256')
    $hasher = [Security.Cryptography.HashAlgorithm]::Create($Algorithm.ToUpperInvariant())
    if (-not $hasher) { throw "Unknown hash algorithm '$Algorithm'." }
    try { "$($Algorithm.ToUpperInvariant())=$(-join ($hasher.ComputeHash($Bytes) | ForEach-Object { $_.ToString('X2') }))" } finally { $hasher.Dispose() }
}

function Invoke-NSPSysmonExe {
    # Runs Sysmon and returns its exit code and combined output. Sysmon needs elevation for
    # everything except -s and -?.
    param([Parameter(Mandatory)][string]$FilePath, [string[]]$ArgumentList)
    $quoted = foreach ($arg in $ArgumentList) { if ($arg -match '[\s"]') { '"' + ($arg -replace '"', '\"') + '"' } else { $arg } }
    $psi = [Diagnostics.ProcessStartInfo]::new($FilePath, (@($quoted) -join ' '))
    $psi.UseShellExecute = $false
    $psi.CreateNoWindow = $true
    $psi.RedirectStandardOutput = $true
    $psi.RedirectStandardError = $true
    Write-Verbose "$FilePath $($psi.Arguments)"
    $process = [Diagnostics.Process]::Start($psi)
    try {
        $stdout = $process.StandardOutput.ReadToEndAsync()
        $stderr = $process.StandardError.ReadToEndAsync()
        $process.WaitForExit()
        $lines = @(($stdout.Result + "`n" + $stderr.Result) -split "`r?`n" | Where-Object { $_.Trim() } |
                Where-Object { $_ -notmatch '^(System Monitor v|By Mark Russinovich|Copyright \(C\)|Using libxml2|Sysinternals - )' })
        [pscustomobject]@{ ExitCode = $process.ExitCode; Output = $lines }
    } finally {
        $process.Dispose()
    }
}

function ConvertFrom-NSPSysmonRuleText {
    # Rules from "sysmon -c" output: an "- <Event> onmatch: include|exclude" header, then
    # "<Field> filter: <condition> value: '<value>'" lines under it.
    param([string[]]$Line)
    $eventName = $null; $onMatch = $null
    foreach ($l in $Line) {
        if ($l -match "^\s*-\s*(?<event>\w+)\s+onmatch:\s*(?<match>include|exclude)") {
            $eventName = $Matches.event; $onMatch = $Matches.match
        } elseif ($eventName -and $l -match "^\s*(?<field>\w+)\s+filter:\s*(?<cond>.+?)\s+value:\s*'(?<value>.*)'\s*$") {
            [pscustomobject]@{ Event = $eventName; OnMatch = $onMatch; Field = $Matches.field; Condition = $Matches.cond; Value = $Matches.value }
        }
    }
}