45 lines
2.8 KiB
PowerShell
45 lines
2.8 KiB
PowerShell
# Optional read-only process inventory. Never launches, closes or controls a GUI.
|
|
#Requires -Version 7.0
|
|
$ErrorActionPreference = 'Stop'
|
|
$trialPath = Join-Path $PSScriptRoot 'trial'
|
|
$resultPath = Join-Path $trialPath 'loaded-modules.json'
|
|
if (Test-Path -LiteralPath $resultPath) { throw 'Preserve existing module receipt.' }
|
|
$launch = Get-Content -LiteralPath (Join-Path $trialPath 'launch.json') -Raw | ConvertFrom-Json
|
|
$identity = Get-Content -LiteralPath (Join-Path $trialPath 'process-identity.json') -Raw | ConvertFrom-Json
|
|
$before = Get-Content -LiteralPath (Join-Path $trialPath 'package-before.json') -Raw | ConvertFrom-Json
|
|
$receipt = [ordered]@{ atUtc = [DateTime]::UtcNow.ToString('o'); pid = $launch.pid; status = 'PENDING'; modules = @(); scope = 'Point-in-time loaded modules of the exact launched process; no GUI action or universal runtime dependency claim.' }
|
|
try {
|
|
$guiProcess = Get-Process -Id $launch.pid -ErrorAction Stop
|
|
if (-not [string]::Equals($guiProcess.Path, $identity.path, [StringComparison]::OrdinalIgnoreCase) -or
|
|
$guiProcess.StartTime.ToUniversalTime().ToFileTimeUtc() -ne $identity.creation_filetime) {
|
|
throw 'PID no longer identifies the original launch.'
|
|
}
|
|
$receipt.creation_filetime = $identity.creation_filetime
|
|
$systemRoot = [IO.Path]::GetFullPath($launch.environment.SystemRoot).TrimEnd('\') + '\'
|
|
$packageRoot = [IO.Path]::GetFullPath($launch.package).TrimEnd('\') + '\'
|
|
$items = foreach ($module in $guiProcess.Modules) {
|
|
$full = [IO.Path]::GetFullPath($module.FileName)
|
|
$inWindows = $full.StartsWith($systemRoot, [StringComparison]::OrdinalIgnoreCase)
|
|
$inPackage = $full.StartsWith($packageRoot, [StringComparison]::OrdinalIgnoreCase)
|
|
$hash = (Get-FileHash -LiteralPath $full -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
if ($inPackage) {
|
|
$relative = $full.Substring($packageRoot.Length).Replace('\', '/')
|
|
$recorded = $before.PSObject.Properties[$relative]
|
|
if (-not $recorded -or $recorded.Value -cne $hash) { throw "Package module differs: $relative" }
|
|
}
|
|
[ordered]@{ name = $module.ModuleName; path = $full; sha256 = $hash; windowsPath = $inWindows; packagePath = $inPackage }
|
|
}
|
|
$receipt.modules = @($items)
|
|
if (-not $receipt.modules.Count) { throw 'No modules observed.' }
|
|
$unexpected = @($items | Where-Object { -not $_.windowsPath -and -not $_.packagePath })
|
|
$receipt.unexpected = $unexpected
|
|
if ($unexpected.Count) { throw 'Unexpected non-Windows, non-package module; inspect receipt.' }
|
|
$receipt.status = 'PASS'
|
|
} catch {
|
|
$receipt.status = 'FAIL'
|
|
$receipt.error = $_.ToString()
|
|
} finally {
|
|
[IO.File]::WriteAllText($resultPath, ($receipt | ConvertTo-Json -Depth 10) + "`n", [Text.UTF8Encoding]::new($false))
|
|
}
|
|
if ($receipt.status -ne 'PASS') { throw $receipt.error }
|