param( [object]$Config, [string]$LogFile ) $ErrorActionPreference = "Continue" function Write-Log { param([string]$Message, [string]$Level = "INFO") $line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" Add-Content -Path $LogFile -Value $line -Encoding UTF8 } function Grant-RegWriteAccess { param([string]$Path) # Grants Administrators FullControl on a registry key that has restricted ACL. # Required for keys owned by TrustedInstaller or with locked-down ACL. try { $hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1' $subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', '' $rootKey = switch ($hive) { "HKLM" { [Microsoft.Win32.Registry]::LocalMachine } "HKCU" { [Microsoft.Win32.Registry]::CurrentUser } "HKCR" { [Microsoft.Win32.Registry]::ClassesRoot } } $rights = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree $regRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership $key = $rootKey.OpenSubKey($subkey, $rights, $regRights) if ($key) { $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None) $acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators") $key.SetAccessControl($acl) $key.Close() } # Re-open with ChangePermissions to grant full control $key = $rootKey.OpenSubKey($subkey, $rights, [System.Security.AccessControl.RegistryRights]::ChangePermissions) if ($key) { $acl = $key.GetAccessControl() $rule = New-Object System.Security.AccessControl.RegistryAccessRule( "BUILTIN\Administrators", [System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit", [System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.AccessControlType]::Allow ) $acl.SetAccessRule($rule) $key.SetAccessControl($acl) $key.Close() } } catch { Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN } } function Set-Reg { param( [string]$Path, [string]$Name, $Value, [string]$Type = "DWord" ) try { if (-not (Test-Path $Path)) { New-Item -Path $Path -Force -ErrorAction Stop | Out-Null } Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop Write-Log " SET $Path\$Name = $Value" -Level OK } catch { # Retry 1: grant write access via ACL manipulation try { Grant-RegWriteAccess -Path $Path if (-not (Test-Path $Path)) { New-Item -Path $Path -Force -ErrorAction Stop | Out-Null } Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop Write-Log " SET $Path\$Name = $Value (after ACL fix)" -Level OK return } catch { } # Retry 2: write via scheduled task running as SYSTEM # SYSTEM has full registry access regardless of key ACL try { $regType = switch ($Type) { "DWord" { "REG_DWORD" } "String" { "REG_SZ" } "ExpandString"{ "REG_EXPAND_SZ" } "MultiString" { "REG_MULTI_SZ" } "QWord" { "REG_QWORD" } default { "REG_DWORD" } } # Convert registry PS path to reg.exe path $regPath = $Path -replace '^HKLM:\\', 'HKLM\' ` -replace '^HKCU:\\', 'HKCU\' ` -replace '^HKCR:\\', 'HKCR\' $tempScript = "$env:TEMP\set-reg-system-$([System.IO.Path]::GetRandomFileName()).ps1" "reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f" | Set-Content -Path $tempScript -Encoding UTF8 $taskName = "TempRegFix-$([System.IO.Path]::GetRandomFileName())" $action = New-ScheduledTaskAction -Execute "cmd.exe" ` -Argument "/c reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f" $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 30) $task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null Start-ScheduledTask -TaskName $taskName Start-Sleep -Seconds 2 Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue Remove-Item $tempScript -Force -ErrorAction SilentlyContinue # Verify it was written $written = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name if ($null -ne $written) { Write-Log " SET $Path\$Name = $Value (via SYSTEM task)" -Level OK } else { Write-Log " FAILED $Path\$Name - SYSTEM task ran but value not found" -Level ERROR } } catch { Write-Log " FAILED $Path\$Name - $_" -Level ERROR } } } function Remove-Reg { param([string]$Path, [string]$Name) try { if (Test-Path $Path) { Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue Write-Log " REMOVED $Path\$Name" -Level OK } } catch { Write-Log " FAILED removing $Path\$Name - $_" -Level ERROR } } Write-Log "3 - Applying HKLM system registry tweaks" -Level STEP # ----------------------------------------------------------------------- # Bypass Network Requirement on OOBE (BypassNRO) # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" ` -Name "BypassNRO" -Value 1 # ----------------------------------------------------------------------- # Disable auto-install of Teams (Chat) # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" ` -Name "ConfigureChatAutoInstall" -Value 0 # ----------------------------------------------------------------------- # Disable Cloud Optimized Content (ads in Start menu etc.) # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" ` -Name "DisableCloudOptimizedContent" -Value 1 Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" ` -Name "DisableWindowsConsumerFeatures" -Value 1 # ----------------------------------------------------------------------- # Disable Widgets (News and Interests) # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" ` -Name "AllowNewsAndInterests" -Value 0 # ----------------------------------------------------------------------- # Microsoft Edge - hide First Run Experience # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" ` -Name "HideFirstRunExperience" -Value 1 # Also disable Edge desktop shortcut creation after install Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" ` -Name "CreateDesktopShortcutDefault" -Value 0 # ----------------------------------------------------------------------- # Password - no expiration # ----------------------------------------------------------------------- Write-Log " Setting password max age to UNLIMITED" -Level INFO $pwResult = & net accounts /maxpwage:UNLIMITED 2>&1 if ($LASTEXITCODE -eq 0) { Write-Log " Password max age set to UNLIMITED" -Level OK } else { Write-Log " Failed to set password max age: $pwResult" -Level ERROR } # ----------------------------------------------------------------------- # Time zone # ----------------------------------------------------------------------- $tz = "Central Europe Standard Time" if ($Config -and $Config.deployment -and $Config.deployment.timezone) { $tz = $Config.deployment.timezone } Write-Log " Setting time zone: $tz" -Level INFO try { Set-TimeZone -Id $tz -ErrorAction Stop Write-Log " Time zone set: $tz" -Level OK } catch { Write-Log " Failed to set time zone: $_" -Level ERROR } # ----------------------------------------------------------------------- # OneDrive - prevent setup and remove shortcuts # ----------------------------------------------------------------------- Write-Log " Disabling OneDrive" -Level INFO # Disable OneDrive via policy Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" ` -Name "DisableFileSyncNGSC" -Value 1 # Remove OneDriveSetup.exe if present $oneDrivePaths = @( "$env:SystemRoot\System32\OneDriveSetup.exe" "$env:SystemRoot\SysWOW64\OneDriveSetup.exe" ) foreach ($odPath in $oneDrivePaths) { if (Test-Path $odPath) { try { # Uninstall first & $odPath /uninstall 2>&1 | Out-Null Write-Log " OneDrive uninstalled via $odPath" -Level OK } catch { Write-Log " OneDrive uninstall failed: $_" -Level WARN } } } # Remove OneDrive Start Menu shortcut $odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk" if (Test-Path $odLnk) { Remove-Item $odLnk -Force -ErrorAction SilentlyContinue Write-Log " Removed OneDrive Start Menu shortcut" -Level OK } # ----------------------------------------------------------------------- # Outlook (new) - disable auto-install via UScheduler # ----------------------------------------------------------------------- Write-Log " Disabling Outlook (new) auto-install" -Level INFO $uschedulerPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate" "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate" ) foreach ($uPath in $uschedulerPaths) { if (Test-Path $uPath) { try { Remove-Item -Path $uPath -Recurse -Force Write-Log " Removed UScheduler key: $uPath" -Level OK } catch { Write-Log " Failed to remove UScheduler key: $_" -Level WARN } } } # ----------------------------------------------------------------------- # Disable GameDVR # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" ` -Name "AllowGameDVR" -Value 0 # ----------------------------------------------------------------------- # Disable Recall (Windows AI feature) # ----------------------------------------------------------------------- Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" ` -Name "DisableAIDataAnalysis" -Value 1 Write-Log "Step 3 complete" -Level OK