Private/Probes.System.ps1
|
function Invoke-HLLocalGuestAccountProbe { [CmdletBinding()] param( [AllowNull()] [object]$SystemContext ) $isDomainController = $null -ne $SystemContext -and [string]$SystemContext.DetectedRole -eq 'DomainController' try { if ($isDomainController) { # Domain controllers have no local SAM; the built-in Guest is the # RID 501 account of the DC's own domain. Lookups stay SID-based # because the account name is localized. $scope = 'Domain accounts (RID 501)' $accounts = @(Get-CimInstance -ClassName Win32_UserAccount -Filter "SID LIKE '%-501'" -ErrorAction Stop) # Win32_UserAccount reports the NetBIOS domain, which can differ # from the DNS prefix; prefer the match, fall back to the only hit. $domainLabel = (([string]$SystemContext.Domain) -split '\.')[0] $guest = $accounts | Where-Object { [string]$_.Domain -ieq $domainLabel } | Select-Object -First 1 if ($null -eq $guest) { $guest = $accounts | Select-Object -First 1 } } else { $scope = 'Local accounts' $accounts = @(Get-CimInstance -ClassName Win32_UserAccount -Filter 'LocalAccount=True' -ErrorAction Stop) $guest = $accounts | Where-Object { [string]$_.SID -match '-501$' } | Select-Object -First 1 } if ($null -eq $guest) { $searchEvidence = [pscustomobject][ordered]@{ Scope = $scope SidSuffix = '-501' DetectedRole = if ($null -ne $SystemContext) { [string]$SystemContext.DetectedRole } else { $null } Domain = if ($null -ne $SystemContext) { [string]$SystemContext.Domain } else { $null } AccountsExamined = $accounts.Count } return Get-HLProbeResult -Status Unknown -Expected 'Disabled' -Actual 'Built-in Guest account not located' -Message 'The RID 501 built-in Guest account could not be located in the examined scope.' -Evidence $searchEvidence } $evidence = [pscustomobject][ordered]@{ Name = [string]$guest.Name Domain = [string]$guest.Domain Scope = $scope SID = [string]$guest.SID Disabled = [bool]$guest.Disabled Status = [string]$guest.Status } if ([bool]$guest.Disabled) { return Get-HLProbeResult -Status Pass -Expected 'Disabled' -Actual 'Disabled' -Message 'The built-in Guest account is disabled.' -Evidence $evidence } return Get-HLProbeResult -Status Fail -Expected 'Disabled' -Actual 'Enabled' -Message 'The built-in Guest account is enabled.' -Evidence $evidence } catch { return Get-HLProbeResult -Status Error -Expected 'Disabled' -Actual $null -Message "Unable to query the built-in Guest account: $($_.Exception.Message)" } } function Invoke-HLCredentialGuardProbe { [CmdletBinding()] param() try { $deviceGuard = Get-CimInstance -Namespace 'root\Microsoft\Windows\DeviceGuard' -ClassName Win32_DeviceGuard -ErrorAction Stop $configured = @($deviceGuard.SecurityServicesConfigured | ForEach-Object { [int]$_ }) $running = @($deviceGuard.SecurityServicesRunning | ForEach-Object { [int]$_ }) $evidence = [pscustomobject][ordered]@{ VirtualizationBasedSecurityStatus = [int]$deviceGuard.VirtualizationBasedSecurityStatus SecurityServicesConfigured = $configured SecurityServicesRunning = $running } if (1 -in $running) { return Get-HLProbeResult -Status Pass -Expected 'Credential Guard running' -Actual 'Running' -Message 'Credential Guard is reported as running.' -Evidence $evidence } if (1 -in $configured) { return Get-HLProbeResult -Status Fail -Expected 'Credential Guard running' -Actual 'Configured but not running' -Message 'Credential Guard is configured but the security service is not running.' -Evidence $evidence } return Get-HLProbeResult -Status Fail -Expected 'Credential Guard running' -Actual 'Not configured' -Message 'Credential Guard is not configured or running.' -Evidence $evidence } catch { return Get-HLProbeResult -Status Unknown -Expected 'Credential Guard running' -Actual $null -Message "Unable to query the Device Guard provider: $($_.Exception.Message)" } } function Invoke-HLDeviceGuardServiceProbe { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Control, [AllowNull()] [object]$CollectionContext ) $serviceId = [int]$Control.parameters.serviceId $serviceName = [string]$Control.parameters.serviceName $expected = "$serviceName running" try { $deviceGuard = Get-HLProviderSnapshot -CollectionContext $CollectionContext -Name 'DeviceGuard' -Factory { Get-CimInstance -Namespace 'root\Microsoft\Windows\DeviceGuard' -ClassName Win32_DeviceGuard -ErrorAction Stop } $configured = @($deviceGuard.SecurityServicesConfigured | ForEach-Object { [int]$_ }) $running = @($deviceGuard.SecurityServicesRunning | ForEach-Object { [int]$_ }) $evidence = [pscustomobject][ordered]@{ ServiceId = $serviceId VirtualizationBasedSecurityStatus = [int]$deviceGuard.VirtualizationBasedSecurityStatus SecurityServicesConfigured = $configured SecurityServicesRunning = $running } if ($serviceId -in $running) { return Get-HLProbeResult -Status Pass -Expected $expected -Actual 'Running' -Message "$serviceName is reported as running." -Evidence $evidence } if ($serviceId -in $configured) { return Get-HLProbeResult -Status Fail -Expected $expected -Actual 'Configured but not running' -Message "$serviceName is configured but the security service is not running." -Evidence $evidence } return Get-HLProbeResult -Status Fail -Expected $expected -Actual 'Not configured' -Message "$serviceName is not configured or running." -Evidence $evidence } catch { return Get-HLProbeResult -Status Unknown -Expected $expected -Actual $null -Message "Unable to query the Device Guard provider: $($_.Exception.Message)" } } function Invoke-HLDeviceGuardCodeIntegrityProbe { [CmdletBinding()] param( [AllowNull()] [object]$CollectionContext ) $expected = 'App Control for Business policy enforced' try { $deviceGuard = Get-HLProviderSnapshot -CollectionContext $CollectionContext -Name 'DeviceGuard' -Factory { Get-CimInstance -Namespace 'root\Microsoft\Windows\DeviceGuard' -ClassName Win32_DeviceGuard -ErrorAction Stop } $kernelStatus = ConvertTo-HLInteger -Value $deviceGuard.CodeIntegrityPolicyEnforcementStatus $userStatus = if (Test-HLProperty -InputObject $deviceGuard -Name 'UsermodeCodeIntegrityPolicyEnforcementStatus') { ConvertTo-HLInteger -Value $deviceGuard.UsermodeCodeIntegrityPolicyEnforcementStatus } else { $null } $evidence = [pscustomobject][ordered]@{ CodeIntegrityPolicyEnforcementStatus = $kernelStatus UsermodeCodeIntegrityPolicyEnforcementStatus = $userStatus } if ($null -eq $kernelStatus) { return Get-HLProbeResult -Status Unknown -Expected $expected -Actual $null -Message 'The Device Guard provider did not report a code integrity enforcement status.' -Evidence $evidence } switch ([int]$kernelStatus) { 2 { return Get-HLProbeResult -Status Pass -Expected $expected -Actual 'Enforced' -Message 'A code integrity policy is deployed and enforced.' -Evidence $evidence } 1 { return Get-HLProbeResult -Status Warning -Expected $expected -Actual 'Audit mode' -Message 'A code integrity policy is deployed in audit mode and does not block untrusted code.' -Evidence $evidence } default { return Get-HLProbeResult -Status Fail -Expected $expected -Actual 'Not deployed' -Message 'No enforced application control policy was reported.' -Evidence $evidence } } } catch { return Get-HLProbeResult -Status Unknown -Expected $expected -Actual $null -Message "Unable to query the Device Guard provider: $($_.Exception.Message)" } } function Invoke-HLFirewallProfilesProbe { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Control, [AllowNull()] [object]$CollectionContext ) if ($null -eq (Get-Command -Name Get-NetFirewallProfile -ErrorAction SilentlyContinue)) { return Get-HLProbeResult -Status Unknown -Expected 'Windows Firewall profile configuration' -Actual $null -Message 'Get-NetFirewallProfile is not available.' } try { $profiles = @(Get-HLProviderSnapshot -CollectionContext $CollectionContext -Name 'FirewallProfiles' -Factory { @(Get-NetFirewallProfile -PolicyStore ActiveStore -ErrorAction Stop | Where-Object { [string]$_.Name -in @('Domain', 'Private', 'Public') }) }) if ($profiles.Count -lt 3) { return Get-HLProbeResult -Status Unknown -Expected 'Domain, Private, and Public profiles' -Actual (@($profiles.Name) -join ', ') -Message 'Not every expected firewall profile was returned.' -Evidence $profiles } $evidence = @($profiles | ForEach-Object { [pscustomobject][ordered]@{ Name = [string]$_.Name Enabled = [string]$_.Enabled DefaultInboundAction = [string]$_.DefaultInboundAction DefaultOutboundAction = [string]$_.DefaultOutboundAction } }) $disabled = @($evidence | Where-Object { (ConvertTo-HLBoolean -Value $_.Enabled) -ne $true }) $notBlocking = @($evidence | Where-Object { [string]$_.DefaultInboundAction -notmatch '^(?i:Block)$' }) $requireInboundBlock = (Test-HLProperty -InputObject $Control.parameters -Name 'requireDefaultInboundBlock') -and [bool]$Control.parameters.requireDefaultInboundBlock if ($disabled.Count -gt 0) { $disabledNames = @($disabled.Name) -join ', ' if ($requireInboundBlock) { return Get-HLProbeResult -Status Fail -Expected 'Default inbound action Block on every enabled profile' -Actual ('Profile disabled: {0}' -f $disabledNames) -Message 'The default inbound action cannot take effect because one or more Windows Firewall profiles are disabled.' -Evidence $evidence } return Get-HLProbeResult -Status Fail -Expected 'All firewall profiles enabled' -Actual ('Disabled or unresolved: {0}' -f $disabledNames) -Message 'One or more Windows Firewall profiles are disabled.' -Evidence $evidence } if ($requireInboundBlock -and $notBlocking.Count -gt 0) { return Get-HLProbeResult -Status Fail -Expected 'Default inbound action Block for all profiles' -Actual ('Not blocking: {0}' -f (@($notBlocking.Name) -join ', ')) -Message 'One or more firewall profiles do not use a default inbound Block action.' -Evidence $evidence } $expected = if ($requireInboundBlock) { 'Enabled with default inbound Block' } else { 'Enabled' } return Get-HLProbeResult -Status Pass -Expected $expected -Actual 'All profiles compliant' -Message 'Every firewall profile matches the baseline.' -Evidence $evidence } catch { return Get-HLProbeResult -Status Error -Expected 'Windows Firewall profile configuration' -Actual $null -Message "Unable to query Windows Firewall profiles: $($_.Exception.Message)" } } function Invoke-HLWindowsOptionalFeatureProbe { [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Control, [AllowNull()] [object]$CollectionContext ) if ($null -eq (Get-Command -Name Get-WindowsOptionalFeature -ErrorAction SilentlyContinue)) { return Get-HLProbeResult -Status Unknown -Expected 'Optional feature state' -Actual $null -Message 'Get-WindowsOptionalFeature is not available.' } $evaluationMode = if (Test-HLProperty -InputObject $Control.parameters -Name 'evaluationMode') { [string]$Control.parameters.evaluationMode } else { 'AllDisabled' } $evidence = New-Object System.Collections.Generic.List[object] $queryErrors = New-Object System.Collections.Generic.List[string] # One full listing serves every optional-feature control in a scan; the # per-feature query path below remains the fallback when the listing fails. $listing = Get-HLProviderSnapshot -CollectionContext $CollectionContext -Name 'WindowsOptionalFeatures' -Factory { try { @(Get-WindowsOptionalFeature -Online -ErrorAction Stop | Where-Object { $null -ne $_ }) } catch { $null } } $featureStates = $null if ($null -ne $listing) { $featureStates = New-Object 'System.Collections.Generic.Dictionary[string,string]' ([StringComparer]::OrdinalIgnoreCase) foreach ($item in @($listing)) { $featureStates[[string]$item.FeatureName] = [string]$item.State } } foreach ($featureName in @($Control.parameters.features)) { if ($null -ne $featureStates) { if ($featureStates.ContainsKey([string]$featureName)) { $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $true State = $featureStates[[string]$featureName] Error = $null Evaluated = $false }) } else { $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $false State = 'NotPresent' Error = $null Evaluated = $false }) } continue } try { $feature = @(Get-WindowsOptionalFeature -Online -FeatureName ([string]$featureName) -ErrorAction Stop | Where-Object { $null -ne $_ }) if ($feature.Count -eq 0) { # Windows 11 24H2 removes retired features such as PowerShell 2.0 # entirely; the query then returns nothing instead of failing. $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $false State = 'NotPresent' Error = $null Evaluated = $false }) continue } $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $true State = [string]$feature[0].State Error = $null Evaluated = $false }) } catch { $message = [string]$_.Exception.Message # 0x800F080C (unknown feature name) is matched through the error # code chain because DISM messages are localized. $notPresent = (Test-HLErrorMatchesCode -Exception $_.Exception -Code 0x800f080c) -or $message -match '(?i:feature name.+unknown|not recognized|does not exist|cannot find|could not be found)' if ($notPresent) { $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $false State = 'NotPresent' Error = $null Evaluated = $false }) } else { $queryErrors.Add(('{0}: {1}' -f [string]$featureName, $message)) $evidence.Add([pscustomobject][ordered]@{ FeatureName = [string]$featureName Present = $null State = 'QueryError' Error = $message Evaluated = $false }) } } } if ($queryErrors.Count -gt 0) { return Get-HLProbeResult -Status Error -Expected 'Disabled or absent' -Actual 'Optional feature state could not be resolved' -Message ('Unable to query one or more optional features: {0}' -f ($queryErrors.ToArray() -join ' | ')) -Evidence $evidence.ToArray() } $present = @($evidence | Where-Object { $_.Present -eq $true }) if ($present.Count -eq 0) { return Get-HLProbeResult -Status Pass -Expected 'Disabled or absent' -Actual 'Feature not present' -Message 'None of the configured feature names are present on this operating system.' -Evidence $evidence.ToArray() } $evaluated = if ($evaluationMode -eq 'FirstPresent') { @($present | Select-Object -First 1) } else { $present } foreach ($item in $evaluated) { $item.Evaluated = $true } $unsafe = @($evaluated | Where-Object { [string]$_.State -in @('Enabled', 'EnablePending') }) $pending = @($evaluated | Where-Object { [string]$_.State -eq 'DisablePending' }) $unexpected = @($evaluated | Where-Object { [string]$_.State -notin @('Disabled', 'DisabledWithPayloadRemoved', 'DisablePending', 'Enabled', 'EnablePending') }) if ($unsafe.Count -gt 0) { return Get-HLProbeResult -Status Fail -Expected 'Disabled or absent' -Actual ((@($unsafe | ForEach-Object { "$($_.FeatureName)=$($_.State)" })) -join '; ') -Message 'At least one evaluated optional feature is enabled or pending enablement.' -Evidence $evidence.ToArray() } if ($pending.Count -gt 0) { return Get-HLProbeResult -Status Warning -Expected 'Disabled' -Actual 'Disable pending' -Message 'The evaluated feature is pending disablement; complete the required restart.' -Evidence $evidence.ToArray() } if ($unexpected.Count -gt 0) { return Get-HLProbeResult -Status Unknown -Expected 'Disabled or absent' -Actual ((@($unexpected | ForEach-Object { "$($_.FeatureName)=$($_.State)" })) -join '; ') -Message 'An optional feature returned an unrecognized state.' -Evidence $evidence.ToArray() } return Get-HLProbeResult -Status Pass -Expected 'Disabled or absent' -Actual 'Disabled' -Message 'Every evaluated optional feature is disabled.' -Evidence $evidence.ToArray() } |