Private/Test-SingleFact.ps1
|
function Test-SingleFact { <# .SYNOPSIS Dispatches verification for a single claim based on its verification_method. .DESCRIPTION Big switch on verification_method. Each case runs the appropriate PowerShell command, compares expected vs actual, and returns the updated claim with actual_value, status, last_checked, and detail. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject]$Claim, [Parameter()] [PSCredential]$Credential, [Parameter()] [hashtable]$UnreachableServers = @{} ) $result = $Claim.PSObject.Copy() $result.last_checked = (Get-Date).ToString('o') $subject = $Claim.subject $expected = $Claim.expected_value $method = $Claim.verification_method # Check if the target server is already known unreachable if ($UnreachableServers.ContainsKey($subject)) { $result.status = 'unreachable' $result.actual_value = 'Server previously marked unreachable this run' return $result } try { switch ($method) { 'ad_computer' { try { $computer = Get-ADComputer -Identity $subject -Properties OperatingSystem, IPv4Address, Enabled -ErrorAction Stop switch ($Claim.claim_type) { 'server_exists' { $result.actual_value = if ($computer) { 'exists' } else { 'not_found' } $result.status = if ($computer) { 'verified' } else { 'drift' } } 'server_os' { $result.actual_value = $computer.OperatingSystem $result.status = if ($computer.OperatingSystem -like "*$expected*") { 'verified' } else { 'drift' } } 'server_enabled' { $result.actual_value = $computer.Enabled.ToString() $result.status = if ($computer.Enabled.ToString() -eq $expected) { 'verified' } else { 'drift' } } default { $result.actual_value = $computer | Select-Object Name, OperatingSystem, IPv4Address, Enabled | ConvertTo-Json -Compress $result.status = 'verified' } } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { $result.actual_value = 'not_found' $result.status = 'drift' } catch { if ($_.Exception.Message -match 'Unable to contact the server|is not recognized|not loaded') { $result.actual_value = 'AD module not available' $result.status = 'unverifiable' } else { throw } } } 'ad_user' { try { $user = Get-ADUser -Identity $subject -Properties MemberOf, Enabled, Description -ErrorAction Stop switch ($Claim.claim_type) { 'user_exists' { $result.actual_value = if ($user) { 'exists' } else { 'not_found' } $result.status = if ($user) { 'verified' } else { 'drift' } } 'user_enabled' { $result.actual_value = $user.Enabled.ToString() $result.status = if ($user.Enabled.ToString() -eq $expected) { 'verified' } else { 'drift' } } default { $result.actual_value = $user.Name $result.status = 'verified' } } } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] { $result.actual_value = 'not_found' $result.status = 'drift' } catch { if ($_.Exception.Message -match 'Unable to contact the server|is not recognized|not loaded') { $result.actual_value = 'AD module not available' $result.status = 'unverifiable' } else { throw } } } 'ad_group' { try { $group = Get-ADGroup -Identity $subject -ErrorAction Stop switch ($Claim.claim_type) { 'group_exists' { $result.actual_value = if ($group) { 'exists' } else { 'not_found' } $result.status = if ($group) { 'verified' } else { 'drift' } } 'group_members' { $members = Get-ADGroupMember -Identity $subject -ErrorAction Stop | Select-Object -ExpandProperty SamAccountName $result.actual_value = ($members -join ', ') # Check if expected member is in the group $result.status = if ($members -contains $expected) { 'verified' } else { 'drift' } } default { $result.actual_value = $group.Name $result.status = 'verified' } } } catch { if ($_.Exception.Message -match 'Unable to contact the server|is not recognized|not loaded') { $result.actual_value = 'AD module not available' $result.status = 'unverifiable' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'drift' } } } 'dns_resolve' { try { $dns = Resolve-DnsName -Name $subject -Type A -ErrorAction Stop $resolvedIPs = ($dns | Where-Object { $_.QueryType -eq 'A' } | Select-Object -ExpandProperty IPAddress) -join ', ' $result.actual_value = $resolvedIPs $result.status = if ($resolvedIPs -match [regex]::Escape($expected)) { 'verified' } else { 'drift' } } catch { if ($_.Exception.Message -match 'DNS name does not exist|Non-existent domain') { $result.actual_value = 'DNS record not found' $result.status = 'drift' } else { $result.actual_value = "DNS error: $($_.Exception.Message)" $result.status = 'unreachable' } } } 'dns_record' { try { $record = Get-DnsServerResourceRecord -ZoneName $subject -ErrorAction Stop $result.actual_value = ($record | Select-Object -First 5 | ForEach-Object { "$($_.HostName) $($_.RecordType)" }) -join '; ' $result.status = if ($result.actual_value -match [regex]::Escape($expected)) { 'verified' } else { 'drift' } } catch { if ($_.Exception.Message -match 'is not recognized|not loaded') { $result.actual_value = 'DnsServer module not available' $result.status = 'unverifiable' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unreachable' } } } 'cim_os' { try { $cimParams = @{ ClassName = 'Win32_OperatingSystem'; ErrorAction = 'Stop' } if ($subject -ne $env:COMPUTERNAME -and $subject -ne 'localhost') { $sessionParams = @{ ComputerName = $subject; ErrorAction = 'Stop' } if ($Credential) { $sessionParams['Credential'] = $Credential } $session = New-CimSession @sessionParams $cimParams['CimSession'] = $session } $os = Get-CimInstance @cimParams $result.actual_value = "$($os.Caption) $($os.Version)" $result.status = if ($result.actual_value -like "*$expected*" -or $expected -like "*$($os.Caption)*") { 'verified' } else { 'drift' } if ($session) { Remove-CimSession $session -ErrorAction SilentlyContinue } } catch { if ($_.Exception.Message -match 'WinRM|access is denied|network path|RPC server') { $result.actual_value = "Unreachable: $($_.Exception.Message)" $result.status = 'unreachable' $UnreachableServers[$subject] = $true } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unverifiable' } } } 'cim_service' { try { $serviceName = $expected -replace '\s.*', '' # Extract likely service name $filter = "Name LIKE '%$($subject.ToLower() -replace '[^a-z0-9]', '%')%'" $cimParams = @{ ClassName = 'Win32_Service'; Filter = $filter; ErrorAction = 'Stop' } if ($subject -ne $env:COMPUTERNAME -and $subject -ne 'localhost') { # Subject might be the server, not the service. Check claim for server context. $targetServer = $subject $sessionParams = @{ ComputerName = $targetServer; ErrorAction = 'Stop' } if ($Credential) { $sessionParams['Credential'] = $Credential } $session = New-CimSession @sessionParams $cimParams['CimSession'] = $session # Re-build filter for service on remote machine $searchTerms = $expected -split '\s+' | Where-Object { $_.Length -gt 2 } | Select-Object -First 2 $filterParts = $searchTerms | ForEach-Object { "Name LIKE '%$_%' OR DisplayName LIKE '%$_%'" } $cimParams['Filter'] = $filterParts -join ' OR ' } $services = Get-CimInstance @cimParams if ($services) { $result.actual_value = ($services | ForEach-Object { "$($_.DisplayName) ($($_.State))" }) -join '; ' $result.status = 'verified' } else { $result.actual_value = 'Service not found' $result.status = 'drift' } if ($session) { Remove-CimSession $session -ErrorAction SilentlyContinue } } catch { if ($_.Exception.Message -match 'WinRM|access is denied|network path|RPC server') { $result.actual_value = "Unreachable: $($_.Exception.Message)" $result.status = 'unreachable' $UnreachableServers[$subject] = $true } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unverifiable' } } } 'cim_disk' { try { $cimParams = @{ ClassName = 'Win32_LogicalDisk'; ErrorAction = 'Stop' } if ($subject -ne $env:COMPUTERNAME -and $subject -ne 'localhost') { $sessionParams = @{ ComputerName = $subject; ErrorAction = 'Stop' } if ($Credential) { $sessionParams['Credential'] = $Credential } $session = New-CimSession @sessionParams $cimParams['CimSession'] = $session } $disks = Get-CimInstance @cimParams $result.actual_value = ($disks | ForEach-Object { "$($_.DeviceID) $([Math]::Round($_.Size / 1GB, 1))GB (Free: $([Math]::Round($_.FreeSpace / 1GB, 1))GB)" }) -join '; ' $result.status = if ($result.actual_value -like "*$expected*") { 'verified' } else { 'drift' } if ($session) { Remove-CimSession $session -ErrorAction SilentlyContinue } } catch { if ($_.Exception.Message -match 'WinRM|access is denied|network path|RPC server') { $result.actual_value = "Unreachable: $($_.Exception.Message)" $result.status = 'unreachable' $UnreachableServers[$subject] = $true } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unverifiable' } } } 'network_test' { try { # Parse expected for port number $port = $null if ($expected -match '(\d+)') { $port = [int]$Matches[1] } $testParams = @{ ComputerName = $subject; WarningAction = 'SilentlyContinue' } if ($port) { $testParams['Port'] = $port } $test = Test-NetConnection @testParams if ($port) { $result.actual_value = "Port ${port}: $($test.TcpTestSucceeded)" $result.status = if ($test.TcpTestSucceeded) { 'verified' } else { 'drift' } } else { $result.actual_value = "Ping: $($test.PingSucceeded), RemoteAddress: $($test.RemoteAddress)" $result.status = if ($test.PingSucceeded) { 'verified' } else { 'unreachable' } } } catch { $result.actual_value = "Network test failed: $($_.Exception.Message)" $result.status = 'unreachable' } } 'file_share' { try { $uncPath = if ($expected -match '^\\\\') { $expected } else { "\\$subject\$expected" } $exists = Test-Path $uncPath -ErrorAction Stop $result.actual_value = if ($exists) { 'accessible' } else { 'not_accessible' } $result.status = if ($exists) { 'verified' } else { 'drift' } } catch { $result.actual_value = "Share test failed: $($_.Exception.Message)" $result.status = 'unreachable' } } 'certificate' { try { if ($subject -eq $env:COMPUTERNAME -or $subject -eq 'localhost') { $certs = Get-ChildItem -Path 'Cert:\LocalMachine\My' -ErrorAction Stop } else { $invokeParams = @{ ComputerName = $subject ScriptBlock = { Get-ChildItem -Path 'Cert:\LocalMachine\My' } ErrorAction = 'Stop' } if ($Credential) { $invokeParams['Credential'] = $Credential } $certs = Invoke-Command @invokeParams } $matchingCerts = $certs | Where-Object { $_.Subject -like "*$expected*" -or $_.DnsNameList.Unicode -contains $expected } if ($matchingCerts) { $cert = $matchingCerts | Select-Object -First 1 $expired = $cert.NotAfter -lt (Get-Date) $result.actual_value = "Found: $($cert.Subject), Expires: $($cert.NotAfter.ToString('yyyy-MM-dd')), Expired: $expired" $result.status = if ($expired) { 'drift' } else { 'verified' } } else { $result.actual_value = 'Certificate not found' $result.status = 'drift' } } catch { if ($_.Exception.Message -match 'WinRM|access is denied|network path') { $result.actual_value = "Unreachable: $($_.Exception.Message)" $result.status = 'unreachable' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unverifiable' } } } 'gpo' { try { $gpo = Get-GPO -Name $expected -ErrorAction Stop $result.actual_value = "$($gpo.DisplayName) (Status: $($gpo.GpoStatus), Modified: $($gpo.ModificationTime))" $result.status = 'verified' } catch { if ($_.Exception.Message -match 'is not recognized|not loaded') { $result.actual_value = 'GroupPolicy module not available' $result.status = 'unverifiable' } elseif ($_.Exception.Message -match 'was not found') { $result.actual_value = 'GPO not found' $result.status = 'drift' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unreachable' } } } 'dhcp_scope' { try { $scopes = Get-DhcpServerv4Scope -ErrorAction Stop $matchingScope = $scopes | Where-Object { $_.ScopeId -eq $expected -or $_.Name -like "*$expected*" } if ($matchingScope) { $result.actual_value = "$($matchingScope.ScopeId) ($($matchingScope.Name)) - $($matchingScope.State)" $result.status = 'verified' } else { $result.actual_value = 'DHCP scope not found' $result.status = 'drift' } } catch { if ($_.Exception.Message -match 'is not recognized|not loaded') { $result.actual_value = 'DhcpServer module not available' $result.status = 'unverifiable' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unreachable' } } } 'ad_domain' { try { $domain = Get-ADDomain -ErrorAction Stop $result.actual_value = "$($domain.DNSRoot) (Forest: $($domain.Forest), Mode: $($domain.DomainMode))" $result.status = if ($result.actual_value -like "*$expected*") { 'verified' } else { 'drift' } } catch { if ($_.Exception.Message -match 'Unable to contact the server|is not recognized|not loaded') { $result.actual_value = 'AD module not available' $result.status = 'unverifiable' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unreachable' } } } 'registry' { try { if ($subject -eq $env:COMPUTERNAME -or $subject -eq 'localhost') { $regValue = Get-ItemProperty -Path $expected -ErrorAction Stop $result.actual_value = $regValue | ConvertTo-Json -Compress $result.status = 'verified' } else { $invokeParams = @{ ComputerName = $subject ScriptBlock = { param($path) Get-ItemProperty -Path $path -ErrorAction Stop } ArgumentList = @($expected) ErrorAction = 'Stop' } if ($Credential) { $invokeParams['Credential'] = $Credential } $regValue = Invoke-Command @invokeParams $result.actual_value = $regValue | ConvertTo-Json -Compress $result.status = 'verified' } } catch { if ($_.Exception.Message -match 'WinRM|access is denied|network path') { $result.actual_value = "Unreachable: $($_.Exception.Message)" $result.status = 'unreachable' $UnreachableServers[$subject] = $true } elseif ($_.Exception.Message -match 'does not exist|Cannot find path') { $result.actual_value = 'Registry key not found' $result.status = 'drift' } else { $result.actual_value = "Error: $($_.Exception.Message)" $result.status = 'unverifiable' } } } 'unverifiable' { $result.actual_value = 'No automated verification method available' $result.status = 'unverifiable' } default { $result.actual_value = "Unknown verification method: $method" $result.status = 'unverifiable' } } } catch { # Catch-all for unexpected errors $result.actual_value = "Unexpected error: $($_.Exception.Message)" $result.status = 'unreachable' Write-Warning "Verification failed for $subject ($method): $($_.Exception.Message)" } return $result } |