1	<#
2	.SYNOPSIS
3	    Applies system-wide registry settings and power configuration (HKLM).
4
5	.DESCRIPTION
6	    Sets machine-wide registry tweaks under HKLM that apply to all users. Disables
7	    unwanted telemetry and cloud features, configures Edge policies, sets power plan
8	    timeouts, and disables proxy auto-detect. Uninstalls the pre-installed OneDrive
9	    consumer version via OneDriveSetup.exe /uninstall - intentional for a clean MSP
10	    deployment baseline. No DisableFileSyncNGSC policy key is set, so M365 installation
11	    can install and run its own OneDrive version without restriction.
12
13	.ITEMS
14	    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.
15	    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.
16	    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.
17	    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.
18	    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).
19	    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.
20	    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.
21	    edge-skryt-first-run-experience: HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0. Suppresses Edge welcome wizard and default browser prompts on first launch.
22	    edge-policies-panel-oblibeny-vyhledavac: FavoritesBarEnabled=1 (always show), DefaultSearchProviderEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines removes other providers.
23	    edge-policies-tlacitka-zobrazit: DownloadsButtonEnabled=1, HistoryButtonEnabled=1.
24	    edge-policies-tlacitka-skryt: HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0 (Drop), WebCaptureEnabled=0 (Screenshot), ShareAllowed=0.
25	    edge-policies-obsah-a-telemetrie: NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, SpotlightExperiencesAndRecommendationsEnabled=0, PersonalizationReportingEnabled=0, EdgeShoppingAssistantEnabled=0, ShowMicrosoftRewards=0, HubsSidebarEnabled=0, SearchSuggestEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0.
26	    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.
27	    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.
28	    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.
29	#>
30	param(
31	    [object]$Config,
32	    [string]$LogFile
33	)
34
35	$ErrorActionPreference = "Continue"
36
37	function Write-Log {
38	    param([string]$Message, [string]$Level = "INFO")
39	    $line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
40	    Add-Content -Path $LogFile -Value $line -Encoding UTF8
41	}
42
43	Add-Type -TypeDefinition @"
44	using System;
45	using System.Runtime.InteropServices;
46	public class RegPrivilege {
47	    [DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
48	    static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen);
49	    [DllImport("kernel32.dll", ExactSpelling=true)]
50	    static extern IntPtr GetCurrentProcess();
51	    [DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
52	    static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
53	    [DllImport("advapi32.dll", SetLastError=true)]
54	    static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
55	    [StructLayout(LayoutKind.Sequential, Pack=1)]
56	    struct TokPriv1Luid { public int Count; public long Luid; public int Attr; }
57	    const int TOKEN_QUERY = 0x8;
58	    const int TOKEN_ADJUST = 0x20;
59	    const int SE_PRIVILEGE_ENABLED = 2;
60	    public static bool Enable(string privilege) {
61	        IntPtr htok = IntPtr.Zero;
62	        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false;
63	        TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED;
64	        if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false;
65	        return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
66	    }
67	}
68	"@ -ErrorAction SilentlyContinue
69
70	function Grant-RegWriteAccess {
71	    param([string]$Path)
72	    # Grants Administrators FullControl on a TrustedInstaller-owned registry key.
73	    # Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
74	    try {
75	        [RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
76	        [RegPrivilege]::Enable("SeRestorePrivilege")       | Out-Null
77
78	        $hive    = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
79	        $subkey  = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
80	        $rootKey = switch ($hive) {
81	            "HKLM" { [Microsoft.Win32.Registry]::LocalMachine }
82	            "HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
83	            "HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
84	        }
85
86	        # Take ownership (requires SeTakeOwnershipPrivilege)
87	        $key = $rootKey.OpenSubKey($subkey,
88	            [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
89	            [System.Security.AccessControl.RegistryRights]::TakeOwnership)
90	        if ($key) {
91	            $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
92	            $acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
93	            $key.SetAccessControl($acl)
94	            $key.Close()
95	        }
96
97	        # Grant FullControl to Administrators (requires ChangePermissions)
98	        $key = $rootKey.OpenSubKey($subkey,
99	            [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
100	            [System.Security.AccessControl.RegistryRights]::ChangePermissions)
101	        if ($key) {
102	            $acl  = $key.GetAccessControl()
103	            $rule = New-Object System.Security.AccessControl.RegistryAccessRule(
104	                "BUILTIN\Administrators",
105	                [System.Security.AccessControl.RegistryRights]::FullControl,
106	                [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
107	                [System.Security.AccessControl.PropagationFlags]::None,
108	                [System.Security.AccessControl.AccessControlType]::Allow)
109	            $acl.SetAccessRule($rule)
110	            $key.SetAccessControl($acl)
111	            $key.Close()
112	        }
113	        Write-Log "  ACL fixed for $Path" -Level INFO
114	    }
115	    catch {
116	        Write-Log "  Grant-RegWriteAccess failed for $Path - $_" -Level WARN
117	    }
118	}
119
120	function Set-Reg {
121	    param(
122	        [string]$Path,
123	        [string]$Name,
124	        $Value,
125	        [string]$Type = "DWord"
126	    )
127	    try {
128	        if (-not (Test-Path $Path)) {
129	            New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
130	        }
131	        Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
132	        Write-Log "  SET $Path\$Name = $Value" -Level OK
133	    }
134	    catch {
135	        # Retry 1: grant write access via ACL manipulation
136	        try {
137	            Grant-RegWriteAccess -Path $Path
138	            if (-not (Test-Path $Path)) {
139	                New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
140	            }
141	            Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
142	            Write-Log "  SET $Path\$Name = $Value (after ACL fix)" -Level OK
143	            return
144	        }
145	        catch { }
146
147	        # Retry 2: write via scheduled task running as SYSTEM
148	        # SYSTEM has full registry access regardless of key ACL
149	        try {
150	            $regType = switch ($Type) {
151	                "DWord"       { "REG_DWORD" }
152	                "String"      { "REG_SZ" }
153	                "ExpandString"{ "REG_EXPAND_SZ" }
154	                "MultiString" { "REG_MULTI_SZ" }
155	                "QWord"       { "REG_QWORD" }
156	                default       { "REG_DWORD" }
157	            }
158	            # Convert registry PS path to reg.exe path
159	            $regPath = $Path -replace '^HKLM:\\', 'HKLM\' `
160	                             -replace '^HKCU:\\', 'HKCU\' `
161	                             -replace '^HKCR:\\', 'HKCR\'
162	            $tempScript = "$env:TEMP\set-reg-system-$([System.IO.Path]::GetRandomFileName()).ps1"
163	            "reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f" |
164	                Set-Content -Path $tempScript -Encoding UTF8
165
166	            $taskName  = "TempRegFix-$([System.IO.Path]::GetRandomFileName())"
167	            $action    = New-ScheduledTaskAction -Execute "cmd.exe" `
168	                            -Argument "/c reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f"
169	            $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
170	            $settings  = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 30)
171	            $task      = New-ScheduledTask -Action $action -Principal $principal -Settings $settings
172
173	            Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
174	            Start-ScheduledTask -TaskName $taskName
175	            Start-Sleep -Seconds 2
176	            Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
177	            Remove-Item $tempScript -Force -ErrorAction SilentlyContinue
178
179	            # Verify it was written
180	            $written = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name
181	            if ($null -ne $written) {
182	                Write-Log "  SET $Path\$Name = $Value (via SYSTEM task)" -Level OK
183	            } else {
184	                Write-Log "  FAILED $Path\$Name - SYSTEM task ran but value not found" -Level ERROR
185	            }
186	        }
187	        catch {
188	            Write-Log "  FAILED $Path\$Name - $_" -Level ERROR
189	        }
190	    }
191	}
192
193	function Remove-Reg {
194	    param([string]$Path, [string]$Name)
195	    try {
196	        if (Test-Path $Path) {
197	            Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue
198	            Write-Log "  REMOVED $Path\$Name" -Level OK
199	        }
200	    }
201	    catch {
202	        Write-Log "  FAILED removing $Path\$Name - $_" -Level ERROR
203	    }
204	}
205
206	Write-Log "3 - Applying HKLM system registry tweaks" -Level STEP
207
208	# -----------------------------------------------------------------------
209	# Bypass Network Requirement on OOBE (BypassNRO)
210	# -----------------------------------------------------------------------
211	Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" `
212	        -Name "BypassNRO" -Value 1
213
214	# -----------------------------------------------------------------------
215	# Disable auto-install of Teams (Chat)
216	# -----------------------------------------------------------------------
217	Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" `
218	        -Name "ConfigureChatAutoInstall" -Value 0
219
220	# -----------------------------------------------------------------------
221	# Disable Cloud Optimized Content (ads in Start menu etc.)
222	# -----------------------------------------------------------------------
223	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
224	        -Name "DisableCloudOptimizedContent" -Value 1
225
226	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
227	        -Name "DisableWindowsConsumerFeatures" -Value 1
228
229	# -----------------------------------------------------------------------
230	# Disable Widgets (News and Interests)
231	# -----------------------------------------------------------------------
232	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" `
233	        -Name "AllowNewsAndInterests" -Value 0
234
235	# -----------------------------------------------------------------------
236	# Microsoft Edge policies
237	# -----------------------------------------------------------------------
238	$edgePath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
239
240	# UI / first run
241	Set-Reg -Path $edgePath -Name "HideFirstRunExperience"    -Value 1
242	Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0
243
244	# New tab page / recommendations
245	Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled"                       -Value 0
246	Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled"                     -Value 0
247	Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled"  -Value 0
248	Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled"                -Value 0
249
250	# Shopping / rewards / sidebar
251	Set-Reg -Path $edgePath -Name "EdgeShoppingAssistantEnabled" -Value 0
252	Set-Reg -Path $edgePath -Name "ShowMicrosoftRewards"         -Value 0
253	Set-Reg -Path $edgePath -Name "HubsSidebarEnabled"           -Value 0
254
255	# Search suggestions
256	Set-Reg -Path $edgePath -Name "SearchSuggestEnabled"  -Value 0
257	Set-Reg -Path $edgePath -Name "ImportOnEachLaunch"    -Value 0
258
259	# Telemetry / feedback
260	Set-Reg -Path $edgePath -Name "DiagnosticData"                  -Value 0
261	Set-Reg -Path $edgePath -Name "FeedbackSurveysEnabled"          -Value 0
262	Set-Reg -Path $edgePath -Name "EdgeCollectionsEnabled"          -Value 0
263
264	# Toolbar buttons - show
265	Set-Reg -Path $edgePath -Name "FavoritesBarEnabled"        -Value 1  # Favorites bar always visible
266	Set-Reg -Path $edgePath -Name "DownloadsButtonEnabled"     -Value 1
267	Set-Reg -Path $edgePath -Name "HistoryButtonEnabled"       -Value 1
268	Set-Reg -Path $edgePath -Name "PerformanceButtonEnabled"   -Value 1  # Sleeping Tabs / Performance
269
270	# Toolbar buttons - hide
271	Set-Reg -Path $edgePath -Name "HomeButtonEnabled"          -Value 0
272	Set-Reg -Path $edgePath -Name "SplitScreenEnabled"         -Value 0
273	Set-Reg -Path $edgePath -Name "EdgeEDropEnabled"           -Value 0  # Drop
274	Set-Reg -Path $edgePath -Name "WebCaptureEnabled"          -Value 0  # Screenshot
275	Set-Reg -Path $edgePath -Name "ShareAllowed"               -Value 0  # Share
276
277	# Default search engine: Google
278	# SearchProviderEnabled must be 1, SearchProviderName + URL set the provider
279	Set-Reg -Path $edgePath -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
280	Set-Reg -Path $edgePath -Name "DefaultSearchProviderName"    -Value "Google" -Type "String"
281	Set-Reg -Path $edgePath -Name "DefaultSearchProviderSearchURL" `
282	        -Value "https://www.google.com/search?q={searchTerms}" -Type "String"
283	# Remove other search engines (empty list = no other providers besides default)
284	Set-Reg -Path $edgePath -Name "ManagedSearchEngines" `
285	        -Value '[{"is_default":true,"name":"Google","search_url":"https://www.google.com/search?q={searchTerms}","keyword":"google.com"}]' `
286	        -Type "String"
287
288	# Disable desktop shortcut on install/update
289	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
290	        -Name "CreateDesktopShortcutDefault" -Value 0
291
292	# -----------------------------------------------------------------------
293	# Password - no expiration
294	# -----------------------------------------------------------------------
295	Write-Log "  Setting password max age to UNLIMITED" -Level INFO
296	$pwResult = & net accounts /maxpwage:UNLIMITED 2>&1
297	if ($LASTEXITCODE -eq 0) {
298	    Write-Log "  Password max age set to UNLIMITED" -Level OK
299	} else {
300	    Write-Log "  Failed to set password max age: $pwResult" -Level ERROR
301	}
302
303	# -----------------------------------------------------------------------
304	# Time zone
305	# -----------------------------------------------------------------------
306	$tz = "Central Europe Standard Time"
307	if ($Config -and $Config.deployment -and $Config.deployment.timezone) {
308	    $tz = $Config.deployment.timezone
309	}
310	Write-Log "  Setting time zone: $tz" -Level INFO
311	try {
312	    Set-TimeZone -Id $tz -ErrorAction Stop
313	    Write-Log "  Time zone set: $tz" -Level OK
314	}
315	catch {
316	    Write-Log "  Failed to set time zone: $_" -Level ERROR
317	}
318
319	# -----------------------------------------------------------------------
320	# OneDrive - uninstall from clean Windows (no policy block)
321	# NOTE: No policy key is set intentionally - M365 installation can reinstall
322	#       and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
323	# -----------------------------------------------------------------------
324	Write-Log "  Uninstalling OneDrive" -Level INFO
325
326	# Remove OneDriveSetup.exe if present
327	$oneDrivePaths = @(
328	    "$env:SystemRoot\System32\OneDriveSetup.exe"
329	    "$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
330	)
331	foreach ($odPath in $oneDrivePaths) {
332	    if (Test-Path $odPath) {
333	        try {
334	            # Uninstall first
335	            & $odPath /uninstall 2>&1 | Out-Null
336	            Write-Log "  OneDrive uninstalled via $odPath" -Level OK
337	        }
338	        catch {
339	            Write-Log "  OneDrive uninstall failed: $_" -Level WARN
340	        }
341	    }
342	}
343
344	# Remove OneDrive Start Menu shortcut
345	$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
346	if (Test-Path $odLnk) {
347	    Remove-Item $odLnk -Force -ErrorAction SilentlyContinue
348	    Write-Log "  Removed OneDrive Start Menu shortcut" -Level OK
349	}
350
351	# -----------------------------------------------------------------------
352	# Outlook (new) - disable auto-install via UScheduler
353	# -----------------------------------------------------------------------
354	Write-Log "  Disabling Outlook (new) auto-install" -Level INFO
355
356	$uschedulerPaths = @(
357	    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate"
358	    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate"
359	)
360	foreach ($uPath in $uschedulerPaths) {
361	    if (Test-Path $uPath) {
362	        try {
363	            Remove-Item -Path $uPath -Recurse -Force
364	            Write-Log "  Removed UScheduler key: $uPath" -Level OK
365	        }
366	        catch {
367	            Write-Log "  Failed to remove UScheduler key: $_" -Level WARN
368	        }
369	    }
370	}
371
372	# -----------------------------------------------------------------------
373	# Disable GameDVR
374	# -----------------------------------------------------------------------
375	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
376	        -Name "AllowGameDVR" -Value 0
377
378	# -----------------------------------------------------------------------
379	# Disable Recall (Windows AI feature)
380	# -----------------------------------------------------------------------
381	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
382	        -Name "DisableAIDataAnalysis" -Value 1
383
384	# -----------------------------------------------------------------------
385	# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
386	# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
387	# this policy key ensures the setting survives Windows Updates.
388	# -----------------------------------------------------------------------
389	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
390	        -Name "SearchOnTaskbarMode" -Value 0
391
392	# -----------------------------------------------------------------------
393	# Start menu - hide Recommended section (Win11)
394	# -----------------------------------------------------------------------
395	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
396	        -Name "HideRecommendedSection" -Value 1
397
398	# -----------------------------------------------------------------------
399	# Power configuration
400	# -----------------------------------------------------------------------
401	Write-Log "Applying power configuration" -Level INFO
402
403	$powercfg = @(
404	    @("/change", "standby-timeout-ac",  "0"),    # never sleep on AC
405	    @("/change", "monitor-timeout-ac",  "60"),   # screen off after 60 min on AC
406	    @("/change", "standby-timeout-dc",  "30"),   # sleep after 30 min on battery
407	    @("/change", "monitor-timeout-dc",  "15")    # screen off after 15 min on battery
408	)
409	foreach ($args in $powercfg) {
410	    $result = & powercfg @args 2>&1
411	    if ($LASTEXITCODE -eq 0) {
412	        Write-Log "  powercfg $($args -join ' ')" -Level OK
413	    } else {
414	        Write-Log "  powercfg $($args -join ' ') failed: $result" -Level WARN
415	    }
416	}
417
418	# -----------------------------------------------------------------------
419	# Proxy auto-detect disable (WPAD)
420	# -----------------------------------------------------------------------
421	Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" `
422	        -Name "AutoDetect" -Value 0
423
424	Write-Log "Step 3 complete" -Level OK
425

<#
.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.
    edge-skryt-first-run-experience: HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0. Suppresses Edge welcome wizard and default browser prompts on first launch.
    edge-policies-panel-oblibeny-vyhledavac: FavoritesBarEnabled=1 (always show), DefaultSearchProviderEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines removes other providers.
    edge-policies-tlacitka-zobrazit: DownloadsButtonEnabled=1, HistoryButtonEnabled=1.
    edge-policies-tlacitka-skryt: HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0 (Drop), WebCaptureEnabled=0 (Screenshot), ShareAllowed=0.
    edge-policies-obsah-a-telemetrie: NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, SpotlightExperiencesAndRecommendationsEnabled=0, PersonalizationReportingEnabled=0, EdgeShoppingAssistantEnabled=0, ShowMicrosoftRewards=0, HubsSidebarEnabled=0, SearchSuggestEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0.
    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(
    [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
}

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

# -----------------------------------------------------------------------
# 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 policies
# -----------------------------------------------------------------------
$edgePath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"

# UI / first run
Set-Reg -Path $edgePath -Name "HideFirstRunExperience"    -Value 1
Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0

# New tab page / recommendations
Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled"                       -Value 0
Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled"                     -Value 0
Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled"  -Value 0
Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled"                -Value 0

# Shopping / rewards / sidebar
Set-Reg -Path $edgePath -Name "EdgeShoppingAssistantEnabled" -Value 0
Set-Reg -Path $edgePath -Name "ShowMicrosoftRewards"         -Value 0
Set-Reg -Path $edgePath -Name "HubsSidebarEnabled"           -Value 0

# Search suggestions
Set-Reg -Path $edgePath -Name "SearchSuggestEnabled"  -Value 0
Set-Reg -Path $edgePath -Name "ImportOnEachLaunch"    -Value 0

# Telemetry / feedback
Set-Reg -Path $edgePath -Name "DiagnosticData"                  -Value 0
Set-Reg -Path $edgePath -Name "FeedbackSurveysEnabled"          -Value 0
Set-Reg -Path $edgePath -Name "EdgeCollectionsEnabled"          -Value 0

# Toolbar buttons - show
Set-Reg -Path $edgePath -Name "FavoritesBarEnabled"        -Value 1  # Favorites bar always visible
Set-Reg -Path $edgePath -Name "DownloadsButtonEnabled"     -Value 1
Set-Reg -Path $edgePath -Name "HistoryButtonEnabled"       -Value 1
Set-Reg -Path $edgePath -Name "PerformanceButtonEnabled"   -Value 1  # Sleeping Tabs / Performance

# Toolbar buttons - hide
Set-Reg -Path $edgePath -Name "HomeButtonEnabled"          -Value 0
Set-Reg -Path $edgePath -Name "SplitScreenEnabled"         -Value 0
Set-Reg -Path $edgePath -Name "EdgeEDropEnabled"           -Value 0  # Drop
Set-Reg -Path $edgePath -Name "WebCaptureEnabled"          -Value 0  # Screenshot
Set-Reg -Path $edgePath -Name "ShareAllowed"               -Value 0  # Share

# Default search engine: Google
# SearchProviderEnabled must be 1, SearchProviderName + URL set the provider
Set-Reg -Path $edgePath -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
Set-Reg -Path $edgePath -Name "DefaultSearchProviderName"    -Value "Google" -Type "String"
Set-Reg -Path $edgePath -Name "DefaultSearchProviderSearchURL" `
        -Value "https://www.google.com/search?q={searchTerms}" -Type "String"
# Remove other search engines (empty list = no other providers besides default)
Set-Reg -Path $edgePath -Name "ManagedSearchEngines" `
        -Value '[{"is_default":true,"name":"Google","search_url":"https://www.google.com/search?q={searchTerms}","keyword":"google.com"}]' `
        -Type "String"

# Disable desktop shortcut on install/update
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 - 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.
# -----------------------------------------------------------------------
Write-Log "  Uninstalling OneDrive" -Level INFO

# 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

# -----------------------------------------------------------------------
# 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

# -----------------------------------------------------------------------
# Power configuration
# -----------------------------------------------------------------------
Write-Log "Applying power configuration" -Level INFO

$powercfg = @(
    @("/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 ($args in $powercfg) {
    $result = & powercfg @args 2>&1
    if ($LASTEXITCODE -eq 0) {
        Write-Log "  powercfg $($args -join ' ')" -Level OK
    } else {
        Write-Log "  powercfg $($args -join ' ') failed: $result" -Level WARN
    }
}

# -----------------------------------------------------------------------
# Proxy auto-detect disable (WPAD)
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" `
        -Name "AutoDetect" -Value 0

Write-Log "Step 3 complete" -Level OK


Teď `03-system-registry.ps1` — přidám `Get-Feature` helper a obalím všech 5 bloků. Soubor přepíšu kompletně pro čistou strukturu:


Write

<#
.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.
    edge-skryt-first-run-experience: HideFirstRunExperience=1 + DefaultBrowserSettingEnabled=0. Suppresses Edge welcome wizard and default browser prompts on first launch.
    edge-policies-panel-oblibeny-vyhledavac: FavoritesBarEnabled=1 (always show), DefaultSearchProviderEnabled=1, DefaultSearchProviderName=Google, ManagedSearchEngines removes other providers.
    edge-policies-tlacitka-zobrazit: DownloadsButtonEnabled=1, HistoryButtonEnabled=1.
    edge-policies-tlacitka-skryt: HomeButtonEnabled=0, SplitScreenEnabled=0, EdgeEDropEnabled=0 (Drop), WebCaptureEnabled=0 (Screenshot), ShareAllowed=0.
    edge-policies-obsah-a-telemetrie: NewTabPageContentEnabled=0, ShowRecommendationsEnabled=0, SpotlightExperiencesAndRecommendationsEnabled=0, PersonalizationReportingEnabled=0, EdgeShoppingAssistantEnabled=0, ShowMicrosoftRewards=0, HubsSidebarEnabled=0, SearchSuggestEnabled=0, DiagnosticData=0, FeedbackSurveysEnabled=0, EdgeCollectionsEnabled=0.
    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(
    [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 Get-Feature {
    param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
    try {
        if ($null -eq $Cfg) { return $Default }
        $stepFeatures = $Cfg.features.$StepID
        if ($null -eq $stepFeatures) { return $Default }
        $val = $stepFeatures.$FeatureID
        if ($null -eq $val) { return $Default }
        return [bool]$val
    } catch { return $Default }
}

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
} else {
    Write-Log "systemTweaks feature disabled - skipping" -Level INFO
}

# -----------------------------------------------------------------------
# Microsoft Edge policies
# -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "edgePolicies") {
    Write-Log "  Applying Edge policies" -Level INFO
    $edgePath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"

    # UI / first run
    Set-Reg -Path $edgePath -Name "HideFirstRunExperience"       -Value 1
    Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0

    # New tab page / recommendations
    Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled"                       -Value 0
    Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled"                     -Value 0
    Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled"  -Value 0
    Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled"                -Value 0

    # Shopping / rewards / sidebar
    Set-Reg -Path $edgePath -Name "EdgeShoppingAssistantEnabled" -Value 0
    Set-Reg -Path $edgePath -Name "ShowMicrosoftRewards"         -Value 0
    Set-Reg -Path $edgePath -Name "HubsSidebarEnabled"           -Value 0

    # Search suggestions
    Set-Reg -Path $edgePath -Name "SearchSuggestEnabled" -Value 0
    Set-Reg -Path $edgePath -Name "ImportOnEachLaunch"   -Value 0

    # Telemetry / feedback
    Set-Reg -Path $edgePath -Name "DiagnosticData"         -Value 0
    Set-Reg -Path $edgePath -Name "FeedbackSurveysEnabled" -Value 0
    Set-Reg -Path $edgePath -Name "EdgeCollectionsEnabled" -Value 0

    # Toolbar buttons - show
    Set-Reg -Path $edgePath -Name "FavoritesBarEnabled"      -Value 1  # Favorites bar always visible
    Set-Reg -Path $edgePath -Name "DownloadsButtonEnabled"   -Value 1
    Set-Reg -Path $edgePath -Name "HistoryButtonEnabled"     -Value 1
    Set-Reg -Path $edgePath -Name "PerformanceButtonEnabled" -Value 1  # Sleeping Tabs / Performance

    # Toolbar buttons - hide
    Set-Reg -Path $edgePath -Name "HomeButtonEnabled"  -Value 0
    Set-Reg -Path $edgePath -Name "SplitScreenEnabled" -Value 0
    Set-Reg -Path $edgePath -Name "EdgeEDropEnabled"   -Value 0  # Drop
    Set-Reg -Path $edgePath -Name "WebCaptureEnabled"  -Value 0  # Screenshot
    Set-Reg -Path $edgePath -Name "ShareAllowed"       -Value 0  # Share

    # Default search engine: Google
    # SearchProviderEnabled must be 1, SearchProviderName + URL set the provider
    Set-Reg -Path $edgePath -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
    Set-Reg -Path $edgePath -Name "DefaultSearchProviderName"    -Value "Google" -Type "String"
    Set-Reg -Path $edgePath -Name "DefaultSearchProviderSearchURL" `
            -Value "https://www.google.com/search?q={searchTerms}" -Type "String"
    # Remove other search engines (empty list = no other providers besides default)
    Set-Reg -Path $edgePath -Name "ManagedSearchEngines" `
            -Value '[{"is_default":true,"name":"Google","search_url":"https://www.google.com/search?q={searchTerms}","keyword":"google.com"}]' `
            -Type "String"

    # Disable desktop shortcut on install/update
    Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
            -Name "CreateDesktopShortcutDefault" -Value 0
} 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

    $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