xetup/scripts/03-system-registry.ps1
X9 Dev 0cfe7510aa
Some checks failed
release / build-and-release (push) Failing after 32s
feat(03): disable hibernation, Smart App Control; rework Edge config
- powercfg /hibernate off added to powercfg block
- Smart App Control: VerifiedAndReputablePolicyState=0 (Win11, permanent)
- Edge: replaced non-working Recommended policies with two-tier approach:
  - Mandatory: only HideFirstRunExperience, DefaultBrowserSettingEnabled,
    DiagnosticData, FeedbackSurveysEnabled (privacy/first-run, locked)
  - initial_preferences JSON written to Edge Application dir: sets UI
    defaults (favorites bar, no home button, clean NTP, no shopping/rewards)
    that users can freely override in Edge settings
- Removed invalid PerformanceButtonEnabled policy key
- SPEC.md and web/spec/index.html updated accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 17:53:07 +02:00

460 lines
23 KiB
PowerShell

<#
.SYNOPSIS
Applies system-wide registry settings and power configuration (HKLM).
.DESCRIPTION
Sets machine-wide registry tweaks under HKLM that apply to all users. Disables
unwanted telemetry and cloud features, configures Edge policies, sets power plan
timeouts, and disables proxy auto-detect. Uninstalls the pre-installed OneDrive
consumer version via OneDriveSetup.exe /uninstall - intentional for a clean MSP
deployment baseline. No DisableFileSyncNGSC policy key is set, so M365 installation
can install and run its own OneDrive version without restriction.
.ITEMS
bypass-nro-oobe-bypassnro-1: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE\BypassNRO = 1. Bypasses the "Let's connect you to a network" OOBE screen. Enables offline Windows setup without forcing a Microsoft account login.
zakaz-auto-instalace-teams: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications\ConfigureChatAutoInstall = 0. Prevents Windows from auto-installing Teams Personal during OOBE or after Cumulative Updates.
zakaz-cloud-optimized-content: ContentDeliveryManager\DisableCloudOptimizedContent = 1. Stops Windows from pushing sponsored app suggestions, tips from Microsoft servers, and "Get even more from Windows" prompts.
zakaz-widgets-news-and-interests: HKLM\SOFTWARE\Policies\Microsoft\Dsh\AllowNewsAndInterests = 0. Disables the Widgets taskbar button and panel (news feed, weather, stocks). Not relevant for business deployments.
hesla-bez-expirace-net-accounts-maxpwage: net accounts /maxpwage:UNLIMITED. Sets the local password expiration policy to never. MSP-managed machines handle password rotation via other means (Atera, domain policy, manual).
casova-zona-central-europe-standard-time: Set-TimeZone -Id "Central Europe Standard Time". UTC+1 (UTC+2 in summer DST). Applied system-wide. Critical for correct log timestamps, scheduled task timing, and calendar sync.
zakaz-gamedvr: HKLM\SOFTWARE\Policies\Microsoft\Windows\GameDVR\AppCaptureEnabled = 0. Disables Xbox Game Bar screen capture overlay. Reduces background resource usage and eliminates unintended capture prompts on business machines.
zakaz-smart-app-control: HKLM\SYSTEM\CurrentControlSet\Control\CI\Policy\VerifiedAndReputablePolicyState = 0. Disables Windows 11 Smart App Control (Intelligent App Control). Prevents SAC from blocking unrecognized business software during deployment. 0=Off, 1=Evaluation, 2=Enforcing. Setting to 0 is permanent - cannot be re-enabled without OS reset.
zakaz-hibernace-powercfg-h-off: powercfg /hibernate off. Disables hibernation entirely. Removes hiberfil.sys, prevents hibernate-related power state issues, and ensures clean shutdown behavior on business machines.
edge-skryt-first-run-experience: HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0 (Mandatory). Suppresses Edge welcome wizard and default browser prompts on first launch.
edge-telemetrie-mandatory: DiagnosticData=0, FeedbackSurveysEnabled=0 (Mandatory). Telemetry and feedback always off - user cannot change.
edge-initial-preferences: Writes C:\Program Files (x86)\Microsoft\Edge\Application\initial_preferences. Sets Edge UI defaults (clean NTP, favorites bar visible, home button hidden, search suggestions off) that the user can freely override in Edge settings. Read by Edge once on first profile creation.
onedrive-uninstall-intentional: Uninstalls the pre-installed OneDrive consumer version via OneDriveSetup.exe /uninstall and removes Start Menu shortcut. Intentional for clean MSP deployment baseline. No DisableFileSyncNGSC policy key is set - M365 installation can reinstall and run OneDrive normally. Only the stock consumer pre-install is removed.
powercfg-nastaveni-spotreba-energie: powercfg /change: standby-timeout-ac 0 (never sleep on AC), monitor-timeout-ac 60 (screen off after 60 min on AC), standby-timeout-dc 30 (sleep after 30 min on battery), monitor-timeout-dc 15 (screen off after 15 min on battery). Applied to active power plan.
proxy-auto-detect-zakaz-autodetect-0: HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\AutoDetect = 0. Disables WPAD (Web Proxy Auto-Discovery). Eliminates startup delays from WPAD DNS lookup and prevents MITM via rogue WPAD on untrusted networks.
#>
param(
[string]$ConfigPath,
[string]$LogFile
)
. "$PSScriptRoot\common.ps1"
$Config = Load-Config $ConfigPath
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class RegPrivilege {
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen);
[DllImport("kernel32.dll", ExactSpelling=true)]
static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
[DllImport("advapi32.dll", SetLastError=true)]
static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
[StructLayout(LayoutKind.Sequential, Pack=1)]
struct TokPriv1Luid { public int Count; public long Luid; public int Attr; }
const int TOKEN_QUERY = 0x8;
const int TOKEN_ADJUST = 0x20;
const int SE_PRIVILEGE_ENABLED = 2;
public static bool Enable(string privilege) {
IntPtr htok = IntPtr.Zero;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false;
TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false;
return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
}
}
"@ -ErrorAction SilentlyContinue
function Grant-RegWriteAccess {
param([string]$Path)
# Grants Administrators FullControl on a TrustedInstaller-owned registry key.
# Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
try {
[RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
[RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null
$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 }
}
# Take ownership (requires SeTakeOwnershipPrivilege)
$key = $rootKey.OpenSubKey($subkey,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
if ($key) {
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
$key.SetAccessControl($acl)
$key.Close()
}
# Grant FullControl to Administrators (requires ChangePermissions)
$key = $rootKey.OpenSubKey($subkey,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[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()
}
Write-Log " ACL fixed for $Path" -Level INFO
}
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
# -----------------------------------------------------------------------
# Always-on: password and timezone (fundamental system settings)
# -----------------------------------------------------------------------
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
}
$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
}
# -----------------------------------------------------------------------
# System tweaks (Windows features, cloud noise, Xbox, Recall, Search UI)
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "systemTweaks") {
Write-Log " Applying system tweaks" -Level INFO
# 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
# Disable Outlook (new) 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
# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
# this policy key ensures the setting survives Windows Updates.
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
-Name "SearchOnTaskbarMode" -Value 0
# Start menu - hide Recommended section (Win11)
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
-Name "HideRecommendedSection" -Value 1
# Disable Smart App Control (Intelligent App Control) - Win11 only key, safe to set on Win10
# 0=Off, 1=Evaluation, 2=Enforcing. Protected key - Set-Reg SYSTEM task fallback handles ACL.
Set-Reg -Path "HKLM:\SYSTEM\CurrentControlSet\Control\CI\Policy" `
-Name "VerifiedAndReputablePolicyState" -Value 0
} else {
Write-Log "systemTweaks feature disabled - skipping" -Level INFO
}
# -----------------------------------------------------------------------
# Microsoft Edge policies + initial_preferences
#
# Strategy (two-tier):
# Mandatory GPO - only privacy/first-run items (user cannot change)
# initial_preferences - all UI defaults (user CAN change freely in Edge settings)
#
# initial_preferences is read by Edge ONCE when a new user profile is created.
# It has no effect on profiles that already exist (i.e. Edge was already launched).
# For a clean deployment where xetup runs before any user opens Edge this is reliable.
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "edgePolicies") {
Write-Log " Applying Edge policies" -Level INFO
$edgeMandatory = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
# --- Mandatory: privacy and first-run suppression only ---
Set-Reg -Path $edgeMandatory -Name "HideFirstRunExperience" -Value 1
Set-Reg -Path $edgeMandatory -Name "DefaultBrowserSettingEnabled" -Value 0
Set-Reg -Path $edgeMandatory -Name "DiagnosticData" -Value 0
Set-Reg -Path $edgeMandatory -Name "FeedbackSurveysEnabled" -Value 0
# Suppress desktop shortcut creation on Edge install/update (EdgeUpdate key)
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
-Name "CreateDesktopShortcutDefault" -Value 0
# --- initial_preferences: UI defaults the user can override ---
# Path: Edge Application dir (stable channel on x64 Windows)
$edgeAppDir = "C:\Program Files (x86)\Microsoft\Edge\Application"
$edgeInitPrefPath = "$edgeAppDir\initial_preferences"
if (Test-Path $edgeAppDir) {
# Build JSON as hashtable - ConvertTo-Json handles serialization
$prefs = [ordered]@{
# Favorites bar always visible
bookmark_bar = [ordered]@{ show_on_all_tabs = $true }
browser = [ordered]@{
show_home_button = $false # hide home button
}
# Disable search-as-you-type suggestions (privacy + less noise)
search = [ordered]@{ suggest_enabled = $false }
# First-run / import suppression (belt-and-suspenders alongside Mandatory policy)
distribution = [ordered]@{
suppress_first_run_default_browser_prompt = $true
skip_first_run_ui = $true
do_not_create_desktop_shortcut = $true
make_chrome_default = $false
import_bookmarks = $false
import_history = $false
import_home_page = $false
import_search_engine = $false
show_welcome_page = $false
}
# No first-run tabs (e.g. Edge welcome page)
first_run_tabs = @()
# Edge-specific defaults (best-effort: key names are internal/semi-documented)
# These set sane defaults; if Edge ignores an unknown key it is a no-op.
edge = [ordered]@{
newTabPageContentEnabled = $false # clean new tab page
newTabPageQuickLinksEnabled = $false # no quick links on NTP
newTabPageBackgroundEnabled = $false # no background image on NTP
showMicrosoftRewards = $false # no rewards badge
collections_enabled = $false # no Collections
shopping_list_enabled = $false # no shopping assistant
share_experience_enabled = $false # no Share button
}
}
try {
$json = $prefs | ConvertTo-Json -Depth 6
$utf8 = New-Object System.Text.UTF8Encoding $false # no BOM
[System.IO.File]::WriteAllText($edgeInitPrefPath, $json, $utf8)
Write-Log " Edge initial_preferences written: $edgeInitPrefPath" -Level OK
}
catch {
Write-Log " Failed to write Edge initial_preferences: $_" -Level WARN
}
} else {
Write-Log " Edge application dir not found - initial_preferences skipped" -Level WARN
}
} else {
Write-Log "edgePolicies feature disabled - skipping" -Level INFO
}
# -----------------------------------------------------------------------
# OneDrive - uninstall from clean Windows (no policy block)
# NOTE: No policy key is set intentionally - M365 installation can reinstall
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "oneDriveUninstall") {
Write-Log " Uninstalling OneDrive" -Level INFO
$oneDrivePaths = @(
"$env:SystemRoot\System32\OneDriveSetup.exe"
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
)
foreach ($odPath in $oneDrivePaths) {
if (Test-Path $odPath) {
try {
& $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
}
} else {
Write-Log "oneDriveUninstall feature disabled - skipping" -Level INFO
}
# -----------------------------------------------------------------------
# Power configuration
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "powercfg") {
Write-Log " Applying power configuration" -Level INFO
# Disable hibernation (removes hiberfil.sys, prevents hibernate-related power issues)
$hibResult = & powercfg /hibernate off 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Log " powercfg /hibernate off" -Level OK
} else {
Write-Log " powercfg /hibernate off failed: $hibResult" -Level WARN
}
$powercfgArgs = @(
@("/change", "standby-timeout-ac", "0"), # never sleep on AC
@("/change", "monitor-timeout-ac", "60"), # screen off after 60 min on AC
@("/change", "standby-timeout-dc", "30"), # sleep after 30 min on battery
@("/change", "monitor-timeout-dc", "15") # screen off after 15 min on battery
)
foreach ($a in $powercfgArgs) {
$result = & powercfg @a 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Log " powercfg $($a -join ' ')" -Level OK
} else {
Write-Log " powercfg $($a -join ' ') failed: $result" -Level WARN
}
}
} else {
Write-Log "powercfg feature disabled - skipping" -Level INFO
}
# -----------------------------------------------------------------------
# Proxy auto-detect disable (WPAD)
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "proxyDisable") {
Write-Log " Disabling WPAD proxy auto-detect" -Level INFO
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" `
-Name "AutoDetect" -Value 0
} else {
Write-Log "proxyDisable feature disabled - skipping" -Level INFO
}
Write-Log "Step 3 complete" -Level OK