Fix Windows task and certificate collector reliability
This commit is contained in:
@@ -4,12 +4,23 @@ function Get-SamsaraCertificates {
|
||||
Invoke-SamsaraRead $Result 'Local machine certificate store list' {
|
||||
$collection.stores = @(Get-ChildItem -LiteralPath 'Cert:\LocalMachine' -ErrorAction Stop)
|
||||
}
|
||||
# Listing store names is not evidence that any listed store was readable.
|
||||
if ($collection.stores.Count -gt 0) { $Result.successful_subchecks-- }
|
||||
foreach ($store in $collection.stores) {
|
||||
Invoke-SamsaraRead $Result 'Local machine public certificate metadata' {
|
||||
Get-ChildItem -LiteralPath $store.PSPath -ErrorAction Stop | ForEach-Object {
|
||||
$Result.data += [pscustomobject]@{ subject = [string]$_.Subject; issuer = [string]$_.Issuer
|
||||
expires = $_.NotAfter.ToUniversalTime().ToString('o'); thumbprint = [string]$_.Thumbprint
|
||||
store = [string]$store.PSChildName }
|
||||
# Certificate provider PSPath may omit LocalMachine (including on 5.1).
|
||||
# Construct an explicit provider path from the enumerated store name.
|
||||
$storeName = [string]$store.PSChildName
|
||||
Invoke-SamsaraRead $Result 'Certificate store query' {
|
||||
if ($storeName -notmatch '^[A-Za-z0-9_. -]+$') { throw 'Invalid store name' }
|
||||
$certificates = @(Get-ChildItem -LiteralPath ('Cert:\LocalMachine\' + $storeName) -ErrorAction Stop)
|
||||
foreach ($certificate in $certificates) {
|
||||
Invoke-SamsaraRead $Result 'Certificate metadata projection' {
|
||||
$row = [pscustomobject]@{ subject = [string]$certificate.Subject; issuer = [string]$certificate.Issuer
|
||||
expires = $certificate.NotAfter.ToUniversalTime().ToString('o'); thumbprint = [string]$certificate.Thumbprint
|
||||
store = $storeName }
|
||||
$null = ConvertTo-Json -InputObject $row -Depth 4 -ErrorAction Stop
|
||||
$Result.data += $row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
#requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param([ValidateSet('all','system','roles','services','network','tasks','software','storage','shares','certificates')][string]$Section = 'all')
|
||||
param([ValidateSet('all','system','roles','services','network','tasks','software','storage','shares','certificates')][string]$Section = 'all', [switch]$Diagnostic)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
$WarningPreference = 'SilentlyContinue'
|
||||
$InformationPreference = 'SilentlyContinue'
|
||||
$env:PSModulePath = Join-Path $PSHOME 'Modules'
|
||||
if ($PSVersionTable.PSEdition -eq 'Core') {
|
||||
# Inbox Windows modules remain under WindowsPowerShell when invoked from PS7.
|
||||
$env:PSModulePath += ';' + (Join-Path ([Environment]::SystemDirectory) 'WindowsPowerShell\v1.0\Modules')
|
||||
}
|
||||
$script:SamsaraDiagnostic = $Diagnostic.IsPresent
|
||||
[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
|
||||
if ([Environment]::OSVersion.Platform -ne [PlatformID]::Win32NT) {
|
||||
[Console]::Error.WriteLine('This collector requires a local Windows Server execution host.')
|
||||
@@ -30,7 +35,7 @@ $collectors = [ordered]@{
|
||||
}
|
||||
if ($Section -ne 'all') {
|
||||
$result = Invoke-SamsaraSection $Section $collectors[$Section]
|
||||
ConvertTo-Json -InputObject $result -Depth 12 -Compress
|
||||
ConvertTo-SamsaraJson $result 12
|
||||
exit 0
|
||||
}
|
||||
# Direct PowerShell use emits the same complete inventory envelope as the Python backend.
|
||||
@@ -50,7 +55,7 @@ foreach ($name in $collectors.Keys) {
|
||||
} elseif ($name -eq 'shares') { $inventory.storage += @($result.data) }
|
||||
else { $inventory[$name] = @($result.data) }
|
||||
}
|
||||
ConvertTo-Json -InputObject ([ordered]@{ schema_version='1.0'; synthetic=$false; collected_at=$collectedAt
|
||||
server=$server; checks=$checks; inventory=$inventory }) -Depth 14 -Compress
|
||||
ConvertTo-SamsaraJson ([ordered]@{ schema_version='1.0'; synthetic=$false; collected_at=$collectedAt
|
||||
server=$server; checks=$checks; inventory=$inventory }) 14
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
function Get-SamsaraTasks {
|
||||
param($Result)
|
||||
Invoke-SamsaraRead $Result 'Scheduled tasks' {
|
||||
$Result.data = @(Get-ScheduledTask -ErrorAction Stop | ForEach-Object {
|
||||
$task = $_
|
||||
$tasks = @(Get-ScheduledTask -ErrorAction Stop)
|
||||
foreach ($task in $tasks) {
|
||||
Invoke-SamsaraRead $Result 'Scheduled task metadata projection' {
|
||||
if ($null -eq $task -or [string]::IsNullOrEmpty([string]$task.TaskName)) { throw 'Missing task identity' }
|
||||
$triggers = @($task.Triggers | ForEach-Object {
|
||||
$type = if ($_.CimClass) { [string]$_.CimClass.CimClassName } else { 'Unknown trigger' }
|
||||
# Select only known scheduler metadata; never serialize entire CIM objects.
|
||||
@@ -10,11 +12,14 @@ function Get-SamsaraTasks {
|
||||
'{0}; start={1}; enabled={2}; daysInterval={3}; weeksInterval={4}; daysOfWeek={5}; repeat={6}' -f $type, $_.StartBoundary, $_.Enabled, $_.DaysInterval, $_.WeeksInterval, $_.DaysOfWeek, $repetition
|
||||
})
|
||||
$executables = @($task.Actions | ForEach-Object { Get-SamsaraSafeExecutable ([string]$_.Execute) })
|
||||
[pscustomobject]@{ name = [string]$task.TaskName; path = [string]$task.TaskPath
|
||||
$row = [pscustomobject]@{ name = [string]$task.TaskName; path = [string]$task.TaskPath
|
||||
enabled = [bool]$task.Settings.Enabled; state = [string]$task.State
|
||||
account = [string]$task.Principal.UserId; executables = ($executables -join '; ')
|
||||
account = if ($task.Principal.UserId) { [string]$task.Principal.UserId } else { [string]$task.Principal.GroupId }; executables = ($executables -join '; ')
|
||||
triggers = if ($triggers.Count) { $triggers -join ' | ' } else { 'No trigger metadata visible; manual or external invocation remains possible' } }
|
||||
})
|
||||
$null = ConvertTo-Json -InputObject $row -Depth 4 -ErrorAction Stop
|
||||
$Result.data += $row
|
||||
}
|
||||
}
|
||||
}
|
||||
$Result.limitations += 'Visible Task Scheduler registrations only. Arguments, inline scripts, task XML, event queries and execution history are omitted. Non-executable actions may lack an executable name. Trigger fields are descriptive, not a prediction of execution.'
|
||||
}
|
||||
|
||||
@@ -13,6 +13,13 @@ function Add-SamsaraError {
|
||||
'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.' }
|
||||
}
|
||||
@@ -21,15 +28,28 @@ 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.
|
||||
$name = [IO.Path]::GetFileName(($Value -replace '\\', '/'))
|
||||
# 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]'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user