[CmdletBinding()] param( [ValidateSet('Inspect', 'Apply', 'EnableExistingAutologon', 'ConfigureAutologon', 'Verify')] [string]$Mode = 'Inspect', [string]$OutputPath = "$env:ProgramData\SotsRe\housekeeping-result.json" ) $ErrorActionPreference = 'Stop' $autologonUser = 're' $winlogonPath = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' $consumerPackages = @('Microsoft.MicrosoftOfficeHub') $taskPaths = @( '\Microsoft\OneDrive\OneDrive Per-Machine Standalone Update Task', '\Microsoft\Windows\Maps\MapsUpdateTask', '\Microsoft\Windows\Windows Error Reporting\QueueReporting', '\Microsoft\Windows\Customer Experience Improvement Program\Consolidator', '\Microsoft\Windows\Customer Experience Improvement Program\UsbCeip', '\Microsoft\Windows\Customer Experience Improvement Program\KernelCeipTask', '\Microsoft\Windows\Windows Defender\Windows Defender Scheduled Scan' ) $policies = @( @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent'; Name = 'DisableWindowsConsumerFeatures'; Value = 1 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent'; Name = 'DisableSoftLanding'; Value = 1 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent'; Name = 'DisableWindowsSpotlightFeatures'; Value = 1 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent'; Name = 'DisableWindowsCopilot'; Value = 1 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Feeds'; Name = 'EnableFeeds'; Value = 0 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR'; Name = 'AllowGameDVR'; Value = 0 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive'; Name = 'DisableFileSyncNGSC'; Value = 1 }, @{ Path = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU'; Name = 'NoAutoRebootWithLoggedOnUsers'; Value = 1 } ) function Get-TaskByPath([string]$TaskPath) { $lastSlash = $TaskPath.LastIndexOf('\') $path = $TaskPath.Substring(0, $lastSlash + 1) $name = $TaskPath.Substring($lastSlash + 1) Get-ScheduledTask -TaskPath $path -TaskName $name -ErrorAction SilentlyContinue } if (-not ('SotsReAutologonNative' -as [type])) { Add-Type -TypeDefinition @' using System; using System.ComponentModel; using System.Runtime.InteropServices; public static class SotsReAutologonNative { [StructLayout(LayoutKind.Sequential)] private struct LSA_OBJECT_ATTRIBUTES { public int Length; public IntPtr RootDirectory; public IntPtr ObjectName; public int Attributes; public IntPtr SecurityDescriptor; public IntPtr SecurityQualityOfService; } [StructLayout(LayoutKind.Sequential)] private struct LSA_UNICODE_STRING { public ushort Length; public ushort MaximumLength; public IntPtr Buffer; } [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern bool LogonUser(string user, string domain, string password, int logonType, int logonProvider, out IntPtr token); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr handle); [DllImport("advapi32.dll")] private static extern uint LsaOpenPolicy(IntPtr systemName, ref LSA_OBJECT_ATTRIBUTES attributes, uint desiredAccess, out IntPtr policyHandle); [DllImport("advapi32.dll")] private static extern uint LsaStorePrivateData(IntPtr policyHandle, ref LSA_UNICODE_STRING key, ref LSA_UNICODE_STRING value); [DllImport("advapi32.dll")] private static extern uint LsaClose(IntPtr handle); [DllImport("advapi32.dll")] private static extern uint LsaNtStatusToWinError(uint status); private static LSA_UNICODE_STRING ToLsaString(string value, out IntPtr buffer) { byte[] bytes = System.Text.Encoding.Unicode.GetBytes(value); buffer = Marshal.AllocHGlobal(bytes.Length + 2); Marshal.Copy(bytes, 0, buffer, bytes.Length); Marshal.WriteInt16(buffer, bytes.Length, 0); return new LSA_UNICODE_STRING { Length = (ushort)bytes.Length, MaximumLength = (ushort)(bytes.Length + 2), Buffer = buffer }; } private static void ZeroAndFree(IntPtr buffer, int bytes) { if (buffer == IntPtr.Zero) return; for (int i = 0; i < bytes; i++) Marshal.WriteByte(buffer, i, 0); Marshal.FreeHGlobal(buffer); } private static void CheckStatus(uint status, string operation) { if (status != 0) throw new Win32Exception((int)LsaNtStatusToWinError(status), operation); } public static void ValidateAndStore(string user, string domain, string password) { IntPtr token; if (!LogonUser(user, domain, password, 2, 0, out token)) throw new Win32Exception(Marshal.GetLastWin32Error(), "Credential validation failed"); try { } finally { CloseHandle(token); } IntPtr policy = IntPtr.Zero, keyBuffer = IntPtr.Zero, passwordBuffer = IntPtr.Zero; try { LSA_OBJECT_ATTRIBUTES attributes = new LSA_OBJECT_ATTRIBUTES(); attributes.Length = Marshal.SizeOf(typeof(LSA_OBJECT_ATTRIBUTES)); CheckStatus(LsaOpenPolicy(IntPtr.Zero, ref attributes, 0x00000020, out policy), "LsaOpenPolicy"); LSA_UNICODE_STRING key = ToLsaString("DefaultPassword", out keyBuffer); LSA_UNICODE_STRING secret = ToLsaString(password, out passwordBuffer); CheckStatus(LsaStorePrivateData(policy, ref key, ref secret), "LsaStorePrivateData"); } finally { if (policy != IntPtr.Zero) LsaClose(policy); ZeroAndFree(keyBuffer, ("DefaultPassword".Length + 1) * 2); ZeroAndFree(passwordBuffer, (password.Length + 1) * 2); } } } '@ } function Get-Inventory { $registry = foreach ($policy in $policies) { $item = Get-ItemProperty -Path $policy.Path -ErrorAction SilentlyContinue $property = if ($item) { $item.PSObject.Properties[$policy.Name] } else { $null } $value = if ($property) { $property.Value } else { $null } [pscustomobject]@{ path = $policy.Path; name = $policy.Name; expected = $policy['Value']; value = $value; compliant = ($value -eq $policy['Value']) } } $tasks = foreach ($taskPath in $taskPaths) { $task = Get-TaskByPath $taskPath [pscustomobject]@{ path = $taskPath; present = ($null -ne $task); state = if ($task) { $task.State.ToString() } else { $null }; compliant = if ($task) { $task.State -eq 'Disabled' } else { $true } } } $processes = Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match 'OneDrive|Teams|GameBar|GameBarFTServer|Widgets|M365Copilot' } | Select-Object ProcessName, Id, SessionId $webviewProcesses = Get-Process -Name 'msedgewebview2' -ErrorAction SilentlyContinue | Select-Object ProcessName, Id, SessionId $packages = foreach ($package in $consumerPackages) { $installed = @(Get-AppxPackage -Name $package -ErrorAction SilentlyContinue) [pscustomobject]@{ name = $package; present = ($installed.Count -gt 0); compliant = ($installed.Count -eq 0) } } $sots = Get-Process -Name 'Sword of the Stars' -ErrorAction SilentlyContinue | Select-Object ProcessName, Id, SessionId $winlogon = Get-ItemProperty -Path $winlogonPath -ErrorAction SilentlyContinue $secretPresent = Test-Path 'HKLM:\SECURITY\Policy\Secrets\DefaultPassword' [pscustomobject]@{ computer = $env:COMPUTERNAME timestamp_utc = (Get-Date).ToUniversalTime().ToString('o') mode = $Mode registry = @($registry) tasks = @($tasks) consumer_processes = @($processes) webview_processes = @($webviewProcesses) consumer_packages = @($packages) sots_processes = @($sots) ssh_service = (Get-Service -Name sshd -ErrorAction SilentlyContinue | Select-Object Name, Status, StartType) autologon = [pscustomobject]@{ enabled = ($winlogon.AutoAdminLogon -eq '1') username = $winlogon.DefaultUserName domain = $winlogon.DefaultDomainName protected_secret_present = $secretPresent compliant = (($winlogon.AutoAdminLogon -eq '1') -and ($winlogon.DefaultUserName -eq $autologonUser) -and $secretPresent) } } } if ($Mode -eq 'Apply') { foreach ($policy in $policies) { if (-not (Test-Path -Path $policy.Path)) { New-Item -Path $policy.Path -Force | Out-Null } New-ItemProperty -Path $policy.Path -Name $policy.Name -PropertyType DWord -Value $policy['Value'] -Force | Out-Null } foreach ($taskPath in $taskPaths) { $task = Get-TaskByPath $taskPath if ($task -and $task.State -ne 'Disabled') { Disable-ScheduledTask -InputObject $task | Out-Null } } Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.ProcessName -match 'OneDrive|Teams|GameBar|GameBarFTServer|Widgets|M365Copilot' } | Stop-Process -Force -ErrorAction SilentlyContinue foreach ($package in $consumerPackages) { Get-AppxPackage -Name $package -ErrorAction SilentlyContinue | Remove-AppxPackage -ErrorAction Stop } } if ($Mode -eq 'ConfigureAutologon') { $password = [Console]::In.ReadToEnd().TrimEnd("`r", "`n") if ([string]::IsNullOrEmpty($password)) { throw 'No autologon credential was received on standard input.' } try { [SotsReAutologonNative]::ValidateAndStore($autologonUser, $env:COMPUTERNAME, $password) New-ItemProperty -Path $winlogonPath -Name 'AutoAdminLogon' -PropertyType String -Value '1' -Force | Out-Null New-ItemProperty -Path $winlogonPath -Name 'DefaultUserName' -PropertyType String -Value $autologonUser -Force | Out-Null New-ItemProperty -Path $winlogonPath -Name 'DefaultDomainName' -PropertyType String -Value $env:COMPUTERNAME -Force | Out-Null } finally { $password = $null } } if ($Mode -eq 'EnableExistingAutologon') { if (-not (Test-Path 'HKLM:\SECURITY\Policy\Secrets\DefaultPassword')) { throw 'The protected DefaultPassword secret is absent; refusing to enable autologon.' } New-ItemProperty -Path $winlogonPath -Name 'AutoAdminLogon' -PropertyType String -Value '1' -Force | Out-Null New-ItemProperty -Path $winlogonPath -Name 'DefaultUserName' -PropertyType String -Value $autologonUser -Force | Out-Null New-ItemProperty -Path $winlogonPath -Name 'DefaultDomainName' -PropertyType String -Value $env:COMPUTERNAME -Force | Out-Null } $result = Get-Inventory $result | Add-Member -NotePropertyName compliant -NotePropertyValue (($result.registry | Where-Object { -not $_.compliant }).Count -eq 0 -and ($result.tasks | Where-Object { -not $_.compliant }).Count -eq 0 -and ($result.consumer_processes.Count -eq 0) -and ($result.consumer_packages | Where-Object { -not $_.compliant }).Count -eq 0 -and (($Mode -ne 'Verify') -or $result.autologon.compliant)) $directory = Split-Path -Parent $OutputPath New-Item -ItemType Directory -Path $directory -Force | Out-Null $result | ConvertTo-Json -Depth 6 | Set-Content -Path $OutputPath -Encoding UTF8 $result | ConvertTo-Json -Depth 6 if ($Mode -eq 'Verify' -and -not $result.compliant) { exit 2 }