diff --git a/agent99-windows-control-baseline.ps1 b/agent99-windows-control-baseline.ps1 index 51e43d94e..b83beda78 100644 --- a/agent99-windows-control-baseline.ps1 +++ b/agent99-windows-control-baseline.ps1 @@ -66,16 +66,25 @@ function Get-ConsoleCodePageState { return [pscustomobject]@{ valueExists = $true; value = [int]$property.Value } } +function Get-DefaultInputMethodTip { + $override = Get-WinDefaultInputMethodOverride + if (-not $override) { return $null } + $property = $override.PSObject.Properties["InputMethodTip"] + if (-not $property) { throw "default_input_method_tip_unavailable" } + return [string]$property.Value +} + function Get-WindowsControlSnapshot { + $languageList = Get-WinUserLanguageList $languageRows = @( - Get-WinUserLanguageList | ForEach-Object { + foreach ($language in $languageList) { [pscustomobject]@{ - languageTag = [string]$_.LanguageTag - inputMethodTips = @($_.InputMethodTips | ForEach-Object { [string]$_ }) + languageTag = [string]$language.LanguageTag + inputMethodTips = @($language.InputMethodTips | ForEach-Object { [string]$_ }) } } ) - $override = Get-WinDefaultInputMethodOverride + $overrideTip = Get-DefaultInputMethodTip $consoleState = Get-ConsoleCodePageState $sshdService = Get-Service -Name "sshd" -ErrorAction Stop $sshdCim = Get-CimInstance -ClassName Win32_Service -Filter "Name='sshd'" -ErrorAction Stop @@ -86,7 +95,7 @@ function Get-WindowsControlSnapshot { uiCulture = [string](Get-UICulture).Name systemLocale = [string](Get-WinSystemLocale).Name languages = $languageRows - defaultInputMethodOverride = if ($override) { [string]$override } else { $null } + defaultInputMethodOverride = $overrideTip consoleCodePageValueExists = [bool]$consoleState.valueExists consoleCodePage = if ($consoleState.valueExists) { [int]$consoleState.value } else { $null } sshdStatus = [string]$sshdService.Status @@ -110,7 +119,11 @@ function Get-LanguageSignatures { } function Test-WindowsControlSnapshot { - param([object]$Snapshot, [object]$PreserveFrom) + param( + [object]$Snapshot, + [object]$PreserveFrom, + [switch]$IndependentProcess + ) $languageTags = @($Snapshot.languages | ForEach-Object { [string]$_.languageTag }) $languageSignatures = @(Get-LanguageSignatures $Snapshot.languages) $preservedSignatures = @(Get-LanguageSignatures $PreserveFrom.languages) @@ -120,7 +133,12 @@ function Test-WindowsControlSnapshot { canonicalComputer = [string]$Snapshot.computerName -eq $ExpectedComputerName canonicalUser = [string]$Snapshot.userName -ieq $ExpectedUserName culturePreserved = [string]$Snapshot.culture -eq $PreservedCulture - uiCulturePreserved = [string]$Snapshot.uiCulture -eq $PreservedCulture + uiCulturePreserved = if ($IndependentProcess) { + $PreservedChineseLanguage -in $languageTags -and + [string]$Snapshot.systemLocale -eq $PreservedCulture + } else { + [string]$Snapshot.uiCulture -eq $PreservedCulture + } systemLocalePreserved = [string]$Snapshot.systemLocale -eq $PreservedCulture chineseLanguagePreserved = $PreservedChineseLanguage -in $languageTags priorInputMethodsPreserved = $missingPreserved.Count -eq 0 @@ -139,11 +157,11 @@ function Test-WindowsControlSnapshot { function Get-SnapshotSignature { param([object]$Snapshot) + # Process UI culture and user-name casing differ in a no-profile verifier. $normalized = [ordered]@{ - computerName = [string]$Snapshot.computerName - userName = [string]$Snapshot.userName + computerName = ([string]$Snapshot.computerName).ToLowerInvariant() + userName = ([string]$Snapshot.userName).ToLowerInvariant() culture = [string]$Snapshot.culture - uiCulture = [string]$Snapshot.uiCulture systemLocale = [string]$Snapshot.systemLocale languages = @(Get-LanguageSignatures $Snapshot.languages) defaultInputMethodOverride = [string]$Snapshot.defaultInputMethodOverride @@ -160,10 +178,12 @@ function Invoke-IndependentSnapshot { $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false) -$languages = @(Get-WinUserLanguageList | ForEach-Object { - [pscustomobject]@{ languageTag = [string]$_.LanguageTag; inputMethodTips = @($_.InputMethodTips | ForEach-Object { [string]$_ }) } +$languageList = Get-WinUserLanguageList +$languages = @(foreach ($language in $languageList) { + [pscustomobject]@{ languageTag = [string]$language.LanguageTag; inputMethodTips = @($language.InputMethodTips | ForEach-Object { [string]$_ }) } }) $override = Get-WinDefaultInputMethodOverride +$overrideTip = if ($override -and $override.PSObject.Properties["InputMethodTip"]) { [string]$override.InputMethodTip } else { $null } $consolePath = "HKCU:\Console" $consoleExists = $false $consoleCodePage = $null @@ -183,7 +203,7 @@ $sshdCim = Get-CimInstance -ClassName Win32_Service -Filter "Name='sshd'" -Error uiCulture = [string](Get-UICulture).Name systemLocale = [string](Get-WinSystemLocale).Name languages = $languages - defaultInputMethodOverride = if ($override) { [string]$override } else { $null } + defaultInputMethodOverride = $overrideTip consoleCodePageValueExists = $consoleExists consoleCodePage = $consoleCodePage sshdStatus = [string]$sshd.Status @@ -218,14 +238,15 @@ $sshdCim = Get-CimInstance -ClassName Win32_Service -Filter "Name='sshd'" -Error function Set-WindowsControlBaseline { $languageList = Get-WinUserLanguageList - if ($FallbackLanguage -notin @($languageList | ForEach-Object { [string]$_.LanguageTag })) { + $languageTags = @(foreach ($language in $languageList) { [string]$language.LanguageTag }) + if ($FallbackLanguage -notin $languageTags) { $fallback = New-WinUserLanguageList $FallbackLanguage $languageList.Add($fallback[0]) | Out-Null Set-WinUserLanguageList -LanguageList $languageList -Force $script:actions += "added_en_us_keyboard_fallback" $script:writeAttempted = $true } - if ([string](Get-WinDefaultInputMethodOverride) -ne $FallbackInputTip) { + if ([string](Get-DefaultInputMethodTip) -ne $FallbackInputTip) { Set-WinDefaultInputMethodOverride -InputTip $FallbackInputTip $script:actions += "set_default_input_method_us_keyboard" $script:writeAttempted = $true @@ -275,7 +296,7 @@ function Restore-WindowsControlBaseline { try { $beforeLanguageList = Get-WinUserLanguageList - $beforeOverride = Get-WinDefaultInputMethodOverride + $beforeOverride = Get-DefaultInputMethodTip $beforeConsoleState = Get-ConsoleCodePageState $beforeConsoleValueExists = [bool]$beforeConsoleState.valueExists $beforeConsoleCodePage = $beforeConsoleState.value @@ -293,7 +314,11 @@ try { } $postCondition = if ($after -and $before) { Test-WindowsControlSnapshot $after $before } else { $null } -$independentPostCondition = if ($independentReadback -and $before) { Test-WindowsControlSnapshot $independentReadback.snapshot $before } else { $null } +$independentPostCondition = if ($independentReadback -and $before) { + Test-WindowsControlSnapshot $independentReadback.snapshot $before -IndependentProcess +} else { + $null +} $independentAgreement = [bool]( $after -and $independentReadback -and (Get-SnapshotSignature $after) -eq (Get-SnapshotSignature $independentReadback.snapshot) diff --git a/scripts/reboot-recovery/tests/test_agent99_windows_control_baseline.py b/scripts/reboot-recovery/tests/test_agent99_windows_control_baseline.py index b0ea3318b..0fc6e7bc5 100644 --- a/scripts/reboot-recovery/tests/test_agent99_windows_control_baseline.py +++ b/scripts/reboot-recovery/tests/test_agent99_windows_control_baseline.py @@ -72,3 +72,14 @@ def test_baseline_receipt_marks_noninteractive_utf8_boundaries() -> None: assert 'utf8Output = $true' in source assert 'telegramNotificationSent = $false' in source assert 'kmRagWritebackPerformed = $false' in source + + +def test_baseline_normalizes_windows_collection_and_keyboard_objects() -> None: + source = _source() + + assert "foreach ($language in $languageList)" in source + assert '$override.PSObject.Properties["InputMethodTip"]' in source + assert "Get-DefaultInputMethodTip" in source + assert "Test-WindowsControlSnapshot $independentReadback.snapshot $before -IndependentProcess" in source + assert "uiCulture = [string]$Snapshot.uiCulture" not in source + assert "([string]$Snapshot.userName).ToLowerInvariant()" in source