diff --git a/Deploy-Windows.ps1 b/Deploy-Windows.ps1 index f77f65e..7b53b19 100644 --- a/Deploy-Windows.ps1 +++ b/Deploy-Windows.ps1 @@ -96,83 +96,117 @@ Invoke-Step -Name "Load config.json" -Action { Write-Log "Config loaded from $ConfigFile" -Level INFO } +# ----------------------------------------------------------------------- +# Build step enable/disable map from config + CLI overrides +# ----------------------------------------------------------------------- +$stepsEnabled = @{ + adminAccount = $true + bloatware = $true + software = $true + systemRegistry = $true + defaultProfile = $true + personalization = $true + scheduledTasks = $true + desktopInfo = $true + activation = $true +} +if ($Config -and $Config.steps) { + foreach ($key in @($stepsEnabled.Keys)) { + $val = $Config.steps.$key + if ($null -ne $val) { $stepsEnabled[$key] = [bool]$val } + } +} +# CLI switches override config.steps +if ($SkipBloatware) { $stepsEnabled['bloatware'] = $false } +if ($SkipSoftware) { $stepsEnabled['software'] = $false } +if ($SkipDefaultProfile) { $stepsEnabled['defaultProfile'] = $false } + +function Skip-Step { + param([string]$Name) + Write-Log "$Name - SKIPPED (disabled in config)" -Level WARN + $StepResults.Add(@{ Name = $Name; Status = "SKIPPED" }) +} + # ----------------------------------------------------------------------- # Step 0a - Admin account # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 0a - Admin account" -Action { - & "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['adminAccount']) { + Invoke-Step -Name "Step 0a - Admin account" -Action { + & "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 0a - Admin account" } # ----------------------------------------------------------------------- # Step 0b - Windows activation # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 0b - Windows activation" -Action { - & "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['activation']) { + Invoke-Step -Name "Step 0b - Windows activation" -Action { + & "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 0b - Windows activation" } # ----------------------------------------------------------------------- # Step 1 - Bloatware removal # ----------------------------------------------------------------------- -if ($SkipBloatware) { - Write-Log "Step 1 - Bloatware removal: SKIPPED (-SkipBloatware)" -Level WARN - $StepResults.Add(@{ Name = "Step 1 - Bloatware removal"; Status = "SKIPPED" }) -} else { +if ($stepsEnabled['bloatware']) { Invoke-Step -Name "Step 1 - Bloatware removal" -Action { & "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile } -} +} else { Skip-Step "Step 1 - Bloatware removal" } # ----------------------------------------------------------------------- # Step 2 - Software installation # ----------------------------------------------------------------------- -if ($SkipSoftware) { - Write-Log "Step 2 - Software installation: SKIPPED (-SkipSoftware)" -Level WARN - $StepResults.Add(@{ Name = "Step 2 - Software installation"; Status = "SKIPPED" }) -} else { +if ($stepsEnabled['software']) { Invoke-Step -Name "Step 2 - Software installation" -Action { & "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile } -} +} else { Skip-Step "Step 2 - Software installation" } # ----------------------------------------------------------------------- # Step 3 - System registry (HKLM) # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 3 - System registry" -Action { - & "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['systemRegistry']) { + Invoke-Step -Name "Step 3 - System registry" -Action { + & "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 3 - System registry" } # ----------------------------------------------------------------------- # Step 4 - Default profile (NTUSER.DAT) # ----------------------------------------------------------------------- -if ($SkipDefaultProfile) { - Write-Log "Step 4 - Default profile: SKIPPED (-SkipDefaultProfile)" -Level WARN - $StepResults.Add(@{ Name = "Step 4 - Default profile"; Status = "SKIPPED" }) -} else { +if ($stepsEnabled['defaultProfile']) { Invoke-Step -Name "Step 4 - Default profile" -Action { & "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile } -} +} else { Skip-Step "Step 4 - Default profile" } # ----------------------------------------------------------------------- # Step 5 - Personalization # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 5 - Personalization" -Action { - & "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['personalization']) { + Invoke-Step -Name "Step 5 - Personalization" -Action { + & "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 5 - Personalization" } # ----------------------------------------------------------------------- # Step 6 - Scheduled tasks # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 6 - Scheduled tasks" -Action { - & "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['scheduledTasks']) { + Invoke-Step -Name "Step 6 - Scheduled tasks" -Action { + & "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 6 - Scheduled tasks" } # ----------------------------------------------------------------------- # Step 7 - DesktopInfo # ----------------------------------------------------------------------- -Invoke-Step -Name "Step 7 - DesktopInfo" -Action { - & "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile -} +if ($stepsEnabled['desktopInfo']) { + Invoke-Step -Name "Step 7 - DesktopInfo" -Action { + & "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 7 - DesktopInfo" } # ----------------------------------------------------------------------- # Summary diff --git a/Run.cmd b/Run.cmd new file mode 100644 index 0000000..f7efc56 --- /dev/null +++ b/Run.cmd @@ -0,0 +1,125 @@ +@echo off +chcp 65001 >nul +setlocal EnableDelayedExpansion + +:: ----------------------------------------------------------------------- +:: Auto-elevate to Administrator if not already elevated +:: ----------------------------------------------------------------------- +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo Requesting administrator privileges... + powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs" + exit /b +) + +:: ----------------------------------------------------------------------- +:: Paths +:: ----------------------------------------------------------------------- +set "SCRIPT_DIR=%~dp0" +set "DEPLOY_PS1=%SCRIPT_DIR%Deploy-Windows.ps1" +set "CONFIG_JSON=%SCRIPT_DIR%config\config.json" +set "CONFIG_EDITOR=%SCRIPT_DIR%config-editor.hta" +set "LOG_FILE=C:\Windows\Setup\Scripts\Deploy.log" + +:MENU +cls +echo. +echo ================================================ +echo X9 - Windows Deployment +echo ================================================ +echo. +echo Config : %CONFIG_JSON% +echo Log : %LOG_FILE% +echo. +echo [1] Full deployment (uses config.json) +echo [2] Dry run (no changes, log only) +echo [3] Skip bloatware removal +echo [4] Skip software install +echo [5] Open config editor (config-editor.hta) +echo [0] Exit +echo. +set /p CHOICE=" Select [0-5]: " + +if "%CHOICE%"=="0" goto EXIT +if "%CHOICE%"=="1" goto FULL +if "%CHOICE%"=="2" goto DRYRUN +if "%CHOICE%"=="3" goto SKIP_BLOATWARE +if "%CHOICE%"=="4" goto SKIP_SOFTWARE +if "%CHOICE%"=="5" goto OPEN_EDITOR + +echo Invalid choice. Try again. +timeout /t 2 >nul +goto MENU + +:: ----------------------------------------------------------------------- +:: [1] Full deployment +:: ----------------------------------------------------------------------- +:FULL +cls +echo. +echo Starting full deployment... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" +goto DONE + +:: ----------------------------------------------------------------------- +:: [2] Dry run +:: ----------------------------------------------------------------------- +:DRYRUN +cls +echo. +echo Starting dry run (no changes will be made)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -DryRun +goto DONE + +:: ----------------------------------------------------------------------- +:: [3] Skip bloatware +:: ----------------------------------------------------------------------- +:SKIP_BLOATWARE +cls +echo. +echo Starting deployment (bloatware removal skipped)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipBloatware +goto DONE + +:: ----------------------------------------------------------------------- +:: [4] Skip software +:: ----------------------------------------------------------------------- +:SKIP_SOFTWARE +cls +echo. +echo Starting deployment (software install skipped)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipSoftware +goto DONE + +:: ----------------------------------------------------------------------- +:: [5] Config editor +:: ----------------------------------------------------------------------- +:OPEN_EDITOR +if not exist "%CONFIG_EDITOR%" ( + echo ERROR: config-editor.hta not found: %CONFIG_EDITOR% + pause + goto MENU +) +start "" mshta.exe "%CONFIG_EDITOR%" +goto MENU + +:: ----------------------------------------------------------------------- +:: Done +:: ----------------------------------------------------------------------- +:DONE +echo. +echo ================================================ +echo Deployment finished. +echo Log: %LOG_FILE% +echo ================================================ +echo. +pause +goto MENU + +:EXIT +endlocal +exit /b 0 diff --git a/config-editor.hta b/config-editor.hta new file mode 100644 index 0000000..168b558 --- /dev/null +++ b/config-editor.hta @@ -0,0 +1,632 @@ + + +X9 - Deployment Config Editor + + + + + + + + +
+
Steps
+
Software
+
Settings
+
+ +
+ + +
+ + + + + + + + + + + +
OnStepName
+
+ + +
+ + + + + + + + + + +
Package NameWinget ID
+ +
+ + +
+
Deployment
+
+
Timezone
+
+ +
Locale
+
+
+ +
Admin Account
+
+
Username
+
+ +
Password
+
+ +
Description
+
+
+ +
PDF Default
+
+
Force Adobe Reader
+
+ +
Scheduled Task
+
+
+ +
Desktop Info
+
+
Enabled
+
+ +
Position
+
+ +
+ +
Font Size
+
+ +
Font Color
+
+
+ +
Activation
+
+
Product Key
+
+ +
KMS Server
+
+
+
+ +
+ + + +
+ + + + diff --git a/config/config.json b/config/config.json index 39b600b..138c78b 100644 --- a/config/config.json +++ b/config/config.json @@ -1,4 +1,15 @@ { + "steps": { + "adminAccount": true, + "bloatware": true, + "software": true, + "systemRegistry": true, + "defaultProfile": true, + "personalization": true, + "scheduledTasks": true, + "desktopInfo": true, + "activation": true + }, "deployment": { "timezone": "Central Europe Standard Time", "locale": "cs-CZ" diff --git a/flash/Deploy-Windows.ps1 b/flash/Deploy-Windows.ps1 new file mode 100644 index 0000000..7b53b19 --- /dev/null +++ b/flash/Deploy-Windows.ps1 @@ -0,0 +1,244 @@ +#Requires -RunAsAdministrator + +[CmdletBinding()] +param( + [switch]$SkipBloatware, + [switch]$SkipSoftware, + [switch]$SkipDefaultProfile, + [switch]$DryRun +) + +$ErrorActionPreference = "Continue" + +# ----------------------------------------------------------------------- +# Paths +# ----------------------------------------------------------------------- +$ScriptRoot = $PSScriptRoot +$LogDir = "C:\Windows\Setup\Scripts" +$LogFile = "$LogDir\Deploy.log" +$ConfigFile = "$ScriptRoot\config\config.json" + +# ----------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------- +function Write-Log { + param( + [string]$Message, + [ValidateSet("INFO","OK","ERROR","WARN","STEP")] + [string]$Level = "INFO" + ) + $timestamp = Get-Date -Format "HH:mm:ss" + $line = "[$timestamp] [$Level] $Message" + Add-Content -Path $LogFile -Value $line -Encoding UTF8 + switch ($Level) { + "OK" { Write-Host $line -ForegroundColor Green } + "ERROR" { Write-Host $line -ForegroundColor Red } + "WARN" { Write-Host $line -ForegroundColor Yellow } + "STEP" { Write-Host $line -ForegroundColor Cyan } + default { Write-Host $line } + } +} + +# ----------------------------------------------------------------------- +# Step runner - catches errors, logs, always continues +# ----------------------------------------------------------------------- +$StepResults = [System.Collections.Generic.List[hashtable]]::new() + +function Invoke-Step { + param( + [string]$Name, + [scriptblock]$Action + ) + + Write-Log "---- $Name ----" -Level STEP + + if ($DryRun) { + Write-Log "DryRun - skipping execution" -Level WARN + $StepResults.Add(@{ Name = $Name; Status = "DRYRUN" }) + return + } + + try { + & $Action + Write-Log "$Name - OK" -Level OK + $StepResults.Add(@{ Name = $Name; Status = "OK" }) + } + catch { + Write-Log "$Name - ERROR: $_" -Level ERROR + $StepResults.Add(@{ Name = $Name; Status = "ERROR" }) + } +} + +# ----------------------------------------------------------------------- +# Init +# ----------------------------------------------------------------------- +if (-not (Test-Path $LogDir)) { + New-Item -ItemType Directory -Path $LogDir -Force | Out-Null +} + +Write-Log "========================================" -Level INFO +Write-Log "Deploy-Windows.ps1 started" -Level INFO +Write-Log "Computer: $env:COMPUTERNAME" -Level INFO +Write-Log "User: $env:USERNAME" -Level INFO +Write-Log "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level INFO +if ($DryRun) { Write-Log "Mode: DRY RUN" -Level WARN } +Write-Log "========================================" -Level INFO + +# ----------------------------------------------------------------------- +# Load config +# ----------------------------------------------------------------------- +$Config = $null +Invoke-Step -Name "Load config.json" -Action { + if (-not (Test-Path $ConfigFile)) { + throw "config.json not found: $ConfigFile" + } + $script:Config = Get-Content $ConfigFile -Raw -Encoding UTF8 | ConvertFrom-Json + Write-Log "Config loaded from $ConfigFile" -Level INFO +} + +# ----------------------------------------------------------------------- +# Build step enable/disable map from config + CLI overrides +# ----------------------------------------------------------------------- +$stepsEnabled = @{ + adminAccount = $true + bloatware = $true + software = $true + systemRegistry = $true + defaultProfile = $true + personalization = $true + scheduledTasks = $true + desktopInfo = $true + activation = $true +} +if ($Config -and $Config.steps) { + foreach ($key in @($stepsEnabled.Keys)) { + $val = $Config.steps.$key + if ($null -ne $val) { $stepsEnabled[$key] = [bool]$val } + } +} +# CLI switches override config.steps +if ($SkipBloatware) { $stepsEnabled['bloatware'] = $false } +if ($SkipSoftware) { $stepsEnabled['software'] = $false } +if ($SkipDefaultProfile) { $stepsEnabled['defaultProfile'] = $false } + +function Skip-Step { + param([string]$Name) + Write-Log "$Name - SKIPPED (disabled in config)" -Level WARN + $StepResults.Add(@{ Name = $Name; Status = "SKIPPED" }) +} + +# ----------------------------------------------------------------------- +# Step 0a - Admin account +# ----------------------------------------------------------------------- +if ($stepsEnabled['adminAccount']) { + Invoke-Step -Name "Step 0a - Admin account" -Action { + & "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 0a - Admin account" } + +# ----------------------------------------------------------------------- +# Step 0b - Windows activation +# ----------------------------------------------------------------------- +if ($stepsEnabled['activation']) { + Invoke-Step -Name "Step 0b - Windows activation" -Action { + & "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 0b - Windows activation" } + +# ----------------------------------------------------------------------- +# Step 1 - Bloatware removal +# ----------------------------------------------------------------------- +if ($stepsEnabled['bloatware']) { + Invoke-Step -Name "Step 1 - Bloatware removal" -Action { + & "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 1 - Bloatware removal" } + +# ----------------------------------------------------------------------- +# Step 2 - Software installation +# ----------------------------------------------------------------------- +if ($stepsEnabled['software']) { + Invoke-Step -Name "Step 2 - Software installation" -Action { + & "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 2 - Software installation" } + +# ----------------------------------------------------------------------- +# Step 3 - System registry (HKLM) +# ----------------------------------------------------------------------- +if ($stepsEnabled['systemRegistry']) { + Invoke-Step -Name "Step 3 - System registry" -Action { + & "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 3 - System registry" } + +# ----------------------------------------------------------------------- +# Step 4 - Default profile (NTUSER.DAT) +# ----------------------------------------------------------------------- +if ($stepsEnabled['defaultProfile']) { + Invoke-Step -Name "Step 4 - Default profile" -Action { + & "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 4 - Default profile" } + +# ----------------------------------------------------------------------- +# Step 5 - Personalization +# ----------------------------------------------------------------------- +if ($stepsEnabled['personalization']) { + Invoke-Step -Name "Step 5 - Personalization" -Action { + & "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 5 - Personalization" } + +# ----------------------------------------------------------------------- +# Step 6 - Scheduled tasks +# ----------------------------------------------------------------------- +if ($stepsEnabled['scheduledTasks']) { + Invoke-Step -Name "Step 6 - Scheduled tasks" -Action { + & "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 6 - Scheduled tasks" } + +# ----------------------------------------------------------------------- +# Step 7 - DesktopInfo +# ----------------------------------------------------------------------- +if ($stepsEnabled['desktopInfo']) { + Invoke-Step -Name "Step 7 - DesktopInfo" -Action { + & "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile + } +} else { Skip-Step "Step 7 - DesktopInfo" } + +# ----------------------------------------------------------------------- +# Summary +# ----------------------------------------------------------------------- +Write-Log "========================================" -Level INFO +Write-Log "SUMMARY" -Level INFO +Write-Log "========================================" -Level INFO + +$countOK = ($StepResults | Where-Object { $_.Status -eq "OK" }).Count +$countError = ($StepResults | Where-Object { $_.Status -eq "ERROR" }).Count +$countSkipped = ($StepResults | Where-Object { $_.Status -eq "SKIPPED" }).Count +$countDryRun = ($StepResults | Where-Object { $_.Status -eq "DRYRUN" }).Count + +foreach ($r in $StepResults) { + $lvl = switch ($r.Status) { + "OK" { "OK" } + "ERROR" { "ERROR" } + "SKIPPED" { "WARN" } + "DRYRUN" { "WARN" } + } + Write-Log "$($r.Status.PadRight(8)) $($r.Name)" -Level $lvl +} + +Write-Log "----------------------------------------" -Level INFO +Write-Log "OK: $countOK ERROR: $countError SKIPPED: $countSkipped DRYRUN: $countDryRun" -Level INFO +Write-Log "Log saved to: $LogFile" -Level INFO +Write-Log "========================================" -Level INFO + +if ($countError -gt 0) { + Write-Log "Deployment finished with errors. Review log: $LogFile" -Level ERROR + exit 1 +} else { + Write-Log "Deployment finished successfully." -Level OK + exit 0 +} diff --git a/flash/Run.cmd b/flash/Run.cmd new file mode 100644 index 0000000..f7efc56 --- /dev/null +++ b/flash/Run.cmd @@ -0,0 +1,125 @@ +@echo off +chcp 65001 >nul +setlocal EnableDelayedExpansion + +:: ----------------------------------------------------------------------- +:: Auto-elevate to Administrator if not already elevated +:: ----------------------------------------------------------------------- +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo Requesting administrator privileges... + powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs" + exit /b +) + +:: ----------------------------------------------------------------------- +:: Paths +:: ----------------------------------------------------------------------- +set "SCRIPT_DIR=%~dp0" +set "DEPLOY_PS1=%SCRIPT_DIR%Deploy-Windows.ps1" +set "CONFIG_JSON=%SCRIPT_DIR%config\config.json" +set "CONFIG_EDITOR=%SCRIPT_DIR%config-editor.hta" +set "LOG_FILE=C:\Windows\Setup\Scripts\Deploy.log" + +:MENU +cls +echo. +echo ================================================ +echo X9 - Windows Deployment +echo ================================================ +echo. +echo Config : %CONFIG_JSON% +echo Log : %LOG_FILE% +echo. +echo [1] Full deployment (uses config.json) +echo [2] Dry run (no changes, log only) +echo [3] Skip bloatware removal +echo [4] Skip software install +echo [5] Open config editor (config-editor.hta) +echo [0] Exit +echo. +set /p CHOICE=" Select [0-5]: " + +if "%CHOICE%"=="0" goto EXIT +if "%CHOICE%"=="1" goto FULL +if "%CHOICE%"=="2" goto DRYRUN +if "%CHOICE%"=="3" goto SKIP_BLOATWARE +if "%CHOICE%"=="4" goto SKIP_SOFTWARE +if "%CHOICE%"=="5" goto OPEN_EDITOR + +echo Invalid choice. Try again. +timeout /t 2 >nul +goto MENU + +:: ----------------------------------------------------------------------- +:: [1] Full deployment +:: ----------------------------------------------------------------------- +:FULL +cls +echo. +echo Starting full deployment... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" +goto DONE + +:: ----------------------------------------------------------------------- +:: [2] Dry run +:: ----------------------------------------------------------------------- +:DRYRUN +cls +echo. +echo Starting dry run (no changes will be made)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -DryRun +goto DONE + +:: ----------------------------------------------------------------------- +:: [3] Skip bloatware +:: ----------------------------------------------------------------------- +:SKIP_BLOATWARE +cls +echo. +echo Starting deployment (bloatware removal skipped)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipBloatware +goto DONE + +:: ----------------------------------------------------------------------- +:: [4] Skip software +:: ----------------------------------------------------------------------- +:SKIP_SOFTWARE +cls +echo. +echo Starting deployment (software install skipped)... +echo. +powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipSoftware +goto DONE + +:: ----------------------------------------------------------------------- +:: [5] Config editor +:: ----------------------------------------------------------------------- +:OPEN_EDITOR +if not exist "%CONFIG_EDITOR%" ( + echo ERROR: config-editor.hta not found: %CONFIG_EDITOR% + pause + goto MENU +) +start "" mshta.exe "%CONFIG_EDITOR%" +goto MENU + +:: ----------------------------------------------------------------------- +:: Done +:: ----------------------------------------------------------------------- +:DONE +echo. +echo ================================================ +echo Deployment finished. +echo Log: %LOG_FILE% +echo ================================================ +echo. +pause +goto MENU + +:EXIT +endlocal +exit /b 0 diff --git a/flash/config-editor.hta b/flash/config-editor.hta new file mode 100644 index 0000000..168b558 --- /dev/null +++ b/flash/config-editor.hta @@ -0,0 +1,632 @@ + + +X9 - Deployment Config Editor + + + + + + + + +
+
Steps
+
Software
+
Settings
+
+ +
+ + +
+ + + + + + + + + + + +
OnStepName
+
+ + +
+ + + + + + + + + + +
Package NameWinget ID
+ +
+ + +
+
Deployment
+
+
Timezone
+
+ +
Locale
+
+
+ +
Admin Account
+
+
Username
+
+ +
Password
+
+ +
Description
+
+
+ +
PDF Default
+
+
Force Adobe Reader
+
+ +
Scheduled Task
+
+
+ +
Desktop Info
+
+
Enabled
+
+ +
Position
+
+ +
+ +
Font Size
+
+ +
Font Color
+
+
+ +
Activation
+
+
Product Key
+
+ +
KMS Server
+
+
+
+ +
+ + + +
+ + + + diff --git a/flash/config/config.json b/flash/config/config.json new file mode 100644 index 0000000..138c78b --- /dev/null +++ b/flash/config/config.json @@ -0,0 +1,49 @@ +{ + "steps": { + "adminAccount": true, + "bloatware": true, + "software": true, + "systemRegistry": true, + "defaultProfile": true, + "personalization": true, + "scheduledTasks": true, + "desktopInfo": true, + "activation": true + }, + "deployment": { + "timezone": "Central Europe Standard Time", + "locale": "cs-CZ" + }, + "adminAccount": { + "username": "adminx9", + "password": "AdminX9.AdminX9", + "description": "X9 MSP admin account" + }, + "activation": { + "productKey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX", + "kmsServer": "" + }, + "software": { + "install": [ + { "name": "7-Zip", "wingetId": "7zip.7zip" }, + { "name": "Adobe Acrobat Reader","wingetId": "Adobe.Acrobat.Reader.64-bit" }, + { "name": "OpenVPN Connect", "wingetId": "OpenVPNTechnologies.OpenVPNConnect" } + ] + }, + "bloatware": { + "keepPackages": [ + "Microsoft.WindowsCalculator" + ] + }, + "desktopInfo": { + "enabled": true, + "position": "bottomRight", + "fontSize": 12, + "fontColor": "#FFFFFF", + "backgroundColor": "transparent" + }, + "pdfDefault": { + "forceAdobeReader": true, + "scheduledTaskEnabled": true + } +} diff --git a/flash/scripts/00-admin-account.ps1 b/flash/scripts/00-admin-account.ps1 new file mode 100644 index 0000000..bbd3390 --- /dev/null +++ b/flash/scripts/00-admin-account.ps1 @@ -0,0 +1,94 @@ +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 +} + +# ----------------------------------------------------------------------- +# Read account config +# ----------------------------------------------------------------------- +$accountName = "adminx9" +$accountPass = "AdminX9.AdminX9" +$accountDesc = "X9 MSP admin account" + +if ($Config -and $Config.adminAccount) { + if ($Config.adminAccount.username) { $accountName = $Config.adminAccount.username } + if ($Config.adminAccount.password) { $accountPass = $Config.adminAccount.password } + if ($Config.adminAccount.description) { $accountDesc = $Config.adminAccount.description } +} + +Write-Log "Creating admin account: $accountName" -Level INFO + +$securePass = ConvertTo-SecureString $accountPass -AsPlainText -Force + +# ----------------------------------------------------------------------- +# Create or update account +# ----------------------------------------------------------------------- +$existing = Get-LocalUser -Name $accountName -ErrorAction SilentlyContinue + +if ($existing) { + Write-Log " Account already exists - updating password" -Level INFO + try { + Set-LocalUser -Name $accountName -Password $securePass -PasswordNeverExpires $true + Enable-LocalUser -Name $accountName + Write-Log " Account updated: $accountName" -Level OK + } + catch { + Write-Log " Failed to update account: $_" -Level ERROR + } +} else { + try { + New-LocalUser -Name $accountName ` + -Password $securePass ` + -Description $accountDesc ` + -PasswordNeverExpires ` + -UserMayNotChangePassword ` + -ErrorAction Stop | Out-Null + Write-Log " Account created: $accountName" -Level OK + } + catch { + Write-Log " Failed to create account: $_" -Level ERROR + } +} + +# ----------------------------------------------------------------------- +# Add to Administrators group +# ----------------------------------------------------------------------- +try { + $adminsGroup = (Get-LocalGroup | Where-Object { $_.SID -eq "S-1-5-32-544" }).Name + $members = Get-LocalGroupMember -Group $adminsGroup -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like "*$accountName" } + if (-not $members) { + Add-LocalGroupMember -Group $adminsGroup -Member $accountName -ErrorAction Stop + Write-Log " Added to $adminsGroup" -Level OK + } else { + Write-Log " Already in $adminsGroup" -Level INFO + } +} +catch { + Write-Log " Failed to add to Administrators: $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# Hide account from login screen +# ----------------------------------------------------------------------- +try { + $specialPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" + if (-not (Test-Path $specialPath)) { + New-Item -Path $specialPath -Force | Out-Null + } + Set-ItemProperty -Path $specialPath -Name $accountName -Value 0 -Type DWord -Force + Write-Log " Account hidden from login screen" -Level OK +} +catch { + Write-Log " Failed to hide account from login screen: $_" -Level ERROR +} + +Write-Log "Step 0a - Admin account complete" -Level OK diff --git a/flash/scripts/01-bloatware.ps1 b/flash/scripts/01-bloatware.ps1 new file mode 100644 index 0000000..db78de4 --- /dev/null +++ b/flash/scripts/01-bloatware.ps1 @@ -0,0 +1,185 @@ +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 +} + +# ----------------------------------------------------------------------- +# 1a - AppX packages +# ----------------------------------------------------------------------- +$AppxToRemove = @( + "Microsoft.Microsoft3DViewer" + "Microsoft.BingSearch" + "Microsoft.WindowsCamera" + "Clipchamp.Clipchamp" + "Microsoft.WindowsAlarms" + "Microsoft.Copilot" + "Microsoft.549981C3F5F10" + "Microsoft.Windows.DevHome" + "MicrosoftCorporationII.MicrosoftFamily" + "Microsoft.WindowsFeedbackHub" + "Microsoft.Edge.GameAssist" + "Microsoft.GetHelp" + "Microsoft.Getstarted" + "microsoft.windowscommunicationsapps" + "Microsoft.WindowsMaps" + "Microsoft.MixedReality.Portal" + "Microsoft.BingNews" + "Microsoft.MicrosoftOfficeHub" + "Microsoft.Office.OneNote" + "Microsoft.OutlookForWindows" + "Microsoft.Paint" + "Microsoft.MSPaint" + "Microsoft.People" + "Microsoft.Windows.Photos" + "Microsoft.PowerAutomateDesktop" + "MicrosoftCorporationII.QuickAssist" + "Microsoft.SkypeApp" + "Microsoft.ScreenSketch" + "Microsoft.MicrosoftSolitaireCollection" + "Microsoft.MicrosoftStickyNotes" + "MicrosoftTeams" + "MSTeams" + "Microsoft.Todos" + "Microsoft.WindowsSoundRecorder" + "Microsoft.Wallet" + "Microsoft.BingWeather" + "Microsoft.WindowsTerminal" + "Microsoft.Xbox.TCUI" + "Microsoft.XboxApp" + "Microsoft.XboxGameOverlay" + "Microsoft.XboxGamingOverlay" + "Microsoft.XboxIdentityProvider" + "Microsoft.XboxSpeechToTextOverlay" + "Microsoft.GamingApp" + "Microsoft.YourPhone" + "Microsoft.ZuneMusic" + "Microsoft.ZuneVideo" + "7EE7776C.LinkedInforWindows" +) + +# Packages to always keep +$KeepPackages = @("Microsoft.WindowsCalculator") +if ($Config -and $Config.bloatware -and $Config.bloatware.keepPackages) { + $KeepPackages += $Config.bloatware.keepPackages +} +$KeepPackages = $KeepPackages | Select-Object -Unique + +Write-Log "1a - Removing AppX packages" -Level STEP + +foreach ($pkg in $AppxToRemove) { + if ($KeepPackages -contains $pkg) { + Write-Log " KEEP $pkg" -Level INFO + continue + } + + # Installed packages (current user + all users) + $installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue + if ($installed) { + try { + $installed | Remove-AppxPackage -AllUsers -ErrorAction Stop + Write-Log " Removed AppxPackage: $pkg" -Level OK + } + catch { + Write-Log " Failed to remove AppxPackage $pkg - $_" -Level WARN + } + } + + # Provisioned packages (for new users) + $provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue | + Where-Object { $_.DisplayName -eq $pkg } + if ($provisioned) { + try { + $provisioned | Remove-AppxProvisionedPackage -Online -ErrorAction Stop | Out-Null + Write-Log " Removed provisioned: $pkg" -Level OK + } + catch { + Write-Log " Failed to remove provisioned $pkg - $_" -Level WARN + } + } + + if (-not $installed -and -not $provisioned) { + Write-Log " Not found (already removed): $pkg" -Level INFO + } +} + +# ----------------------------------------------------------------------- +# 1b - Windows Capabilities +# ----------------------------------------------------------------------- +$CapabilitiesToRemove = @( + "Print.Fax.Scan" + "Language.Handwriting" + "Browser.InternetExplorer" + "MathRecognizer" + "OneCoreUAP.OneSync" + "OpenSSH.Client" + "Microsoft.Windows.MSPaint" + "Microsoft.Windows.PowerShell.ISE" + "App.Support.QuickAssist" + "Microsoft.Windows.SnippingTool" + "App.StepsRecorder" + "Hello.Face" + "Media.WindowsMediaPlayer" + "Microsoft.Windows.WordPad" +) + +Write-Log "1b - Removing Windows Capabilities" -Level STEP + +$installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue + +foreach ($cap in $CapabilitiesToRemove) { + # Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0) + $matches = $installedCaps | Where-Object { + $_.Name -like "$cap*" -and $_.State -eq "Installed" + } + if ($matches) { + foreach ($c in $matches) { + try { + Remove-WindowsCapability -Online -Name $c.Name -ErrorAction Stop | Out-Null + Write-Log " Removed capability: $($c.Name)" -Level OK + } + catch { + Write-Log " Failed to remove capability $($c.Name) - $_" -Level WARN + } + } + } else { + Write-Log " Not found or not installed: $cap" -Level INFO + } +} + +# ----------------------------------------------------------------------- +# 1c - Windows Optional Features +# ----------------------------------------------------------------------- +$FeaturesToDisable = @( + "MediaPlayback" + "MicrosoftWindowsPowerShellV2Root" + "Microsoft-RemoteDesktopConnection" + "Recall" + "Microsoft-SnippingTool" +) + +Write-Log "1c - Disabling Windows Optional Features" -Level STEP + +foreach ($feat in $FeaturesToDisable) { + $feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue + if ($feature -and $feature.State -eq "Enabled") { + try { + Disable-WindowsOptionalFeature -Online -FeatureName $feat -NoRestart -ErrorAction Stop | Out-Null + Write-Log " Disabled feature: $feat" -Level OK + } + catch { + Write-Log " Failed to disable feature $feat - $_" -Level WARN + } + } else { + Write-Log " Not enabled or not found: $feat" -Level INFO + } +} + +Write-Log "Step 1 complete" -Level OK diff --git a/flash/scripts/02-software.ps1 b/flash/scripts/02-software.ps1 new file mode 100644 index 0000000..f6d7cf1 --- /dev/null +++ b/flash/scripts/02-software.ps1 @@ -0,0 +1,122 @@ +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 +} + +# ----------------------------------------------------------------------- +# Check winget availability +# ----------------------------------------------------------------------- +Write-Log "Checking winget availability" -Level INFO + +$winget = Get-Command winget -ErrorAction SilentlyContinue +if (-not $winget) { + # Try to find winget in known locations + $wingetPaths = @( + "$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe" + "$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller*\winget.exe" + ) + foreach ($p in $wingetPaths) { + $found = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($found) { $winget = $found.FullName; break } + } +} + +if (-not $winget) { + Write-Log "winget not found - software installation skipped" -Level ERROR + exit 1 +} + +Write-Log "winget found: $($winget.Source -or $winget)" -Level OK + +# Accept agreements upfront +& winget source update --accept-source-agreements 2>&1 | Out-Null + +# ----------------------------------------------------------------------- +# Install packages from config +# ----------------------------------------------------------------------- +if (-not $Config -or -not $Config.software -or -not $Config.software.install) { + Write-Log "No software list in config - skipping installs" -Level WARN +} else { + foreach ($pkg in $Config.software.install) { + Write-Log "Installing $($pkg.name) ($($pkg.wingetId))" -Level INFO + $result = & winget install --id $pkg.wingetId ` + --silent ` + --accept-package-agreements ` + --accept-source-agreements ` + --disable-interactivity ` + 2>&1 + + $exitCode = $LASTEXITCODE + if ($exitCode -eq 0) { + Write-Log " Installed OK: $($pkg.name)" -Level OK + } elseif ($exitCode -eq -1978335189) { + # 0x8A150011 = already installed + Write-Log " Already installed: $($pkg.name)" -Level OK + } else { + Write-Log " Failed: $($pkg.name) (exit $exitCode)" -Level ERROR + Write-Log " Output: $($result -join ' ')" -Level ERROR + } + } +} + +# ----------------------------------------------------------------------- +# Set Adobe Reader as default PDF app +# ----------------------------------------------------------------------- +$forcePdf = $true +if ($Config -and $Config.pdfDefault) { + $forcePdf = [bool]$Config.pdfDefault.forceAdobeReader +} + +if ($forcePdf) { + Write-Log "Setting Adobe Reader as default PDF app" -Level INFO + + # Find Adobe PDF viewer executable (Acrobat DC or Reader DC) + $acroPaths = @( + "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + "${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" + "$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" + "${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe" + ) + $acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 + + if (-not $acroExe) { + Write-Log " Adobe PDF viewer not found - PDF default not set" -Level WARN + } else { + Write-Log " Found: $acroExe" -Level INFO + + # Set file type association via HKCR (system-wide, requires admin) + $progId = "AcroExch.Document.DC" + $openCmd = "`"$acroExe`" `"%1`"" + + # HKCR\.pdf -> progId + if (-not (Test-Path "HKCR:\.pdf")) { + New-Item -Path "HKCR:\.pdf" -Force | Out-Null + } + Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId + + # HKCR\AcroExch.Document.DC\shell\open\command + $cmdPath = "HKCR:\$progId\shell\open\command" + if (-not (Test-Path $cmdPath)) { + New-Item -Path $cmdPath -Force | Out-Null + } + Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd + + # Note: HKCU UserChoice requires a Windows-computed Hash value to be valid. + # Direct ProgId write without Hash is silently reset by Windows on next access. + # HKCR write above provides the system-wide default; per-user default is + # handled by the PDF-DefaultApp scheduled task via HKCR on every logon. + + Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK + } +} + +Write-Log "Step 2 complete" -Level OK diff --git a/flash/scripts/03-system-registry.ps1 b/flash/scripts/03-system-registry.ps1 new file mode 100644 index 0000000..38e6fdb --- /dev/null +++ b/flash/scripts/03-system-registry.ps1 @@ -0,0 +1,324 @@ +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 - hide First Run Experience +# ----------------------------------------------------------------------- +Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" ` + -Name "HideFirstRunExperience" -Value 1 + +# Also disable Edge desktop shortcut creation after install +Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" ` + -Name "CreateDesktopShortcutDefault" -Value 0 + +# ----------------------------------------------------------------------- +# Password - no expiration +# ----------------------------------------------------------------------- +Write-Log " Setting password max age to UNLIMITED" -Level INFO +$pwResult = & net accounts /maxpwage:UNLIMITED 2>&1 +if ($LASTEXITCODE -eq 0) { + Write-Log " Password max age set to UNLIMITED" -Level OK +} else { + Write-Log " Failed to set password max age: $pwResult" -Level ERROR +} + +# ----------------------------------------------------------------------- +# Time zone +# ----------------------------------------------------------------------- +$tz = "Central Europe Standard Time" +if ($Config -and $Config.deployment -and $Config.deployment.timezone) { + $tz = $Config.deployment.timezone +} +Write-Log " Setting time zone: $tz" -Level INFO +try { + Set-TimeZone -Id $tz -ErrorAction Stop + Write-Log " Time zone set: $tz" -Level OK +} +catch { + Write-Log " Failed to set time zone: $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# OneDrive - prevent setup and remove shortcuts +# ----------------------------------------------------------------------- +Write-Log " Disabling OneDrive" -Level INFO + +# Disable OneDrive via policy +Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" ` + -Name "DisableFileSyncNGSC" -Value 1 + +# Remove OneDriveSetup.exe if present +$oneDrivePaths = @( + "$env:SystemRoot\System32\OneDriveSetup.exe" + "$env:SystemRoot\SysWOW64\OneDriveSetup.exe" +) +foreach ($odPath in $oneDrivePaths) { + if (Test-Path $odPath) { + try { + # Uninstall first + & $odPath /uninstall 2>&1 | Out-Null + Write-Log " OneDrive uninstalled via $odPath" -Level OK + } + catch { + Write-Log " OneDrive uninstall failed: $_" -Level WARN + } + } +} + +# Remove OneDrive Start Menu shortcut +$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk" +if (Test-Path $odLnk) { + Remove-Item $odLnk -Force -ErrorAction SilentlyContinue + Write-Log " Removed OneDrive Start Menu shortcut" -Level OK +} + +# ----------------------------------------------------------------------- +# Outlook (new) - disable auto-install via UScheduler +# ----------------------------------------------------------------------- +Write-Log " Disabling Outlook (new) auto-install" -Level INFO + +$uschedulerPaths = @( + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate" + "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate" +) +foreach ($uPath in $uschedulerPaths) { + if (Test-Path $uPath) { + try { + Remove-Item -Path $uPath -Recurse -Force + Write-Log " Removed UScheduler key: $uPath" -Level OK + } + catch { + Write-Log " Failed to remove UScheduler key: $_" -Level WARN + } + } +} + +# ----------------------------------------------------------------------- +# Disable GameDVR +# ----------------------------------------------------------------------- +Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" ` + -Name "AllowGameDVR" -Value 0 + +# ----------------------------------------------------------------------- +# Disable Recall (Windows AI feature) +# ----------------------------------------------------------------------- +Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" ` + -Name "DisableAIDataAnalysis" -Value 1 + +# ----------------------------------------------------------------------- +# 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 + +Write-Log "Step 3 complete" -Level OK diff --git a/flash/scripts/04-default-profile.ps1 b/flash/scripts/04-default-profile.ps1 new file mode 100644 index 0000000..03b0b1e --- /dev/null +++ b/flash/scripts/04-default-profile.ps1 @@ -0,0 +1,324 @@ +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 +} + +# ----------------------------------------------------------------------- +# Helper - apply a registry setting to both Default hive and current HKCU +# ----------------------------------------------------------------------- +function Grant-HiveWriteAccess { + param([string]$HivePath) # full path e.g. "Registry::HKU\DefaultProfile\Software\..." + # Grants Administrators FullControl on a loaded hive key with restricted ACL. + try { + $acl = Get-Acl -Path $HivePath -ErrorAction Stop + $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) + Set-Acl -Path $HivePath -AclObject $acl -ErrorAction Stop + } + catch { + Write-Log " Grant-HiveWriteAccess failed for $HivePath - $_" -Level WARN + } +} + +function Set-ProfileReg { + param( + [string]$SubKey, # relative to HKCU (e.g. "Software\Microsoft\...") + [string]$Name, + $Value, + [string]$Type = "DWord" + ) + + # Apply to loaded Default hive + $defPath = "Registry::HKU\DefaultProfile\$SubKey" + try { + if (-not (Test-Path $defPath)) { + New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null + } + Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop + } + catch { + # Retry after granting write access to parent key + try { + $parentPath = $defPath -replace '\\[^\\]+$', '' + if (Test-Path $parentPath) { Grant-HiveWriteAccess -HivePath $parentPath } + if (-not (Test-Path $defPath)) { + New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null + } + Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop + } + catch { + Write-Log " DEFAULT HIVE failed $SubKey\$Name - $_" -Level ERROR + } + } + + # Apply to current user as well + $hkcuPath = "HKCU:\$SubKey" + try { + if (-not (Test-Path $hkcuPath)) { + New-Item -Path $hkcuPath -Force -ErrorAction Stop | Out-Null + } + Set-ItemProperty -Path $hkcuPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop + Write-Log " SET $SubKey\$Name = $Value" -Level OK + } + catch { + Write-Log " HKCU failed $SubKey\$Name - $_" -Level ERROR + } +} + +function Remove-ProfileReg { + param([string]$SubKey, [string]$Name) + + $defPath = "Registry::HKU\DefaultProfile\$SubKey" + try { + if (Test-Path $defPath) { + Remove-ItemProperty -Path $defPath -Name $Name -Force -ErrorAction SilentlyContinue + } + } + catch { } + + $hkcuPath = "HKCU:\$SubKey" + try { + if (Test-Path $hkcuPath) { + Remove-ItemProperty -Path $hkcuPath -Name $Name -Force -ErrorAction SilentlyContinue + Write-Log " REMOVED $SubKey\$Name" -Level OK + } + } + catch { + Write-Log " FAILED removing $SubKey\$Name - $_" -Level ERROR + } +} + +# ----------------------------------------------------------------------- +# Load Default profile hive +# ----------------------------------------------------------------------- +$hivePath = "C:\Users\Default\NTUSER.DAT" +$hiveKey = "DefaultProfile" + +Write-Log "Loading Default hive: $hivePath" -Level INFO + +# Unload first in case previous run left it mounted +& reg unload "HKU\$hiveKey" 2>&1 | Out-Null + +$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to load Default hive: $loadResult" -Level ERROR + exit 1 +} +Write-Log "Default hive loaded" -Level OK + +try { + # ----------------------------------------------------------------------- + # Taskbar settings (Win10 + Win11) + # ----------------------------------------------------------------------- + Write-Log "Applying taskbar settings" -Level STEP + + $tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" + + # Win11: align taskbar to left (0 = left, 1 = center) + Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0 + + # Hide Search box / button - Win10/11 (0 = hidden, 1 = icon, 2 = full box) + # Note: Win11 uses Search subkey, Win10 uses Explorer\Advanced - set both + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Search" ` + -Name "SearchboxTaskbarMode" -Value 0 + Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0 + + # Hide Task View button + Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0 + + # Hide Widgets button + Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0 + + # Hide Chat / Teams button + Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0 + + # Hide Copilot button + Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0 + + # Show file extensions in Explorer + Set-ProfileReg -SubKey $tbPath -Name "HideFileExt" -Value 0 + + # Open Explorer to This PC instead of Quick Access + Set-ProfileReg -SubKey $tbPath -Name "LaunchTo" -Value 1 + + # ----------------------------------------------------------------------- + # System tray - show all icons + # ----------------------------------------------------------------------- + # EnableAutoTray = 0 works on Win10; Win11 ignores it but set anyway + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" ` + -Name "EnableAutoTray" -Value 0 + + # Win11 workaround: clear cached tray icon streams so all icons appear on next login + # Windows rebuilds the streams with all icons visible when no cache exists + $trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" + if (Test-Path $trayNotifyKey) { + Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $trayNotifyKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue + Write-Log " Cleared TrayNotify icon streams (Win11 systray workaround)" -Level OK + } + + # Also clear in Default hive so new users start with clean state + $defTrayKey = "Registry::HKU\DefaultProfile\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" + if (Test-Path $defTrayKey) { + Remove-ItemProperty -Path $defTrayKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $defTrayKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue + Write-Log " Cleared TrayNotify icon streams in Default hive" -Level OK + } + + # ----------------------------------------------------------------------- + # Desktop icons - show This PC + # ----------------------------------------------------------------------- + # CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} = This PC / Computer + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" ` + -Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0 + + # ----------------------------------------------------------------------- + # Start menu settings + # ----------------------------------------------------------------------- + Write-Log "Applying Start menu settings" -Level STEP + + # Disable Bing search suggestions in Start menu + Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" ` + -Name "DisableSearchBoxSuggestions" -Value 1 + + # Win11: empty Start menu pins + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" ` + -Name "ConfigureStartPins" ` + -Value '{"pinnedList":[]}' ` + -Type "String" + + # Hide "Recently added" apps in Start menu + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" ` + -Name "Start_TrackProgs" -Value 0 + + # Hide recently opened files/docs from Start menu + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" ` + -Name "Start_TrackDocs" -Value 0 + + # ----------------------------------------------------------------------- + # Copilot - disable + # ----------------------------------------------------------------------- + Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" ` + -Name "TurnOffWindowsCopilot" -Value 1 + + # ----------------------------------------------------------------------- + # GameDVR - disable + # ----------------------------------------------------------------------- + Set-ProfileReg -SubKey "System\GameConfigStore" ` + -Name "GameDVR_Enabled" -Value 0 + + Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" ` + -Name "AppCaptureEnabled" -Value 0 + + # ----------------------------------------------------------------------- + # Num Lock on startup + # ----------------------------------------------------------------------- + Set-ProfileReg -SubKey "Control Panel\Keyboard" ` + -Name "InitialKeyboardIndicators" -Value 2 -Type "String" + + # ----------------------------------------------------------------------- + # Accent color on title bars + # ----------------------------------------------------------------------- + Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" ` + -Name "ColorPrevalence" -Value 1 + + # ----------------------------------------------------------------------- + # OneDrive - remove RunOnce key from Default profile + # ----------------------------------------------------------------------- + Write-Log "Removing OneDrive from Default profile RunOnce" -Level INFO + Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Run" ` + -Name "OneDriveSetup" + + Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\RunOnce" ` + -Name "Delete Cached Standalone Update Binary" + + # Remove OneDrive from Explorer namespace (left panel) + $oneDriveClsid = "{018D5C66-4533-4307-9B53-224DE2ED1FE6}" + $nsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$oneDriveClsid" + $defNsPath = "Registry::HKU\DefaultProfile\$nsPath" + if (Test-Path $defNsPath) { + Remove-Item -Path $defNsPath -Recurse -Force -ErrorAction SilentlyContinue + Write-Log " Removed OneDrive from Explorer namespace (Default)" -Level OK + } + $hkcuNsPath = "HKCU:\$nsPath" + if (Test-Path $hkcuNsPath) { + Remove-Item -Path $hkcuNsPath -Recurse -Force -ErrorAction SilentlyContinue + Write-Log " Removed OneDrive from Explorer namespace (HKCU)" -Level OK + } + + # ----------------------------------------------------------------------- + # Empty taskbar pinned apps (Win10/11) + # ----------------------------------------------------------------------- + Write-Log "Clearing taskbar pinned apps layout" -Level INFO + + $taskbarLayoutDir = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell" + if (-not (Test-Path $taskbarLayoutDir)) { + New-Item -ItemType Directory -Path $taskbarLayoutDir -Force | Out-Null + } + + $taskbarLayoutXml = @" + + + + + + + + + +"@ + $taskbarLayoutXml | Set-Content -Path "$taskbarLayoutDir\LayoutModification.xml" -Encoding UTF8 -Force + Write-Log " Taskbar LayoutModification.xml written" -Level OK + +} +finally { + # ----------------------------------------------------------------------- + # Unload Default hive - always, even on error + # ----------------------------------------------------------------------- + Write-Log "Unloading Default hive" -Level INFO + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + Start-Sleep -Milliseconds 500 + + $unloadResult = & reg unload "HKU\$hiveKey" 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Log "Default hive unloaded" -Level OK + } else { + Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR + } +} + +# ----------------------------------------------------------------------- +# Restart Explorer to apply taskbar/tray changes to current session +# ----------------------------------------------------------------------- +Write-Log "Restarting Explorer to apply taskbar changes" -Level INFO +try { + Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + Start-Process explorer + Write-Log "Explorer restarted" -Level OK +} +catch { + Write-Log "Explorer restart failed (non-fatal): $_" -Level WARN +} + +Write-Log "Step 4 complete" -Level OK diff --git a/flash/scripts/05-personalization.ps1 b/flash/scripts/05-personalization.ps1 new file mode 100644 index 0000000..957806e --- /dev/null +++ b/flash/scripts/05-personalization.ps1 @@ -0,0 +1,172 @@ +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 +} + +# Accent color #223B47 stored as ABGR DWORD: 0xFF473B22 +# A=FF B=47 G=3B R=22 -> 0xFF473B22 = 4283612962 +$AccentColorABGR = 0xFF473B22 + +# Gradient colors (Windows generates these automatically but we set them explicitly) +# AccentPalette is 32 bytes - 8 shades of the accent color (BGRA each) +# We use the same color for all shades as a safe default +$AccentColorHex = "#223B47" + +function Set-Reg { + param([string]$Path, [string]$Name, $Value, [string]$Type = "DWord") + try { + if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null } + Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force + Write-Log " SET $Path\$Name = $Value" -Level OK + } + catch { + Write-Log " FAILED $Path\$Name - $_" -Level ERROR + } +} + +function Apply-ThemeSettings { + param([string]$HiveRoot) # "HKCU:" or "Registry::HKU\DefaultProfile" + + # ----------------------------------------------------------------------- + # System theme - Dark (taskbar, Start, action center) + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" ` + -Name "SystemUsesLightTheme" -Value 0 + + # ----------------------------------------------------------------------- + # App theme - Light + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" ` + -Name "AppsUseLightTheme" -Value 1 + + # ----------------------------------------------------------------------- + # Accent color on Start and taskbar + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" ` + -Name "ColorPrevalence" -Value 1 + + # ----------------------------------------------------------------------- + # Transparency effects - disabled + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" ` + -Name "EnableTransparency" -Value 0 + + # ----------------------------------------------------------------------- + # Accent color + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" ` + -Name "AccentColor" -Value $AccentColorABGR -Type "DWord" + + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" ` + -Name "ColorizationColor" -Value $AccentColorABGR -Type "DWord" + + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" ` + -Name "ColorizationAfterglow" -Value $AccentColorABGR -Type "DWord" + + # Accent color on title bars and borders + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" ` + -Name "ColorPrevalence" -Value 1 + + # ----------------------------------------------------------------------- + # Wallpaper - solid color #223B47 (fallback before DesktopInfo runs) + # ----------------------------------------------------------------------- + # Background color as decimal RGB + Set-Reg -Path "$HiveRoot\Control Panel\Colors" ` + -Name "Background" -Value "34 59 71" -Type "String" + + Set-Reg -Path "$HiveRoot\Control Panel\Desktop" ` + -Name "WallpaperStyle" -Value "0" -Type "String" + + Set-Reg -Path "$HiveRoot\Control Panel\Desktop" ` + -Name "TileWallpaper" -Value "0" -Type "String" + + # ----------------------------------------------------------------------- + # Desktop icons - show This PC + # ----------------------------------------------------------------------- + Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" ` + -Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0 +} + +# ----------------------------------------------------------------------- +# Load Default hive +# ----------------------------------------------------------------------- +$hivePath = "C:\Users\Default\NTUSER.DAT" +$hiveKey = "DefaultProfile" + +Write-Log "Loading Default hive for personalization" -Level INFO + +& reg unload "HKU\$hiveKey" 2>&1 | Out-Null +$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1 +if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to load Default hive: $loadResult" -Level ERROR + Write-Log "Applying personalization to current user only" -Level WARN + + Write-Log "Applying theme to current user (HKCU)" -Level STEP + Apply-ThemeSettings -HiveRoot "HKCU:" + + # Set wallpaper via SystemParametersInfo for current user + Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; +public class WallpaperHelper { + [DllImport("user32.dll", CharSet=CharSet.Auto)] + public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); +} +"@ -ErrorAction SilentlyContinue + [WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null + exit 0 +} + +try { + Write-Log "Applying theme to Default hive" -Level STEP + Apply-ThemeSettings -HiveRoot "Registry::HKU\DefaultProfile" + + Write-Log "Applying theme to current user (HKCU)" -Level STEP + Apply-ThemeSettings -HiveRoot "HKCU:" +} +finally { + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + Start-Sleep -Milliseconds 500 + + $unloadResult = & reg unload "HKU\$hiveKey" 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Log "Default hive unloaded" -Level OK + } else { + Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR + } +} + +# ----------------------------------------------------------------------- +# Apply wallpaper (solid color) to current desktop session +# ----------------------------------------------------------------------- +Write-Log "Setting desktop wallpaper to solid color" -Level INFO + +try { + Add-Type -TypeDefinition @" +using System; +using System.Runtime.InteropServices; +public class WallpaperHelper { + [DllImport("user32.dll", CharSet=CharSet.Auto)] + public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); +} +"@ -ErrorAction SilentlyContinue + + # SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3 + # Empty string = solid color defined in Control Panel\Colors\Background + [WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null + Write-Log " Desktop wallpaper updated" -Level OK +} +catch { + Write-Log " Failed to update wallpaper: $_" -Level WARN +} + +Write-Log "Step 5 complete" -Level OK diff --git a/flash/scripts/06-scheduled-tasks.ps1 b/flash/scripts/06-scheduled-tasks.ps1 new file mode 100644 index 0000000..fbdfec8 --- /dev/null +++ b/flash/scripts/06-scheduled-tasks.ps1 @@ -0,0 +1,194 @@ +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 +} + +$ScriptDir = "C:\Windows\Setup\Scripts" +if (-not (Test-Path $ScriptDir)) { + New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null +} + +function Register-Task { + param( + [string]$TaskName, + [string]$Description, + [object]$Action, + [object[]]$Triggers, + [string]$RunLevel = "Highest" + ) + try { + # Remove existing task with same name + Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue + + $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) ` + -MultipleInstances IgnoreNew ` + -StartWhenAvailable + + $principal = New-ScheduledTaskPrincipal -GroupId "Users" ` + -RunLevel $RunLevel + + $task = New-ScheduledTask -Action $Action ` + -Trigger $Triggers ` + -Settings $settings ` + -Principal $principal ` + -Description $Description + + Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force | Out-Null + Write-Log " Registered task: $TaskName" -Level OK + } + catch { + Write-Log " Failed to register task $TaskName - $_" -Level ERROR + } +} + +# ----------------------------------------------------------------------- +# Task: ShowAllTrayIcons +# Runs on logon: clears TrayNotify icon cache and restarts Explorer so all +# tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear) +# ----------------------------------------------------------------------- +Write-Log "Registering task: ShowAllTrayIcons" -Level STEP + +$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1" +@' +# Win10: disable auto-hiding of tray icons +$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer" +Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force -ErrorAction SilentlyContinue + +# Win11: clear icon stream cache so all icons become visible after Explorer restart +$trayPath = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" +if (Test-Path $trayPath) { + Remove-ItemProperty -Path $trayPath -Name "IconStreams" -Force -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $trayPath -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue +} + +# Restart Explorer to apply changes +Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue +Start-Sleep -Milliseconds 1500 +if (-not (Get-Process explorer -ErrorAction SilentlyContinue)) { + Start-Process explorer +} +'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force + +$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`"" +$showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn + +Register-Task -TaskName "ShowAllTrayIcons" ` + -Description "Show all system tray icons for current user" ` + -Action $showTrayAction ` + -Triggers $showTrayTrigger + +# ----------------------------------------------------------------------- +# Task: PDF-DefaultApp +# Runs on every logon, restores .pdf -> Adobe Reader association +# Guards against Edge overwriting it +# ----------------------------------------------------------------------- +Write-Log "Registering task: PDF-DefaultApp" -Level STEP + +$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1" +@' +# Restore .pdf -> Adobe Reader HKCR association (system-wide). +# Runs as SYSTEM so it can write to HKCR regardless of Edge updates. +# Note: HKCU UserChoice requires Windows Hash validation and cannot be +# set reliably via registry; HKCR provides the system-wide fallback. +$acroPaths = @( + "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe" + "${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" + "$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe" + "${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe" +) +$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 +if (-not $acroExe) { exit 0 } + +$progId = "AcroExch.Document.DC" +$openCmd = "`"$acroExe`" `"%1`"" + +# HKCR\.pdf +if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null } +$current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)" +if ($current -ne $progId) { + Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force +} + +# HKCR\AcroExch.Document.DC\shell\open\command +$cmdPath = "HKCR:\$progId\shell\open\command" +if (-not (Test-Path $cmdPath)) { New-Item -Path $cmdPath -Force | Out-Null } +Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd -Force +'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force + +$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`"" +$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn + +# Runs as SYSTEM to allow HKCR writes (system-wide file association) +$pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest +$pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) ` + -MultipleInstances IgnoreNew ` + -StartWhenAvailable +$pdfTask = New-ScheduledTask -Action $pdfAction ` + -Trigger $pdfTrigger ` + -Settings $pdfSettings ` + -Principal $pdfPrincipal ` + -Description "Restore Adobe Reader as default PDF app on logon" +try { + Unregister-ScheduledTask -TaskName "PDF-DefaultApp" -Confirm:$false -ErrorAction SilentlyContinue + Register-ScheduledTask -TaskName "PDF-DefaultApp" -InputObject $pdfTask -Force | Out-Null + Write-Log " Registered task: PDF-DefaultApp" -Level OK +} +catch { + Write-Log " Failed to register task PDF-DefaultApp - $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# Task: UnlockStartLayout +# Runs once after deployment to unlock the Start menu layout +# so users can still customize it later +# ----------------------------------------------------------------------- +Write-Log "Registering task: UnlockStartLayout" -Level STEP + +$unlockScript = "$ScriptDir\UnlockStartLayout.ps1" +@' +# Remove Start layout lock so users can modify it +$layoutXml = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml" +if (Test-Path $layoutXml) { + Remove-Item $layoutXml -Force -ErrorAction SilentlyContinue +} + +# Unregister self after running once +Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue +'@ | Set-Content -Path $unlockScript -Encoding UTF8 -Force + +$unlockAction = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$unlockScript`"" +# Trigger: 5 minutes after system startup, once +$unlockTrigger = New-ScheduledTaskTrigger -AtStartup +$unlockTrigger.Delay = "PT5M" + +$unlockPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest +$unlockSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 10) ` + -StartWhenAvailable +$unlockTask = New-ScheduledTask -Action $unlockAction ` + -Trigger $unlockTrigger ` + -Settings $unlockSettings ` + -Principal $unlockPrincipal ` + -Description "Unlock Start menu layout 5 min after first boot" + +try { + Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue + Register-ScheduledTask -TaskName "UnlockStartLayout" -InputObject $unlockTask -Force | Out-Null + Write-Log " Registered task: UnlockStartLayout" -Level OK +} +catch { + Write-Log " Failed to register task UnlockStartLayout - $_" -Level ERROR +} + +Write-Log "Step 6 complete" -Level OK diff --git a/flash/scripts/07-desktop-info.ps1 b/flash/scripts/07-desktop-info.ps1 new file mode 100644 index 0000000..7e836b8 --- /dev/null +++ b/flash/scripts/07-desktop-info.ps1 @@ -0,0 +1,217 @@ +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 +} + +$ScriptDir = "C:\Windows\Setup\Scripts" +$RenderScript = "$ScriptDir\DesktopInfo-Render.ps1" +$BmpPath = "$ScriptDir\desktopinfo.bmp" + +if (-not (Test-Path $ScriptDir)) { + New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null +} + +# ----------------------------------------------------------------------- +# Read display settings from config +# ----------------------------------------------------------------------- +$fontSize = 13 +$fontColor = "#FFFFFF" +$position = "bottomRight" + +if ($Config -and $Config.desktopInfo) { + if ($Config.desktopInfo.fontSize) { $fontSize = [int]$Config.desktopInfo.fontSize } + if ($Config.desktopInfo.fontColor) { $fontColor = $Config.desktopInfo.fontColor } + if ($Config.desktopInfo.position) { $position = $Config.desktopInfo.position } +} + +# ----------------------------------------------------------------------- +# Write the rendering script (runs on every logon as the user) +# ----------------------------------------------------------------------- +Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO + +$renderContent = @" +# DesktopInfo-Render.ps1 +# Collects system info and renders it onto the desktop wallpaper. +# Runs on every user logon via Scheduled Task. + +`$ErrorActionPreference = "Continue" + +Add-Type -AssemblyName System.Drawing +Add-Type -TypeDefinition @' +using System; +using System.Runtime.InteropServices; +public class WallpaperApi { + [DllImport("user32.dll", CharSet=CharSet.Auto)] + public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); +} +'@ -ErrorAction SilentlyContinue + +# ----------------------------------------------------------------------- +# Collect system info +# ----------------------------------------------------------------------- +`$hostname = `$env:COMPUTERNAME +`$username = `$env:USERNAME +`$ipAddress = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | + Where-Object { `$_.IPAddress -ne "127.0.0.1" -and `$_.PrefixOrigin -ne "WellKnown" } | + Select-Object -First 1).IPAddress +if (-not `$ipAddress) { `$ipAddress = "N/A" } + +`$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue +`$osName = if (`$osInfo) { `$osInfo.Caption -replace "Microsoft ", "" } else { "Windows" } +`$osBuild = if (`$osInfo) { `$osInfo.BuildNumber } else { "" } + +# Deployment date = when script was first run, stored in registry +`$deployRegPath = "HKLM:\SOFTWARE\X9\Deployment" +`$deployDate = (Get-ItemProperty -Path `$deployRegPath -Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate +if (-not `$deployDate) { `$deployDate = "N/A" } + +# ----------------------------------------------------------------------- +# Build info lines +# ----------------------------------------------------------------------- +`$lines = @( + "Computer : `$hostname" + "User : `$username" + "IP : `$ipAddress" + "OS : `$osName (build `$osBuild)" + "Deployed : `$deployDate" +) + +# ----------------------------------------------------------------------- +# Render bitmap +# ----------------------------------------------------------------------- +Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue + +`$screen = [System.Windows.Forms.Screen]::PrimaryScreen +`$width = if (`$screen) { `$screen.Bounds.Width } else { 1920 } +`$height = if (`$screen) { `$screen.Bounds.Height } else { 1080 } + +`$bmp = New-Object System.Drawing.Bitmap(`$width, `$height) +`$g = [System.Drawing.Graphics]::FromImage(`$bmp) + +# Background: solid accent color #223B47 +`$bgColor = [System.Drawing.ColorTranslator]::FromHtml("#223B47") +`$g.Clear(`$bgColor) + +# Font and colors +`$fontFamily = "Consolas" +`$fontSize = $fontSize +`$font = New-Object System.Drawing.Font(`$fontFamily, `$fontSize, [System.Drawing.FontStyle]::Regular) +`$brush = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("$fontColor")) +`$shadowBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(180, 0, 0, 0)) + +# Measure text block +`$lineHeight = `$font.GetHeight(`$g) + 4 +`$blockH = `$lines.Count * `$lineHeight +`$maxWidth = (`$lines | ForEach-Object { `$g.MeasureString(`$_, `$font).Width } | Measure-Object -Maximum).Maximum + +# Position +`$margin = 24 +`$pos = "$position" +`$x = switch -Wildcard (`$pos) { + "*Right" { `$width - `$maxWidth - `$margin } + "*Left" { `$margin } + default { `$margin } +} +`$y = switch -Wildcard (`$pos) { + "bottom*" { `$height - `$blockH - `$margin } + "top*" { `$margin } + default { `$height - `$blockH - `$margin } +} + +# Draw shadow then text +foreach (`$line in `$lines) { + `$g.DrawString(`$line, `$font, `$shadowBrush, (`$x + 1), (`$y + 1)) + `$g.DrawString(`$line, `$font, `$brush, `$x, `$y) + `$y += `$lineHeight +} + +`$g.Dispose() + +# Save BMP +`$bmpPath = "$BmpPath" +`$bmp.Save(`$bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp) +`$bmp.Dispose() + +# Set as wallpaper +# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3 +[WallpaperApi]::SystemParametersInfo(20, 0, `$bmpPath, 3) | Out-Null +"@ + +$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force +Write-Log "Render script written" -Level OK + +# ----------------------------------------------------------------------- +# Store deployment date in registry (used by render script) +# ----------------------------------------------------------------------- +Write-Log "Storing deployment date in registry" -Level INFO +try { + if (-not (Test-Path "HKLM:\SOFTWARE\X9\Deployment")) { + New-Item -Path "HKLM:\SOFTWARE\X9\Deployment" -Force | Out-Null + } + $existingDate = (Get-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" ` + -Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate + if (-not $existingDate) { + Set-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" ` + -Name "DeployDate" ` + -Value (Get-Date -Format "yyyy-MM-dd") ` + -Force + Write-Log " DeployDate set: $(Get-Date -Format 'yyyy-MM-dd')" -Level OK + } else { + Write-Log " DeployDate already set: $existingDate" -Level INFO + } +} +catch { + Write-Log " Failed to set DeployDate: $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# Register scheduled task: DesktopInfo +# Runs the render script on every user logon +# ----------------------------------------------------------------------- +Write-Log "Registering task: DesktopInfo" -Level STEP + +try { + Unregister-ScheduledTask -TaskName "DesktopInfo" -Confirm:$false -ErrorAction SilentlyContinue + + $action = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`"" + $trigger = New-ScheduledTaskTrigger -AtLogOn + $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) ` + -MultipleInstances IgnoreNew ` + -StartWhenAvailable + $principal = New-ScheduledTaskPrincipal -GroupId "Users" -RunLevel Limited + + $task = New-ScheduledTask -Action $action ` + -Trigger $trigger ` + -Settings $settings ` + -Principal $principal ` + -Description "Render system info onto desktop wallpaper on logon" + + Register-ScheduledTask -TaskName "DesktopInfo" -InputObject $task -Force | Out-Null + Write-Log "Task DesktopInfo registered" -Level OK +} +catch { + Write-Log "Failed to register DesktopInfo task: $_" -Level ERROR +} + +# ----------------------------------------------------------------------- +# Run once immediately for current user +# ----------------------------------------------------------------------- +Write-Log "Running DesktopInfo render now for current user" -Level INFO +try { + & powershell.exe -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File $RenderScript + Write-Log "DesktopInfo rendered" -Level OK +} +catch { + Write-Log "DesktopInfo render failed: $_" -Level WARN +} + +Write-Log "Step 7 complete" -Level OK diff --git a/flash/scripts/08-activation.ps1 b/flash/scripts/08-activation.ps1 new file mode 100644 index 0000000..c3cbef3 --- /dev/null +++ b/flash/scripts/08-activation.ps1 @@ -0,0 +1,109 @@ +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 +} + +# ----------------------------------------------------------------------- +# KMS Generic Volume License Keys (GVLK) +# Source: https://docs.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys +# These are official Microsoft-published keys for use with KMS infrastructure. +# Replace with your MAK/retail key for standalone activation. +# ----------------------------------------------------------------------- +$KmsKeys = @{ + # Windows 11 + "Windows 11 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX" + "Windows 11 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9" + "Windows 11 Pro Education" = "6TP4R-GNPTD-KYYHQ-7B7DP-J447Y" + "Windows 11 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2" + "Windows 11 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43" + # Windows 10 + "Windows 10 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX" + "Windows 10 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9" + "Windows 10 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2" + "Windows 10 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43" + "Windows 10 Home" = "TX9XD-98N7V-6WMQ6-BX7FG-H8Q99" +} + +# ----------------------------------------------------------------------- +# Check current activation status +# ----------------------------------------------------------------------- +Write-Log "Checking Windows activation status" -Level INFO + +$licenseStatus = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue | + Select-Object -First 1).LicenseStatus +# LicenseStatus: 0=Unlicensed, 1=Licensed, 2=OOBGrace, 3=OOTGrace, 4=NonGenuineGrace, 5=Notification, 6=ExtendedGrace + +if ($licenseStatus -eq 1) { + Write-Log " Windows is already activated - skipping" -Level OK +} else { + Write-Log " Activation status: $licenseStatus (not activated)" -Level WARN + + # Detect Windows edition + $osCaption = (Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption + Write-Log " Detected OS: $osCaption" -Level INFO + + # Check if a key is configured in config + $customKey = $null + if ($Config -and $Config.activation -and $Config.activation.productKey) { + $customKey = $Config.activation.productKey + } + + if ($customKey -and $customKey -ne "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX") { + # Use key from config + $keyToUse = $customKey + Write-Log " Using product key from config" -Level INFO + } else { + # Find matching GVLK key by OS name + $keyToUse = $null + foreach ($entry in $KmsKeys.GetEnumerator()) { + if ($osCaption -like "*$($entry.Key)*") { + $keyToUse = $entry.Value + Write-Log " Matched GVLK key for: $($entry.Key)" -Level INFO + break + } + } + } + + if (-not $keyToUse) { + Write-Log " No matching key found for: $osCaption" -Level WARN + Write-Log " Skipping activation - set activation.productKey in config.json" -Level WARN + } else { + # Install key + Write-Log " Installing product key..." -Level INFO + $ipkResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ipk $keyToUse 2>&1 + if ($LASTEXITCODE -eq 0) { + Write-Log " Key installed" -Level OK + } else { + Write-Log " Key install result: $ipkResult" -Level WARN + } + + # Set KMS server if configured + if ($Config -and $Config.activation -and $Config.activation.kmsServer) { + $kmsServer = $Config.activation.kmsServer + Write-Log " Setting KMS server: $kmsServer" -Level INFO + & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /skms $kmsServer 2>&1 | Out-Null + } + + # Attempt activation + Write-Log " Attempting activation..." -Level INFO + $atoResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ato 2>&1 + $atoOutput = $atoResult -join " " + + if ($atoOutput -match "successfully" -or $atoOutput -match "uspesn") { + Write-Log " Activation successful" -Level OK + } else { + Write-Log " Activation result: $atoOutput" -Level WARN + Write-Log " Activation may require a KMS server or valid MAK key" -Level WARN + } + } +} + +Write-Log "Step 8 - Activation complete" -Level OK diff --git a/scripts/02-software.ps1 b/scripts/02-software.ps1 index ab65ac1..f6d7cf1 100644 --- a/scripts/02-software.ps1 +++ b/scripts/02-software.ps1 @@ -110,14 +110,12 @@ if ($forcePdf) { } Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd - # Also set in HKCU for current user (UserChoice) - $ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" - if (-not (Test-Path $ucPath)) { - New-Item -Path $ucPath -Force | Out-Null - } - Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId + # Note: HKCU UserChoice requires a Windows-computed Hash value to be valid. + # Direct ProgId write without Hash is silently reset by Windows on next access. + # HKCR write above provides the system-wide default; per-user default is + # handled by the PDF-DefaultApp scheduled task via HKCR on every logon. - Write-Log " PDF default set to AcroRd32" -Level OK + Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK } } diff --git a/scripts/03-system-registry.ps1 b/scripts/03-system-registry.ps1 index e296349..38e6fdb 100644 --- a/scripts/03-system-registry.ps1 +++ b/scripts/03-system-registry.ps1 @@ -307,6 +307,14 @@ Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" ` 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) # ----------------------------------------------------------------------- diff --git a/scripts/06-scheduled-tasks.ps1 b/scripts/06-scheduled-tasks.ps1 index d57182b..fbdfec8 100644 --- a/scripts/06-scheduled-tasks.ps1 +++ b/scripts/06-scheduled-tasks.ps1 @@ -21,7 +21,7 @@ function Register-Task { [string]$TaskName, [string]$Description, [object]$Action, - [object]$Trigger, + [object[]]$Triggers, [string]$RunLevel = "Highest" ) try { @@ -36,7 +36,7 @@ function Register-Task { -RunLevel $RunLevel $task = New-ScheduledTask -Action $Action ` - -Trigger $Trigger ` + -Trigger $Triggers ` -Settings $settings ` -Principal $principal ` -Description $Description @@ -51,29 +51,40 @@ function Register-Task { # ----------------------------------------------------------------------- # Task: ShowAllTrayIcons -# Runs on logon + every 1 minute, sets EnableAutoTray=0 so all tray icons -# are always visible (Win11 hides them by default) +# Runs on logon: clears TrayNotify icon cache and restarts Explorer so all +# tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear) # ----------------------------------------------------------------------- Write-Log "Registering task: ShowAllTrayIcons" -Level STEP $showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1" @' +# Win10: disable auto-hiding of tray icons $regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer" -Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force +Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force -ErrorAction SilentlyContinue + +# Win11: clear icon stream cache so all icons become visible after Explorer restart +$trayPath = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" +if (Test-Path $trayPath) { + Remove-ItemProperty -Path $trayPath -Name "IconStreams" -Force -ErrorAction SilentlyContinue + Remove-ItemProperty -Path $trayPath -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue +} + +# Restart Explorer to apply changes Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue +Start-Sleep -Milliseconds 1500 +if (-not (Get-Process explorer -ErrorAction SilentlyContinue)) { + Start-Process explorer +} '@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force $showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`"" -$showTrayTrigger = @( - $(New-ScheduledTaskTrigger -AtLogOn), - $(New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -Once -At (Get-Date)) -) +$showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn Register-Task -TaskName "ShowAllTrayIcons" ` -Description "Show all system tray icons for current user" ` -Action $showTrayAction ` - -Trigger $showTrayTrigger[0] + -Triggers $showTrayTrigger # ----------------------------------------------------------------------- # Task: PDF-DefaultApp @@ -84,7 +95,10 @@ Write-Log "Registering task: PDF-DefaultApp" -Level STEP $pdfScript = "$ScriptDir\PDF-DefaultApp.ps1" @' -# Restore .pdf -> Adobe Reader association +# Restore .pdf -> Adobe Reader HKCR association (system-wide). +# Runs as SYSTEM so it can write to HKCR regardless of Edge updates. +# Note: HKCU UserChoice requires Windows Hash validation and cannot be +# set reliably via registry; HKCR provides the system-wide fallback. $acroPaths = @( "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe" "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe" @@ -95,27 +109,44 @@ $acroPaths = @( $acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 if (-not $acroExe) { exit 0 } -$progId = "AcroExch.Document.DC" - -# Check current association -$current = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" ` - -Name "ProgId" -ErrorAction SilentlyContinue).ProgId +$progId = "AcroExch.Document.DC" +$openCmd = "`"$acroExe`" `"%1`"" +# HKCR\.pdf +if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null } +$current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)" if ($current -ne $progId) { - $ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" - if (-not (Test-Path $ucPath)) { New-Item -Path $ucPath -Force | Out-Null } - Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId -Force + Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force } + +# HKCR\AcroExch.Document.DC\shell\open\command +$cmdPath = "HKCR:\$progId\shell\open\command" +if (-not (Test-Path $cmdPath)) { New-Item -Path $cmdPath -Force | Out-Null } +Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd -Force '@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force $pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`"" $pdfTrigger = New-ScheduledTaskTrigger -AtLogOn -Register-Task -TaskName "PDF-DefaultApp" ` - -Description "Restore Adobe Reader as default PDF app on logon" ` - -Action $pdfAction ` - -Trigger $pdfTrigger +# Runs as SYSTEM to allow HKCR writes (system-wide file association) +$pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest +$pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) ` + -MultipleInstances IgnoreNew ` + -StartWhenAvailable +$pdfTask = New-ScheduledTask -Action $pdfAction ` + -Trigger $pdfTrigger ` + -Settings $pdfSettings ` + -Principal $pdfPrincipal ` + -Description "Restore Adobe Reader as default PDF app on logon" +try { + Unregister-ScheduledTask -TaskName "PDF-DefaultApp" -Confirm:$false -ErrorAction SilentlyContinue + Register-ScheduledTask -TaskName "PDF-DefaultApp" -InputObject $pdfTask -Force | Out-Null + Write-Log " Registered task: PDF-DefaultApp" -Level OK +} +catch { + Write-Log " Failed to register task PDF-DefaultApp - $_" -Level ERROR +} # ----------------------------------------------------------------------- # Task: UnlockStartLayout