Modules/Private/72-CimDepthProbe.ps1
|
function Invoke-RangerCimDepthProbe { <# .SYNOPSIS v2.1.0 (#234): deep WinRM credential verification. .DESCRIPTION After WinRM preflight selects a credential, confirm the credential can actually read the CIM namespaces Ranger needs. An account with valid WinRM logon rights but no WMI / DCOM access rights passes the shallow preflight, then collectors fail mid-run with 'Invalid namespace' or 'Access denied' from deep in Get-CimInstance. Issues one probe per representative namespace against the first reachable target: - root/MSCluster -> MSCluster_Cluster - root/virtualization/v2 -> Msvm_VirtualSystemManagementService - root/Microsoft/Windows/Storage -> MSFT_StoragePool Returns a hashtable with overall CimDepth status: - 'sufficient' all probes succeeded - 'partial' one or more namespaces denied (warn, do not throw) - 'denied' every probe failed (caller decides whether to throw) - 'skipped' no reachable target or no credential #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] [AllowEmptyCollection()] [string[]]$Targets, [pscredential]$Credential, [int]$TimeoutSeconds = 15 ) if (-not $Targets -or $Targets.Count -eq 0) { return [ordered]@{ status = 'skipped'; reason = 'No targets supplied to CIM depth probe.'; probes = @() } } # Pick the first target that already resolves — the shallow preflight has # been run before us, so a DNS-resolvable name is a safe assumption. $target = [string]$Targets[0] $namespaces = @( [ordered]@{ Namespace = 'root/MSCluster'; ClassName = 'MSCluster_Cluster'; Label = 'Failover Cluster (MSCluster)' } [ordered]@{ Namespace = 'root/virtualization/v2'; ClassName = 'Msvm_VirtualSystemManagementService';Label = 'Hyper-V (virtualization/v2)' } [ordered]@{ Namespace = 'root/Microsoft/Windows/Storage'; ClassName = 'MSFT_StoragePool'; Label = 'Storage Spaces (Storage)' } ) $cimSession = $null try { $cimArgs = @{ ComputerName = $target; OperationTimeoutSec = $TimeoutSeconds; ErrorAction = 'Stop' } if ($Credential) { $cimArgs['Credential'] = $Credential } $cimSession = New-CimSession @cimArgs } catch { return [ordered]@{ status = 'skipped' reason = "Could not establish a CIM session to $target : $($_.Exception.Message)" target = $target probes = @() } } $probes = New-Object System.Collections.Generic.List[pscustomobject] try { foreach ($ns in $namespaces) { $probe = [ordered]@{ namespace = $ns.Namespace className = $ns.ClassName label = $ns.Label status = 'unknown' message = $null } try { $null = Get-CimInstance -CimSession $cimSession -Namespace $ns.Namespace -ClassName $ns.ClassName -ErrorAction Stop | Select-Object -First 1 $probe.status = 'ok' } catch { $msg = [string]$_.Exception.Message $probe.message = $msg if ($msg -match '(?i)Access is denied|0x80070005|HRESULT\s*:\s*0x80041003|WMI.*authoriz') { $probe.status = 'denied' } elseif ($msg -match '(?i)Invalid namespace|WBEM_E_INVALID_NAMESPACE|0x8004100E') { $probe.status = 'missing-namespace' } else { $probe.status = 'error' } } [void]$probes.Add([pscustomobject]$probe) } } finally { if ($cimSession) { try { Remove-CimSession -CimSession $cimSession -ErrorAction SilentlyContinue } catch { } } } $okCount = @($probes | Where-Object { $_.status -eq 'ok' }).Count $deniedCount = @($probes | Where-Object { $_.status -in @('denied','error','missing-namespace') }).Count $overall = if ($okCount -eq $namespaces.Count) { 'sufficient' } elseif ($okCount -eq 0) { 'denied' } else { 'partial' } return [ordered]@{ status = $overall target = $target probedAtUtc = (Get-Date).ToUniversalTime().ToString('o') probes = @($probes) summary = [ordered]@{ total = $namespaces.Count ok = $okCount denied = $deniedCount } } } |