Add config GUI, USB launcher, flash folder; fix bugs

- config-editor.hta: lightweight WYSIWYG HTA editor for config.json
  - Step on/off toggles with info tooltips
  - Editable software list (winget packages)
  - Settings: timezone, admin account, desktopInfo, PDF default
- Run.cmd: USB launcher with UAC auto-elevation and deployment menu
- flash/: minimal USB-ready subset (Deploy, scripts, config, GUI, launcher)
- config.json: add steps section for per-step enable/disable
- Deploy-Windows.ps1: read steps from config, CLI switches override
- 03-system-registry.ps1: add SearchOnTaskbarMode HKLM policy (Win11 search fix)
- 04-default-profile.ps1: fix systray - clear TrayNotify cache + proper Explorer restart
- 06-scheduled-tasks.ps1: fix Register-Task trigger array, ShowAllTrayIcons Win11 fix,
  PDF-DefaultApp runs as SYSTEM via HKCR (bypasses UserChoice Hash validation)
- 02-software.ps1: remove unreliable UserChoice ProgId write without Hash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Filip Zubik 2026-03-16 09:35:42 +01:00
parent 79fcfea8df
commit 80a542252d
20 changed files with 3693 additions and 63 deletions

View file

@ -96,83 +96,117 @@ Invoke-Step -Name "Load config.json" -Action {
Write-Log "Config loaded from $ConfigFile" -Level INFO 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 # Step 0a - Admin account
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['adminAccount']) {
Invoke-Step -Name "Step 0a - Admin account" -Action { Invoke-Step -Name "Step 0a - Admin account" -Action {
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 0a - Admin account" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 0b - Windows activation # Step 0b - Windows activation
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['activation']) {
Invoke-Step -Name "Step 0b - Windows activation" -Action { Invoke-Step -Name "Step 0b - Windows activation" -Action {
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 0b - Windows activation" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 1 - Bloatware removal # Step 1 - Bloatware removal
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($SkipBloatware) { if ($stepsEnabled['bloatware']) {
Write-Log "Step 1 - Bloatware removal: SKIPPED (-SkipBloatware)" -Level WARN
$StepResults.Add(@{ Name = "Step 1 - Bloatware removal"; Status = "SKIPPED" })
} else {
Invoke-Step -Name "Step 1 - Bloatware removal" -Action { Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
} }
} } else { Skip-Step "Step 1 - Bloatware removal" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 2 - Software installation # Step 2 - Software installation
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($SkipSoftware) { if ($stepsEnabled['software']) {
Write-Log "Step 2 - Software installation: SKIPPED (-SkipSoftware)" -Level WARN
$StepResults.Add(@{ Name = "Step 2 - Software installation"; Status = "SKIPPED" })
} else {
Invoke-Step -Name "Step 2 - Software installation" -Action { Invoke-Step -Name "Step 2 - Software installation" -Action {
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
} }
} } else { Skip-Step "Step 2 - Software installation" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 3 - System registry (HKLM) # Step 3 - System registry (HKLM)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['systemRegistry']) {
Invoke-Step -Name "Step 3 - System registry" -Action { Invoke-Step -Name "Step 3 - System registry" -Action {
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 3 - System registry" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 4 - Default profile (NTUSER.DAT) # Step 4 - Default profile (NTUSER.DAT)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($SkipDefaultProfile) { if ($stepsEnabled['defaultProfile']) {
Write-Log "Step 4 - Default profile: SKIPPED (-SkipDefaultProfile)" -Level WARN
$StepResults.Add(@{ Name = "Step 4 - Default profile"; Status = "SKIPPED" })
} else {
Invoke-Step -Name "Step 4 - Default profile" -Action { Invoke-Step -Name "Step 4 - Default profile" -Action {
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile
} }
} } else { Skip-Step "Step 4 - Default profile" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 5 - Personalization # Step 5 - Personalization
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['personalization']) {
Invoke-Step -Name "Step 5 - Personalization" -Action { Invoke-Step -Name "Step 5 - Personalization" -Action {
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 5 - Personalization" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 6 - Scheduled tasks # Step 6 - Scheduled tasks
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['scheduledTasks']) {
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action { Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 6 - Scheduled tasks" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Step 7 - DesktopInfo # Step 7 - DesktopInfo
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if ($stepsEnabled['desktopInfo']) {
Invoke-Step -Name "Step 7 - DesktopInfo" -Action { Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile & "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile
} }
} else { Skip-Step "Step 7 - DesktopInfo" }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Summary # Summary

125
Run.cmd Normal file
View file

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

632
config-editor.hta Normal file
View file

@ -0,0 +1,632 @@
<html>
<head>
<title>X9 - Deployment Config Editor</title>
<HTA:APPLICATION
ID="ConfigEditor"
APPLICATIONNAME="X9 Config Editor"
SCROLL="no"
SINGLEINSTANCE="yes"
WINDOWSTATE="normal"
INNERBORDER="no"
SELECTION="no"
CONTEXTMENU="no"
/>
<meta http-equiv="x-ua-compatible" content="ie=11">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Segoe UI, Arial, sans-serif;
font-size: 13px;
background: #1a2a33;
color: #d0dde3;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
#header {
background: #223B47;
padding: 12px 18px;
border-bottom: 2px solid #2e5568;
flex-shrink: 0;
}
#header h1 {
font-size: 16px;
font-weight: 600;
color: #e8f4f8;
letter-spacing: 0.5px;
}
#header .config-path {
font-size: 11px;
color: #7aabbd;
margin-top: 3px;
word-break: break-all;
}
#tabs {
display: flex;
background: #1a2a33;
border-bottom: 1px solid #2e5568;
flex-shrink: 0;
}
.tab {
padding: 8px 18px;
cursor: pointer;
color: #7aabbd;
border-bottom: 2px solid transparent;
font-size: 12px;
font-weight: 500;
}
.tab:hover { color: #b0d4e0; }
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
#content {
flex: 1;
overflow-y: auto;
padding: 0;
}
.tab-panel { display: none; padding: 16px 18px; }
.tab-panel.active { display: block; }
table { width: 100%; border-collapse: collapse; }
th {
background: #223B47;
color: #9ac8d8;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 7px 10px;
text-align: left;
border-bottom: 1px solid #2e5568;
}
td {
padding: 6px 10px;
border-bottom: 1px solid #1e3340;
vertical-align: middle;
}
tr:hover td { background: #1e3340; }
.step-num {
color: #4a9aba;
font-size: 11px;
font-weight: 600;
width: 36px;
}
.step-name { color: #d0dde3; }
.info-btn {
background: #2e5568;
border: none;
color: #7aabbd;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
font-size: 11px;
font-weight: 700;
line-height: 20px;
text-align: center;
position: relative;
}
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #4a9aba;
}
input[type="text"], select {
background: #1a2a33;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 8px;
border-radius: 3px;
font-size: 12px;
width: 100%;
}
input[type="text"]:focus, select:focus {
outline: none;
border-color: #4a9aba;
}
.label-cell {
width: 160px;
color: #9ac8d8;
font-size: 12px;
font-weight: 500;
padding-top: 10px;
vertical-align: top;
}
.section-title {
color: #4a9aba;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 16px 0 8px 0;
padding-bottom: 4px;
border-bottom: 1px solid #2e5568;
}
.section-title:first-child { margin-top: 0; }
.btn {
background: #223B47;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 12px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn:hover { background: #2e5568; color: #e8f4f8; }
.btn-danger {
background: #3d1f1f;
border-color: #6b2c2c;
color: #d08080;
}
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
.btn-add {
background: #1a3a2a;
border-color: #2e6a4a;
color: #80c8a0;
margin-top: 8px;
}
.btn-add:hover { background: #235a38; color: #a0e0b8; }
#footer {
background: #223B47;
border-top: 2px solid #2e5568;
padding: 10px 18px;
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
#btn-save {
background: #1a5c3a;
border: 1px solid #2a8a58;
color: #80e0b0;
padding: 7px 22px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
}
#btn-save:hover { background: #206e46; color: #a0f0c8; }
#status {
font-size: 12px;
color: #7aabbd;
flex: 1;
}
#tooltip {
position: fixed;
background: #0f1e26;
border: 1px solid #3a7a9a;
color: #c0dde8;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
max-width: 340px;
line-height: 1.5;
z-index: 9999;
display: none;
pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
.settings-grid .span2 { grid-column: 1 / -1; }
</style>
</head>
<body>
<div id="header">
<h1>X9 - Deployment Config</h1>
<div class="config-path" id="config-path-display">Loading...</div>
</div>
<div id="tabs">
<div class="tab active" onclick="showTab('steps')">Steps</div>
<div class="tab" onclick="showTab('software')">Software</div>
<div class="tab" onclick="showTab('settings')">Settings</div>
</div>
<div id="content">
<!-- STEPS TAB -->
<div class="tab-panel active" id="tab-steps">
<table id="steps-table">
<thead>
<tr>
<th style="width:40px;">On</th>
<th style="width:36px;">Step</th>
<th>Name</th>
<th style="width:32px;"></th>
</tr>
</thead>
<tbody id="steps-tbody">
</tbody>
</table>
</div>
<!-- SOFTWARE TAB -->
<div class="tab-panel" id="tab-software">
<table id="software-table">
<thead>
<tr>
<th>Package Name</th>
<th>Winget ID</th>
<th style="width:70px;"></th>
</tr>
</thead>
<tbody id="software-tbody">
</tbody>
</table>
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
</div>
<!-- SETTINGS TAB -->
<div class="tab-panel" id="tab-settings">
<div class="section-title">Deployment</div>
<div class="settings-grid">
<div class="label-cell">Timezone</div>
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
<div class="label-cell">Locale</div>
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
</div>
<div class="section-title">Admin Account</div>
<div class="settings-grid">
<div class="label-cell">Username</div>
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
<div class="label-cell">Password</div>
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
<div class="label-cell">Description</div>
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
</div>
<div class="section-title">PDF Default</div>
<div class="settings-grid">
<div class="label-cell">Force Adobe Reader</div>
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
<div class="label-cell">Scheduled Task</div>
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
</div>
<div class="section-title">Desktop Info</div>
<div class="settings-grid">
<div class="label-cell">Enabled</div>
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
<div class="label-cell">Position</div>
<div>
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
<option value="bottomRight">Bottom Right</option>
<option value="bottomLeft">Bottom Left</option>
<option value="topRight">Top Right</option>
<option value="topLeft">Top Left</option>
<option value="center">Center</option>
</select>
</div>
<div class="label-cell">Font Size</div>
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
<div class="label-cell">Font Color</div>
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
</div>
<div class="section-title">Activation</div>
<div class="settings-grid">
<div class="label-cell">Product Key</div>
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
<div class="label-cell">KMS Server</div>
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
</div>
</div>
</div>
<div id="footer">
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
<div id="status">Ready.</div>
</div>
<div id="tooltip"></div>
<script language="JScript">
var STEP_DEFS = [
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
];
var configPath = "";
var config = null;
var fso = null;
function init() {
try {
fso = new ActiveXObject("Scripting.FileSystemObject");
// Derive config path from HTA location
var htaPath = location.pathname.replace(/\//g, "\\");
// Remove leading backslash from pathname
if (htaPath.charAt(0) === "\\") {
htaPath = htaPath.substring(1);
}
var dir = fso.GetParentFolderName(htaPath);
configPath = dir + "\\config\\config.json";
document.getElementById("config-path-display").innerText = configPath;
loadConfig();
buildStepsTable();
resizeWindow();
} catch(e) {
setStatus("Init error: " + e.message, true);
}
}
function resizeWindow() {
window.resizeTo(800, 720);
window.moveTo(
(screen.width - 800) / 2,
(screen.height - 720) / 2
);
}
function loadConfig() {
try {
if (!fso.FileExists(configPath)) {
setStatus("config.json not found: " + configPath, true);
config = {};
return;
}
var f = fso.OpenTextFile(configPath, 1, false, -1);
var raw = f.ReadAll();
f.Close();
config = JSON.parse(raw);
setStatus("Loaded: " + configPath);
populateSettings();
buildSoftwareTable();
} catch(e) {
setStatus("Load error: " + e.message, true);
config = {};
}
}
function buildStepsTable() {
var tbody = document.getElementById("steps-tbody");
var html = "";
for (var i = 0; i < STEP_DEFS.length; i++) {
var s = STEP_DEFS[i];
var checked = "";
// Check config.steps if loaded, default true
if (config && config.steps && config.steps[s.key] === false) {
checked = "";
} else {
checked = "checked";
}
html += "<tr>";
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
html += "<td class='step-num'>" + s.num + "</td>";
html += "<td class='step-name'>" + s.label + "</td>";
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
html += "</tr>";
}
tbody.innerHTML = html;
}
function buildSoftwareTable() {
var tbody = document.getElementById("software-tbody");
var html = "";
if (config && config.software && config.software.install) {
var list = config.software.install;
for (var i = 0; i < list.length; i++) {
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
}
}
tbody.innerHTML = html;
}
function makeSoftwareRow(idx, name, wingetId) {
return "<tr id='sw-row-" + idx + "'>" +
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
"</tr>";
}
function addSoftwareRow() {
if (!config.software) { config.software = {}; }
if (!config.software.install) { config.software.install = []; }
config.software.install.push({ name: "", wingetId: "" });
buildSoftwareTable();
setStatus("Package added. Fill in name and Winget ID, then save.");
}
function removeSoftwareRow(idx) {
if (!config || !config.software || !config.software.install) { return; }
config.software.install.splice(idx, 1);
buildSoftwareTable();
setStatus("Package removed. Click Save to persist.");
}
function updateSoftwareName(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].name = val;
}
}
function updateSoftwareId(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].wingetId = val;
}
}
function populateSettings() {
if (!config) { return; }
if (config.deployment) {
setVal("s-timezone", config.deployment.timezone || "");
setVal("s-locale", config.deployment.locale || "");
}
if (config.adminAccount) {
setVal("s-admin-user", config.adminAccount.username || "");
setVal("s-admin-pass", config.adminAccount.password || "");
setVal("s-admin-desc", config.adminAccount.description || "");
}
if (config.pdfDefault) {
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
}
if (config.desktopInfo) {
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
}
if (config.activation) {
setVal("s-act-key", config.activation.productKey || "");
setVal("s-act-kms", config.activation.kmsServer || "");
}
}
function setVal(id, val) {
var el = document.getElementById(id);
if (el) { el.value = val; }
}
function setChk(id, val) {
var el = document.getElementById(id);
if (el) { el.checked = !!val; }
}
function setSelVal(id, val) {
var el = document.getElementById(id);
if (!el) { return; }
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].value === val) {
el.selectedIndex = i;
break;
}
}
}
function toggleStep(key, enabled) {
if (!config) { return; }
if (!config.steps) { config.steps = {}; }
config.steps[key] = enabled;
}
function updateSetting(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = val;
}
function updateSettingBool(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = !!val;
}
function updateSettingInt(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
var n = parseInt(val, 10);
config[section][key] = isNaN(n) ? val : n;
}
function saveConfig() {
if (!config) { setStatus("No config loaded.", true); return; }
try {
// Sync steps checkboxes before save (in case table was rebuilt)
syncStepsFromTable();
var json = JSON.stringify(config, null, 2);
var f = fso.CreateTextFile(configPath, true, true);
f.Write(json);
f.Close();
setStatus("Saved: " + configPath + " [" + now() + "]");
} catch(e) {
setStatus("Save error: " + e.message, true);
}
}
function syncStepsFromTable() {
if (!config.steps) { config.steps = {}; }
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
var chk = rows[i].getElementsByTagName("input")[0];
if (chk && STEP_DEFS[i]) {
config.steps[STEP_DEFS[i].key] = chk.checked;
}
}
}
function showTab(name) {
var panels = document.getElementsByClassName("tab-panel");
for (var i = 0; i < panels.length; i++) {
panels[i].className = "tab-panel";
}
var tabs = document.getElementsByClassName("tab");
for (var i = 0; i < tabs.length; i++) {
tabs[i].className = "tab";
}
document.getElementById("tab-" + name).className = "tab-panel active";
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
var nameMap = { steps: 0, software: 1, settings: 2 };
if (nameMap[name] !== undefined) {
allTabs[nameMap[name]].className = "tab active";
}
}
function showTooltip(evt, text) {
var t = document.getElementById("tooltip");
t.innerText = text;
t.style.display = "block";
positionTooltip(evt);
}
function positionTooltip(evt) {
var t = document.getElementById("tooltip");
var x = evt.clientX + 12;
var y = evt.clientY + 12;
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
t.style.left = x + "px";
t.style.top = y + "px";
}
function hideTooltip() {
document.getElementById("tooltip").style.display = "none";
}
function setStatus(msg, isError) {
var el = document.getElementById("status");
el.innerText = msg;
el.style.color = isError ? "#d08080" : "#7aabbd";
}
function now() {
var d = new Date();
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
}
function pad(n) { return n < 10 ? "0" + n : String(n); }
function escapeQ(s) {
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "&quot;");
}
window.onload = function() { init(); };
</script>
</body>
</html>

View file

@ -1,4 +1,15 @@
{ {
"steps": {
"adminAccount": true,
"bloatware": true,
"software": true,
"systemRegistry": true,
"defaultProfile": true,
"personalization": true,
"scheduledTasks": true,
"desktopInfo": true,
"activation": true
},
"deployment": { "deployment": {
"timezone": "Central Europe Standard Time", "timezone": "Central Europe Standard Time",
"locale": "cs-CZ" "locale": "cs-CZ"

244
flash/Deploy-Windows.ps1 Normal file
View file

@ -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
}

125
flash/Run.cmd Normal file
View file

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

632
flash/config-editor.hta Normal file
View file

@ -0,0 +1,632 @@
<html>
<head>
<title>X9 - Deployment Config Editor</title>
<HTA:APPLICATION
ID="ConfigEditor"
APPLICATIONNAME="X9 Config Editor"
SCROLL="no"
SINGLEINSTANCE="yes"
WINDOWSTATE="normal"
INNERBORDER="no"
SELECTION="no"
CONTEXTMENU="no"
/>
<meta http-equiv="x-ua-compatible" content="ie=11">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Segoe UI, Arial, sans-serif;
font-size: 13px;
background: #1a2a33;
color: #d0dde3;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
#header {
background: #223B47;
padding: 12px 18px;
border-bottom: 2px solid #2e5568;
flex-shrink: 0;
}
#header h1 {
font-size: 16px;
font-weight: 600;
color: #e8f4f8;
letter-spacing: 0.5px;
}
#header .config-path {
font-size: 11px;
color: #7aabbd;
margin-top: 3px;
word-break: break-all;
}
#tabs {
display: flex;
background: #1a2a33;
border-bottom: 1px solid #2e5568;
flex-shrink: 0;
}
.tab {
padding: 8px 18px;
cursor: pointer;
color: #7aabbd;
border-bottom: 2px solid transparent;
font-size: 12px;
font-weight: 500;
}
.tab:hover { color: #b0d4e0; }
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
#content {
flex: 1;
overflow-y: auto;
padding: 0;
}
.tab-panel { display: none; padding: 16px 18px; }
.tab-panel.active { display: block; }
table { width: 100%; border-collapse: collapse; }
th {
background: #223B47;
color: #9ac8d8;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 7px 10px;
text-align: left;
border-bottom: 1px solid #2e5568;
}
td {
padding: 6px 10px;
border-bottom: 1px solid #1e3340;
vertical-align: middle;
}
tr:hover td { background: #1e3340; }
.step-num {
color: #4a9aba;
font-size: 11px;
font-weight: 600;
width: 36px;
}
.step-name { color: #d0dde3; }
.info-btn {
background: #2e5568;
border: none;
color: #7aabbd;
width: 20px;
height: 20px;
border-radius: 50%;
cursor: pointer;
font-size: 11px;
font-weight: 700;
line-height: 20px;
text-align: center;
position: relative;
}
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
accent-color: #4a9aba;
}
input[type="text"], select {
background: #1a2a33;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 8px;
border-radius: 3px;
font-size: 12px;
width: 100%;
}
input[type="text"]:focus, select:focus {
outline: none;
border-color: #4a9aba;
}
.label-cell {
width: 160px;
color: #9ac8d8;
font-size: 12px;
font-weight: 500;
padding-top: 10px;
vertical-align: top;
}
.section-title {
color: #4a9aba;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin: 16px 0 8px 0;
padding-bottom: 4px;
border-bottom: 1px solid #2e5568;
}
.section-title:first-child { margin-top: 0; }
.btn {
background: #223B47;
border: 1px solid #2e5568;
color: #d0dde3;
padding: 5px 12px;
border-radius: 3px;
cursor: pointer;
font-size: 12px;
}
.btn:hover { background: #2e5568; color: #e8f4f8; }
.btn-danger {
background: #3d1f1f;
border-color: #6b2c2c;
color: #d08080;
}
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
.btn-add {
background: #1a3a2a;
border-color: #2e6a4a;
color: #80c8a0;
margin-top: 8px;
}
.btn-add:hover { background: #235a38; color: #a0e0b8; }
#footer {
background: #223B47;
border-top: 2px solid #2e5568;
padding: 10px 18px;
display: flex;
align-items: center;
gap: 14px;
flex-shrink: 0;
}
#btn-save {
background: #1a5c3a;
border: 1px solid #2a8a58;
color: #80e0b0;
padding: 7px 22px;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
}
#btn-save:hover { background: #206e46; color: #a0f0c8; }
#status {
font-size: 12px;
color: #7aabbd;
flex: 1;
}
#tooltip {
position: fixed;
background: #0f1e26;
border: 1px solid #3a7a9a;
color: #c0dde8;
padding: 8px 12px;
border-radius: 4px;
font-size: 12px;
max-width: 340px;
line-height: 1.5;
z-index: 9999;
display: none;
pointer-events: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
.settings-grid .span2 { grid-column: 1 / -1; }
</style>
</head>
<body>
<div id="header">
<h1>X9 - Deployment Config</h1>
<div class="config-path" id="config-path-display">Loading...</div>
</div>
<div id="tabs">
<div class="tab active" onclick="showTab('steps')">Steps</div>
<div class="tab" onclick="showTab('software')">Software</div>
<div class="tab" onclick="showTab('settings')">Settings</div>
</div>
<div id="content">
<!-- STEPS TAB -->
<div class="tab-panel active" id="tab-steps">
<table id="steps-table">
<thead>
<tr>
<th style="width:40px;">On</th>
<th style="width:36px;">Step</th>
<th>Name</th>
<th style="width:32px;"></th>
</tr>
</thead>
<tbody id="steps-tbody">
</tbody>
</table>
</div>
<!-- SOFTWARE TAB -->
<div class="tab-panel" id="tab-software">
<table id="software-table">
<thead>
<tr>
<th>Package Name</th>
<th>Winget ID</th>
<th style="width:70px;"></th>
</tr>
</thead>
<tbody id="software-tbody">
</tbody>
</table>
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
</div>
<!-- SETTINGS TAB -->
<div class="tab-panel" id="tab-settings">
<div class="section-title">Deployment</div>
<div class="settings-grid">
<div class="label-cell">Timezone</div>
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
<div class="label-cell">Locale</div>
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
</div>
<div class="section-title">Admin Account</div>
<div class="settings-grid">
<div class="label-cell">Username</div>
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
<div class="label-cell">Password</div>
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
<div class="label-cell">Description</div>
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
</div>
<div class="section-title">PDF Default</div>
<div class="settings-grid">
<div class="label-cell">Force Adobe Reader</div>
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
<div class="label-cell">Scheduled Task</div>
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
</div>
<div class="section-title">Desktop Info</div>
<div class="settings-grid">
<div class="label-cell">Enabled</div>
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
<div class="label-cell">Position</div>
<div>
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
<option value="bottomRight">Bottom Right</option>
<option value="bottomLeft">Bottom Left</option>
<option value="topRight">Top Right</option>
<option value="topLeft">Top Left</option>
<option value="center">Center</option>
</select>
</div>
<div class="label-cell">Font Size</div>
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
<div class="label-cell">Font Color</div>
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
</div>
<div class="section-title">Activation</div>
<div class="settings-grid">
<div class="label-cell">Product Key</div>
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
<div class="label-cell">KMS Server</div>
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
</div>
</div>
</div>
<div id="footer">
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
<div id="status">Ready.</div>
</div>
<div id="tooltip"></div>
<script language="JScript">
var STEP_DEFS = [
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
];
var configPath = "";
var config = null;
var fso = null;
function init() {
try {
fso = new ActiveXObject("Scripting.FileSystemObject");
// Derive config path from HTA location
var htaPath = location.pathname.replace(/\//g, "\\");
// Remove leading backslash from pathname
if (htaPath.charAt(0) === "\\") {
htaPath = htaPath.substring(1);
}
var dir = fso.GetParentFolderName(htaPath);
configPath = dir + "\\config\\config.json";
document.getElementById("config-path-display").innerText = configPath;
loadConfig();
buildStepsTable();
resizeWindow();
} catch(e) {
setStatus("Init error: " + e.message, true);
}
}
function resizeWindow() {
window.resizeTo(800, 720);
window.moveTo(
(screen.width - 800) / 2,
(screen.height - 720) / 2
);
}
function loadConfig() {
try {
if (!fso.FileExists(configPath)) {
setStatus("config.json not found: " + configPath, true);
config = {};
return;
}
var f = fso.OpenTextFile(configPath, 1, false, -1);
var raw = f.ReadAll();
f.Close();
config = JSON.parse(raw);
setStatus("Loaded: " + configPath);
populateSettings();
buildSoftwareTable();
} catch(e) {
setStatus("Load error: " + e.message, true);
config = {};
}
}
function buildStepsTable() {
var tbody = document.getElementById("steps-tbody");
var html = "";
for (var i = 0; i < STEP_DEFS.length; i++) {
var s = STEP_DEFS[i];
var checked = "";
// Check config.steps if loaded, default true
if (config && config.steps && config.steps[s.key] === false) {
checked = "";
} else {
checked = "checked";
}
html += "<tr>";
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
html += "<td class='step-num'>" + s.num + "</td>";
html += "<td class='step-name'>" + s.label + "</td>";
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
html += "</tr>";
}
tbody.innerHTML = html;
}
function buildSoftwareTable() {
var tbody = document.getElementById("software-tbody");
var html = "";
if (config && config.software && config.software.install) {
var list = config.software.install;
for (var i = 0; i < list.length; i++) {
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
}
}
tbody.innerHTML = html;
}
function makeSoftwareRow(idx, name, wingetId) {
return "<tr id='sw-row-" + idx + "'>" +
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
"</tr>";
}
function addSoftwareRow() {
if (!config.software) { config.software = {}; }
if (!config.software.install) { config.software.install = []; }
config.software.install.push({ name: "", wingetId: "" });
buildSoftwareTable();
setStatus("Package added. Fill in name and Winget ID, then save.");
}
function removeSoftwareRow(idx) {
if (!config || !config.software || !config.software.install) { return; }
config.software.install.splice(idx, 1);
buildSoftwareTable();
setStatus("Package removed. Click Save to persist.");
}
function updateSoftwareName(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].name = val;
}
}
function updateSoftwareId(idx, val) {
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
config.software.install[idx].wingetId = val;
}
}
function populateSettings() {
if (!config) { return; }
if (config.deployment) {
setVal("s-timezone", config.deployment.timezone || "");
setVal("s-locale", config.deployment.locale || "");
}
if (config.adminAccount) {
setVal("s-admin-user", config.adminAccount.username || "");
setVal("s-admin-pass", config.adminAccount.password || "");
setVal("s-admin-desc", config.adminAccount.description || "");
}
if (config.pdfDefault) {
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
}
if (config.desktopInfo) {
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
}
if (config.activation) {
setVal("s-act-key", config.activation.productKey || "");
setVal("s-act-kms", config.activation.kmsServer || "");
}
}
function setVal(id, val) {
var el = document.getElementById(id);
if (el) { el.value = val; }
}
function setChk(id, val) {
var el = document.getElementById(id);
if (el) { el.checked = !!val; }
}
function setSelVal(id, val) {
var el = document.getElementById(id);
if (!el) { return; }
for (var i = 0; i < el.options.length; i++) {
if (el.options[i].value === val) {
el.selectedIndex = i;
break;
}
}
}
function toggleStep(key, enabled) {
if (!config) { return; }
if (!config.steps) { config.steps = {}; }
config.steps[key] = enabled;
}
function updateSetting(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = val;
}
function updateSettingBool(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
config[section][key] = !!val;
}
function updateSettingInt(section, key, val) {
if (!config) { return; }
if (!config[section]) { config[section] = {}; }
var n = parseInt(val, 10);
config[section][key] = isNaN(n) ? val : n;
}
function saveConfig() {
if (!config) { setStatus("No config loaded.", true); return; }
try {
// Sync steps checkboxes before save (in case table was rebuilt)
syncStepsFromTable();
var json = JSON.stringify(config, null, 2);
var f = fso.CreateTextFile(configPath, true, true);
f.Write(json);
f.Close();
setStatus("Saved: " + configPath + " [" + now() + "]");
} catch(e) {
setStatus("Save error: " + e.message, true);
}
}
function syncStepsFromTable() {
if (!config.steps) { config.steps = {}; }
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
for (var i = 0; i < rows.length; i++) {
var chk = rows[i].getElementsByTagName("input")[0];
if (chk && STEP_DEFS[i]) {
config.steps[STEP_DEFS[i].key] = chk.checked;
}
}
}
function showTab(name) {
var panels = document.getElementsByClassName("tab-panel");
for (var i = 0; i < panels.length; i++) {
panels[i].className = "tab-panel";
}
var tabs = document.getElementsByClassName("tab");
for (var i = 0; i < tabs.length; i++) {
tabs[i].className = "tab";
}
document.getElementById("tab-" + name).className = "tab-panel active";
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
var nameMap = { steps: 0, software: 1, settings: 2 };
if (nameMap[name] !== undefined) {
allTabs[nameMap[name]].className = "tab active";
}
}
function showTooltip(evt, text) {
var t = document.getElementById("tooltip");
t.innerText = text;
t.style.display = "block";
positionTooltip(evt);
}
function positionTooltip(evt) {
var t = document.getElementById("tooltip");
var x = evt.clientX + 12;
var y = evt.clientY + 12;
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
t.style.left = x + "px";
t.style.top = y + "px";
}
function hideTooltip() {
document.getElementById("tooltip").style.display = "none";
}
function setStatus(msg, isError) {
var el = document.getElementById("status");
el.innerText = msg;
el.style.color = isError ? "#d08080" : "#7aabbd";
}
function now() {
var d = new Date();
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
}
function pad(n) { return n < 10 ? "0" + n : String(n); }
function escapeQ(s) {
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, "&quot;");
}
window.onload = function() { init(); };
</script>
</body>
</html>

49
flash/config/config.json Normal file
View file

@ -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
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = @"
<?xml version="1.0" encoding="utf-8"?>
<LayoutModificationTemplate
xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
Version="1">
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
<defaultlayout:TaskbarLayout>
<taskbar:TaskbarPinList>
</taskbar:TaskbarPinList>
</defaultlayout:TaskbarLayout>
</CustomTaskbarLayoutCollection>
</LayoutModificationTemplate>
"@
$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

View file

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

View file

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

View file

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

View file

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

View file

@ -110,14 +110,12 @@ if ($forcePdf) {
} }
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
# Also set in HKCU for current user (UserChoice) # Note: HKCU UserChoice requires a Windows-computed Hash value to be valid.
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" # Direct ProgId write without Hash is silently reset by Windows on next access.
if (-not (Test-Path $ucPath)) { # HKCR write above provides the system-wide default; per-user default is
New-Item -Path $ucPath -Force | Out-Null # handled by the PDF-DefaultApp scheduled task via HKCR on every logon.
}
Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId
Write-Log " PDF default set to AcroRd32" -Level OK Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK
} }
} }

View file

@ -307,6 +307,14 @@ Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" ` Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
-Name "DisableAIDataAnalysis" -Value 1 -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) # Start menu - hide Recommended section (Win11)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------

View file

@ -21,7 +21,7 @@ function Register-Task {
[string]$TaskName, [string]$TaskName,
[string]$Description, [string]$Description,
[object]$Action, [object]$Action,
[object]$Trigger, [object[]]$Triggers,
[string]$RunLevel = "Highest" [string]$RunLevel = "Highest"
) )
try { try {
@ -36,7 +36,7 @@ function Register-Task {
-RunLevel $RunLevel -RunLevel $RunLevel
$task = New-ScheduledTask -Action $Action ` $task = New-ScheduledTask -Action $Action `
-Trigger $Trigger ` -Trigger $Triggers `
-Settings $settings ` -Settings $settings `
-Principal $principal ` -Principal $principal `
-Description $Description -Description $Description
@ -51,29 +51,40 @@ function Register-Task {
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Task: ShowAllTrayIcons # Task: ShowAllTrayIcons
# Runs on logon + every 1 minute, sets EnableAutoTray=0 so all tray icons # Runs on logon: clears TrayNotify icon cache and restarts Explorer so all
# are always visible (Win11 hides them by default) # tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1" $showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
@' @'
# Win10: disable auto-hiding of tray icons
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer" $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 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 '@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" ` $showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`"" -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
$showTrayTrigger = @( $showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn
$(New-ScheduledTaskTrigger -AtLogOn),
$(New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -Once -At (Get-Date))
)
Register-Task -TaskName "ShowAllTrayIcons" ` Register-Task -TaskName "ShowAllTrayIcons" `
-Description "Show all system tray icons for current user" ` -Description "Show all system tray icons for current user" `
-Action $showTrayAction ` -Action $showTrayAction `
-Trigger $showTrayTrigger[0] -Triggers $showTrayTrigger
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Task: PDF-DefaultApp # Task: PDF-DefaultApp
@ -84,7 +95,10 @@ Write-Log "Registering task: PDF-DefaultApp" -Level STEP
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1" $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 = @( $acroPaths = @(
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe" "$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe" "${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
@ -96,26 +110,43 @@ $acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $acroExe) { exit 0 } if (-not $acroExe) { exit 0 }
$progId = "AcroExch.Document.DC" $progId = "AcroExch.Document.DC"
$openCmd = "`"$acroExe`" `"%1`""
# Check current association # HKCR\.pdf
$current = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" ` if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null }
-Name "ProgId" -ErrorAction SilentlyContinue).ProgId $current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)"
if ($current -ne $progId) { if ($current -ne $progId) {
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force
if (-not (Test-Path $ucPath)) { New-Item -Path $ucPath -Force | Out-Null }
Set-ItemProperty -Path $ucPath -Name "ProgId" -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 '@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" ` $pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`"" -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn $pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
Register-Task -TaskName "PDF-DefaultApp" ` # Runs as SYSTEM to allow HKCR writes (system-wide file association)
-Description "Restore Adobe Reader as default PDF app on logon" ` $pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
-Action $pdfAction ` $pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
-Trigger $pdfTrigger -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 # Task: UnlockStartLayout