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
+
+
+
+
+
+
+
+
+
+ | On |
+ Step |
+ Name |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ | Package Name |
+ Winget 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
+
+
+
+
+
+
+
+
+
+ | On |
+ Step |
+ Name |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ | Package Name |
+ Winget 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