<#
.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
        $tempScript = $null
        $taskName   = $null
        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

            # 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
        }
        finally {
            if ($taskName) {
                Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
            }
            if ($tempScript) {
                Remove-Item $tempScript -Force -ErrorAction SilentlyContinue
            }
        }
    }
}

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
