# Shared helpers, compatible with Windows PowerShell 5.1. No raw errors are serialized. function Get-SamsaraTimestamp { [DateTime]::UtcNow.ToString('o') } function Add-SamsaraError { param($Result, $Record, [string]$Scope) $code = 'collection_failed' if ($Record.CategoryInfo.Category -in @('PermissionDenied', 'SecurityError') -or $Record.Exception -is [UnauthorizedAccessException] -or $Record.Exception.HResult -eq -2147024891) { $code = 'access_denied' } elseif ($Record.Exception -is [System.Management.Automation.CommandNotFoundException]) { $code = 'command_unavailable' } $message = switch ($code) { 'access_denied' { 'Current account cannot read this scope.' } 'command_unavailable' { 'Required local inventory command is unavailable.' } default { 'Local query could not complete; raw diagnostics are excluded.' } } if ($script:SamsaraDiagnostic) { # Fixed stages only; never include messages, identifiers, targets or stacks. $type = $Record.Exception.GetType().Name if ($type -notmatch '^[A-Za-z0-9]+Exception$') { $type = 'Exception' } $category = [string]$Record.CategoryInfo.Category $message += ' Exception type: ' + $type + '; category: ' + $category + '.' } $Result.errors += [pscustomobject]@{ code = $code; scope = $Scope; message = $message } if ($code -eq 'access_denied') { $Result.permission_limitations += $Scope + ': read access denied.' } } function Invoke-SamsaraRead { param($Result, [string]$Scope, [scriptblock]$Query) $Result.attempted_subchecks++ try { $ErrorActionPreference = 'Stop' & $Query $Result.successful_subchecks++ } catch { Add-SamsaraError $Result $_ $Scope } } function ConvertTo-SamsaraJson { param($Value, [int]$Depth = 14) try { ConvertTo-Json -InputObject $Value -Depth $Depth -Compress -ErrorAction Stop } catch { # A serialization failure must not emit a complete empty inventory. [Console]::Error.WriteLine('Inventory JSON serialization failed; raw diagnostics are excluded.') exit 3 } } function Get-SamsaraSafeExecutable { param([string]$Value) # Task action arguments, scripts and environment values are deliberately omitted. # Scheduler action values need not be valid filesystem paths. Path.GetFileName # throws on malformed/quoted values in .NET Framework (PowerShell 5.1). $name = (($Value -replace '\\', '/') -split '/')[-1] if ($name -match '^[A-Za-z0-9_. -]+\.(exe|com|cmd|bat|ps1|vbs|js|wsf|msc)$') { return $name } return '[executable name omitted]' } function Invoke-SamsaraSection { param([string]$Name, [scriptblock]$Collector) $result = [ordered]@{ section = $Name; status = 'complete'; message = 'Local read-only query completed within the documented scope.' started_at = Get-SamsaraTimestamp; completed_at = $null errors = @(); permission_limitations = @(); limitations = @() attempted_subchecks = 0; successful_subchecks = 0; data = @() } try { & $Collector $result } catch { Add-SamsaraError $result $_ $Name } if ($result.errors.Count -gt 0) { $result.status = if ($result.successful_subchecks -gt 0) { 'partial' } else { 'failed' } $result.message = 'One or more local queries failed; see sanitized errors and limitations.' } if (-not $script:SamsaraIsElevated -and $Name -in @('roles','services','network','tasks','shares','storage','certificates')) { $result.permission_limitations += 'Not elevated: protected objects may be hidden by the current account permissions.' if ($result.status -eq 'complete') { $result.status = 'partial' } } $result.completed_at = Get-SamsaraTimestamp [pscustomobject]$result } function ConvertTo-SamsaraCheck { param($Section) [ordered]@{ status = $Section.status; message = $Section.message started_at = $Section.started_at; completed_at = $Section.completed_at errors = @($Section.errors); permission_limitations = @($Section.permission_limitations) limitations = @($Section.limitations) } }