Compare commits

...

10 commits

Author SHA1 Message Date
Filip Zubik
fe63de3ed7 fix: clear wallpaper cache and add logging to DesktopInfo render
Windows reuses TranscodedWallpaper cache and ignores updated BMP
if the path stays the same. Clear cache before SystemParametersInfo
so wallpaper always reloads.

Add per-run logging to desktopinfo.log for diagnostics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 14:33:47 +01:00
Filip Zubik
602e51aa5b fix: add 20s logon delay to DesktopInfo task
Without delay the task fires before network init, causing
Get-NetIPAddress to return nothing and IP showing as N/A.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 13:23:41 +01:00
Filip Zubik
926ca301b3 feat: redesign DesktopInfo to match BackInfo layout
Centered block on desktop: hostname large bold (36pt), then detail
lines in Segoe UI 14pt - user, OS (bold), CPU+RAM on one line,
IPs+domain on one line. Collects CPU count/speed, total RAM, all
IPv4 addresses, and domain/workgroup. Background #556364.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 05:59:20 +01:00
Filip Zubik
6d5d6083ff fix: keep mstsc (RDP client) enabled - not bloatware
Microsoft-RemoteDesktopConnection is required for Remote Desktop
connections and must not be disabled on business machines.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:09:20 +01:00
Filip Zubik
e78b6d23b8 fix: remove OneDrive policy block to allow M365 reinstall
DisableFileSyncNGSC=1 prevented OneDrive from launching after M365
installation. Keep uninstall for clean PCs but drop the policy key so
Office 365 can reinstall and run OneDrive without restrictions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-23 16:03:38 +01:00
Filip Zubik
7db5b4d1e8 Add jak_na_to.txt usage guide to flash folder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 09:40:48 +01:00
Filip Zubik
80a542252d 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>
2026-03-16 09:35:42 +01:00
X9
79fcfea8df Fix search box and systray for Win10/Win11 compatibility
- Add Search subkey for Win10 search box hiding
- Clear TrayNotify icon streams as Win11 systray workaround
- Restart Explorer to apply taskbar changes in current session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 21:46:40 +01:00
X9
29e25d5905 Add This PC desktop icon, hide Start menu recommendations, remove LinkedIn
- 04-default-profile.ps1 + 05-personalization.ps1: show This PC icon on
  desktop via HideDesktopIcons CLSID {20D04FE0...} = 0
- 03-system-registry.ps1: HideRecommendedSection = 1 hides Win11 Start
  menu Recommended section (HKLM policy)
- 04-default-profile.ps1: Start_TrackProgs = 0 and Start_TrackDocs = 0
  hide recently added/opened items from Start menu
- 01-bloatware.ps1: add 7EE7776C.LinkedInforWindows to removal list
- tests/Test-Deployment.ps1: add checks for all three new settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 18:24:47 +01:00
X9
3a3513c3bc Fix TrustedInstaller-owned registry key write via token privileges
- 03-system-registry.ps1: replace .NET OpenSubKey approach with proper
  P/Invoke that enables SeTakeOwnershipPrivilege and SeRestorePrivilege
  before attempting to take ownership of TrustedInstaller-owned keys
  (e.g. HKLM\...\Communications\ConfigureChatAutoInstall)
- Remove SYSTEM scheduled task fallback (not needed with token approach)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 20:06:01 +01:00
26 changed files with 4028 additions and 174 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
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 0a - Admin account" -Action { if ($stepsEnabled['adminAccount']) {
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile 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 # Step 0b - Windows activation
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 0b - Windows activation" -Action { if ($stepsEnabled['activation']) {
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile 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 # 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)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 3 - System registry" -Action { if ($stepsEnabled['systemRegistry']) {
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile 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) # 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
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 5 - Personalization" -Action { if ($stepsEnabled['personalization']) {
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile 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 # Step 6 - Scheduled tasks
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action { if ($stepsEnabled['scheduledTasks']) {
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile 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 # Step 7 - DesktopInfo
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Invoke-Step -Name "Step 7 - DesktopInfo" -Action { if ($stepsEnabled['desktopInfo']) {
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
} & "$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
}
}

88
flash/jak_na_to.txt Normal file
View file

@ -0,0 +1,88 @@
X9 - Windows Deployment
========================
Automatizovana priprava noveho Windows 10/11 pocitace pro klienta.
Spustit jednou jako Administrator na ciste nainstalovanem Windows.
CO JE NA FLASHCE
----------------
Run.cmd - SPUSTIT TOTO. Hlavni spoustec s menu.
config-editor.hta - Graficky editor konfigurace (dvojklik).
Deploy-Windows.ps1 - Hlavni deployment skript (spousti Run.cmd).
config\config.json - Konfigurace deploymentu.
scripts\ - Jednotlive kroky deploymentu (00-08).
JAK NA TO
---------
1. Zapojit flashku do pocitace.
2. Spustit Run.cmd pravym tlacitkem > "Spustit jako spravce".
(nebo dvojklik - skript si sam vyziada adminova prava)
3. Zobrazit se menu:
[1] Full deployment - spusti vsechny kroky dle config.json
[2] Dry run - projde vsechny kroky BEZ zmen, jen loguje
[3] Skip bloatware - deployment bez odstranovani aplikaci
[4] Skip software - deployment bez instalace softwaru
[5] Open config editor - otevre graficky editor konfigurace
[0] Exit
4. Vybrat [1] pro standardni deployment.
5. Po dokonceni zkontrolovat log:
C:\Windows\Setup\Scripts\Deploy.log
PRED DEPLOYMENTEM (volitelne)
------------------------------
Otevrit config-editor.hta (dvojklik) a zkontrolovat / upravit:
Zalozka STEPS - zapnout / vypnout jednotlive kroky
(kliknout na "i" pro popis co krok dela)
Zalozka SOFTWARE - seznam aplikaci instalovanych pres winget
(pridat / odebrat balicky)
Zalozka SETTINGS - casova zona, nazev admin uctu,
nastaveni DesktopInfo wallpaperu
Po uprave kliknout "Save config.json". Zmeny se projeji pri
spusteni deploymentu.
CO DEPLOYMENT DELA (kroky v poradí)
-------------------------------------
00 Admin account - vytvori lokalni admin ucet pro MSP pristup
01 Bloatware - odebere nepotrebne aplikace (Teams, Xbox, ...)
02 Software - nainstaluje aplikace pres winget
03 System registry - HKLM tweaky (widgety, OneDrive, Edge, ...)
04 Default profile - nastaveni taskbaru a Exploreru pro vsechny uzivatele
05 Personalization - tmave tema, akcetni barva #223B47
06 Scheduled tasks - registruje 4 ulohy (systray, PDF, DesktopInfo, ...)
07 DesktopInfo - vlastni tapeta s info o pocitaci (jmeno, IP, OS, ...)
08 Activation - aktivace Windows
LOG
---
Kazdy krok zapisuje do:
C:\Windows\Setup\Scripts\Deploy.log
Format zaznamu:
[HH:mm:ss] [OK/ERROR/WARN] zprava
Na konci logu je souhrn: pocet OK / ERROR / SKIPPED.
POZNAMKY
--------
- Skript vyzaduje pripojeni k internetu (winget instalace).
- Restart pocitace neni potreba - spoustec na to upozorni pokud ano.
- Nastaveni z Default Profile se aplikuji na KAZDEHO noveho uzivatele.
- DesktopInfo tapeta se obnovi pri kazdem prihlaseni.
- PDF default (Adobe Reader) se obnovuje pri kazdem prihlaseni jako ochrana
proti tomu, ze Edge prepise asociaci.
X9.cz MSP deployment suite

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,184 @@
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"
"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,322 @@
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 - uninstall from clean Windows (no policy block)
# NOTE: No policy key is set intentionally - M365 installation can reinstall
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
# -----------------------------------------------------------------------
Write-Log " Uninstalling OneDrive" -Level INFO
# Remove OneDriveSetup.exe if present
$oneDrivePaths = @(
"$env:SystemRoot\System32\OneDriveSetup.exe"
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
)
foreach ($odPath in $oneDrivePaths) {
if (Test-Path $odPath) {
try {
# Uninstall first
& $odPath /uninstall 2>&1 | Out-Null
Write-Log " OneDrive uninstalled via $odPath" -Level OK
}
catch {
Write-Log " OneDrive uninstall failed: $_" -Level WARN
}
}
}
# Remove OneDrive Start Menu shortcut
$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
if (Test-Path $odLnk) {
Remove-Item $odLnk -Force -ErrorAction SilentlyContinue
Write-Log " Removed OneDrive Start Menu shortcut" -Level OK
}
# -----------------------------------------------------------------------
# Outlook (new) - disable auto-install via UScheduler
# -----------------------------------------------------------------------
Write-Log " Disabling Outlook (new) auto-install" -Level INFO
$uschedulerPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate"
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate"
)
foreach ($uPath in $uschedulerPaths) {
if (Test-Path $uPath) {
try {
Remove-Item -Path $uPath -Recurse -Force
Write-Log " Removed UScheduler key: $uPath" -Level OK
}
catch {
Write-Log " Failed to remove UScheduler key: $_" -Level WARN
}
}
}
# -----------------------------------------------------------------------
# Disable GameDVR
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
-Name "AllowGameDVR" -Value 0
# -----------------------------------------------------------------------
# Disable Recall (Windows AI feature)
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
-Name "DisableAIDataAnalysis" -Value 1
# -----------------------------------------------------------------------
# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
# this policy key ensures the setting survives Windows Updates.
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
-Name "SearchOnTaskbarMode" -Value 0
# -----------------------------------------------------------------------
# Start menu - hide Recommended section (Win11)
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
-Name "HideRecommendedSection" -Value 1
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,235 @@
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
}
# -----------------------------------------------------------------------
# Write the rendering script (runs on every logon as the user)
# Layout: hostname (large bold, centered), then detail lines centered
# -----------------------------------------------------------------------
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
$renderContent = @'
# DesktopInfo-Render.ps1
# Collects system info and renders it centered on the desktop wallpaper.
# Runs on every user logon via Scheduled Task.
$ErrorActionPreference = "Continue"
$LogFile = "C:\Windows\Setup\Scripts\desktopinfo.log"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" -Encoding UTF8
}
Write-Log "DesktopInfo render started" -Level INFO
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Windows.Forms
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
# -----------------------------------------------------------------------
Write-Log "Collecting system info"
$hostname = $env:COMPUTERNAME
$userDomain = $env:USERDOMAIN
$userName = $env:USERNAME
$loggedUser = if ($userDomain -and $userDomain -ne $hostname) { "$userDomain\$userName" } else { "$hostname\$userName" }
$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
$osName = if ($osInfo) { $osInfo.Caption -replace "^Microsoft\s*", "" } else { "Windows" }
$ramGB = if ($osInfo) { [math]::Round($osInfo.TotalVisibleMemorySize / 1024 / 1024, 1) } else { "?" }
$cpuInfo = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
$cpuCount = if ($cpuInfo) { $cpuInfo.NumberOfLogicalProcessors } else { "?" }
$cpuSpeed = if ($cpuInfo) { $cpuInfo.MaxClockSpeed } else { "?" }
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } |
Select-Object -ExpandProperty IPAddress) -join ", "
if (-not $ips) { $ips = "N/A" }
$csInfo = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
$domain = if ($csInfo -and $csInfo.PartOfDomain) { $csInfo.Domain } `
elseif ($csInfo -and $csInfo.Workgroup) { $csInfo.Workgroup.ToLower() } `
else { "N/A" }
Write-Log "hostname=$hostname user=$loggedUser os=$osName ram=$($ramGB)GB cpu=${cpuCount}x${cpuSpeed}MHz ips=$ips domain=$domain"
# -----------------------------------------------------------------------
# Screen dimensions
# -----------------------------------------------------------------------
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
$width = if ($screen) { $screen.Bounds.Width } else { 1920 }
$height = if ($screen) { $screen.Bounds.Height } else { 1080 }
Write-Log "screen=${width}x${height}"
# -----------------------------------------------------------------------
# Create bitmap and graphics context
# -----------------------------------------------------------------------
$bmp = New-Object System.Drawing.Bitmap($width, $height)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
$g.Clear([System.Drawing.ColorTranslator]::FromHtml("#556364"))
# -----------------------------------------------------------------------
# Fonts and brushes
# -----------------------------------------------------------------------
$fontName = "Segoe UI"
$fontTitle = New-Object System.Drawing.Font($fontName, 36, [System.Drawing.FontStyle]::Bold)
$fontBold = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Bold)
$fontReg = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Regular)
$brushWhite = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
$brushGray = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("#C8D2D2"))
# -----------------------------------------------------------------------
# Lines: text, font, brush
# -----------------------------------------------------------------------
$texts = @(
$hostname
"Logged on user: $loggedUser"
"OS: $osName"
"CPU: $cpuCount at $cpuSpeed MHz RAM: $($ramGB)GB"
"IPv4 address: $ips Machine domain: $domain"
)
$fonts = @( $fontTitle, $fontReg, $fontBold, $fontReg, $fontReg )
$brushes = @( $brushWhite, $brushGray, $brushGray, $brushGray, $brushGray )
# -----------------------------------------------------------------------
# Measure total block height, then center vertically
# -----------------------------------------------------------------------
$lineSpacing = 8
$heights = @()
for ($i = 0; $i -lt $texts.Count; $i++) {
$heights += [int]($g.MeasureString($texts[$i], $fonts[$i]).Height)
}
$totalH = ($heights | Measure-Object -Sum).Sum + $lineSpacing * ($texts.Count - 1)
$currentY = [int](($height - $totalH) / 2)
# -----------------------------------------------------------------------
# Draw each line centered horizontally
# -----------------------------------------------------------------------
for ($i = 0; $i -lt $texts.Count; $i++) {
$sz = $g.MeasureString($texts[$i], $fonts[$i])
$x = [int](($width - $sz.Width) / 2)
$g.DrawString($texts[$i], $fonts[$i], $brushes[$i], [float]$x, [float]$currentY)
$currentY += $heights[$i] + $lineSpacing
}
$g.Dispose()
# -----------------------------------------------------------------------
# Save and set as wallpaper
# -----------------------------------------------------------------------
$bmpPath = "C:\Windows\Setup\Scripts\desktopinfo.bmp"
Write-Log "Saving BMP: $bmpPath"
$bmp.Save($bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
$bmp.Dispose()
# Clear Windows wallpaper cache so it reloads from our BMP
# Without this, Windows reuses TranscodedWallpaper and ignores the updated file
$transcodedPath = "$env:APPDATA\Microsoft\Windows\Themes\TranscodedWallpaper"
if (Test-Path $transcodedPath) {
Remove-Item $transcodedPath -Force -ErrorAction SilentlyContinue
Write-Log "Cleared TranscodedWallpaper cache"
}
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
$result = [WallpaperApi]::SystemParametersInfo(20, 0, $bmpPath, 3)
Write-Log "SystemParametersInfo result: $result"
Write-Log "DesktopInfo render complete" -Level INFO
'@
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
Write-Log "Render script written" -Level OK
# -----------------------------------------------------------------------
# Store deployment date in registry (used for reference)
# -----------------------------------------------------------------------
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
$trigger.Delay = "PT20S" # wait for network to be available
$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

@ -62,6 +62,7 @@ $AppxToRemove = @(
"Microsoft.YourPhone" "Microsoft.YourPhone"
"Microsoft.ZuneMusic" "Microsoft.ZuneMusic"
"Microsoft.ZuneVideo" "Microsoft.ZuneVideo"
"7EE7776C.LinkedInforWindows"
) )
# Packages to always keep # Packages to always keep
@ -159,7 +160,6 @@ foreach ($cap in $CapabilitiesToRemove) {
$FeaturesToDisable = @( $FeaturesToDisable = @(
"MediaPlayback" "MediaPlayback"
"MicrosoftWindowsPowerShellV2Root" "MicrosoftWindowsPowerShellV2Root"
"Microsoft-RemoteDesktopConnection"
"Recall" "Recall"
"Microsoft-SnippingTool" "Microsoft-SnippingTool"
) )

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

@ -11,11 +11,41 @@ function Write-Log {
Add-Content -Path $LogFile -Value $line -Encoding UTF8 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 { function Grant-RegWriteAccess {
param([string]$Path) param([string]$Path)
# Grants Administrators FullControl on a registry key that has restricted ACL. # Grants Administrators FullControl on a TrustedInstaller-owned registry key.
# Required for keys owned by TrustedInstaller or with locked-down ACL. # Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
try { try {
[RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
[RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null
$hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1' $hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
$subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', '' $subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
$rootKey = switch ($hive) { $rootKey = switch ($hive) {
@ -23,17 +53,22 @@ function Grant-RegWriteAccess {
"HKCU" { [Microsoft.Win32.Registry]::CurrentUser } "HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
"HKCR" { [Microsoft.Win32.Registry]::ClassesRoot } "HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
} }
$rights = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree
$regRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership # Take ownership (requires SeTakeOwnershipPrivilege)
$key = $rootKey.OpenSubKey($subkey, $rights, $regRights) $key = $rootKey.OpenSubKey($subkey,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
if ($key) { if ($key) {
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None) $acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators") $acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
$key.SetAccessControl($acl) $key.SetAccessControl($acl)
$key.Close() $key.Close()
} }
# Re-open with ChangePermissions to grant full control
$key = $rootKey.OpenSubKey($subkey, $rights, [System.Security.AccessControl.RegistryRights]::ChangePermissions) # Grant FullControl to Administrators (requires ChangePermissions)
$key = $rootKey.OpenSubKey($subkey,
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
if ($key) { if ($key) {
$acl = $key.GetAccessControl() $acl = $key.GetAccessControl()
$rule = New-Object System.Security.AccessControl.RegistryAccessRule( $rule = New-Object System.Security.AccessControl.RegistryAccessRule(
@ -41,12 +76,12 @@ function Grant-RegWriteAccess {
[System.Security.AccessControl.RegistryRights]::FullControl, [System.Security.AccessControl.RegistryRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit", [System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
[System.Security.AccessControl.PropagationFlags]::None, [System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow [System.Security.AccessControl.AccessControlType]::Allow)
)
$acl.SetAccessRule($rule) $acl.SetAccessRule($rule)
$key.SetAccessControl($acl) $key.SetAccessControl($acl)
$key.Close() $key.Close()
} }
Write-Log " ACL fixed for $Path" -Level INFO
} }
catch { catch {
Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN
@ -206,13 +241,11 @@ catch {
} }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# OneDrive - prevent setup and remove shortcuts # OneDrive - uninstall from clean Windows (no policy block)
# NOTE: No policy key is set intentionally - M365 installation can reinstall
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Log " Disabling OneDrive" -Level INFO Write-Log " Uninstalling 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 # Remove OneDriveSetup.exe if present
$oneDrivePaths = @( $oneDrivePaths = @(
@ -272,4 +305,18 @@ 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)
# -----------------------------------------------------------------------
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
-Name "HideRecommendedSection" -Value 1
Write-Log "Step 3 complete" -Level OK Write-Log "Step 3 complete" -Level OK

View file

@ -131,7 +131,10 @@ try {
# Win11: align taskbar to left (0 = left, 1 = center) # Win11: align taskbar to left (0 = left, 1 = center)
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
# Hide Search box / button (0 = hidden, 1 = icon, 2 = full box) # 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 Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
# Hide Task View button # Hide Task View button
@ -155,9 +158,34 @@ try {
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# System tray - show all icons # System tray - show all icons
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# EnableAutoTray = 0 works on Win10; Win11 ignores it but set anyway
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
-Name "EnableAutoTray" -Value 0 -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 # Start menu settings
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -168,12 +196,19 @@ try {
-Name "DisableSearchBoxSuggestions" -Value 1 -Name "DisableSearchBoxSuggestions" -Value 1
# Win11: empty Start menu pins # Win11: empty Start menu pins
$startPinsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
-Name "ConfigureStartPins" ` -Name "ConfigureStartPins" `
-Value '{"pinnedList":[]}' ` -Value '{"pinnedList":[]}' `
-Type "String" -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 # Copilot - disable
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -272,4 +307,18 @@ finally {
} }
} }
# -----------------------------------------------------------------------
# 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 Write-Log "Step 4 complete" -Level OK

View file

@ -87,6 +87,12 @@ function Apply-ThemeSettings {
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" ` Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
-Name "TileWallpaper" -Value "0" -Type "String" -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
} }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------

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"
@ -95,27 +109,44 @@ $acroPaths = @(
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1 $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
$current = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" `
-Name "ProgId" -ErrorAction SilentlyContinue).ProgId
# 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) { 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

View file

@ -11,7 +11,7 @@ function Write-Log {
Add-Content -Path $LogFile -Value $line -Encoding UTF8 Add-Content -Path $LogFile -Value $line -Encoding UTF8
} }
$ScriptDir = "C:\Windows\Setup\Scripts" $ScriptDir = "C:\Windows\Setup\Scripts"
$RenderScript = "$ScriptDir\DesktopInfo-Render.ps1" $RenderScript = "$ScriptDir\DesktopInfo-Render.ps1"
$BmpPath = "$ScriptDir\desktopinfo.bmp" $BmpPath = "$ScriptDir\desktopinfo.bmp"
@ -19,137 +19,154 @@ if (-not (Test-Path $ScriptDir)) {
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null 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 the rendering script (runs on every logon as the user)
# Layout: hostname (large bold, centered), then detail lines centered
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
$renderContent = @" $renderContent = @'
# DesktopInfo-Render.ps1 # DesktopInfo-Render.ps1
# Collects system info and renders it onto the desktop wallpaper. # Collects system info and renders it centered on the desktop wallpaper.
# Runs on every user logon via Scheduled Task. # Runs on every user logon via Scheduled Task.
`$ErrorActionPreference = "Continue" $ErrorActionPreference = "Continue"
$LogFile = "C:\Windows\Setup\Scripts\desktopinfo.log"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" -Encoding UTF8
}
Write-Log "DesktopInfo render started" -Level INFO
Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Drawing
Add-Type -TypeDefinition @' Add-Type -AssemblyName System.Windows.Forms
Add-Type -TypeDefinition @"
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
public class WallpaperApi { public class WallpaperApi {
[DllImport("user32.dll", CharSet=CharSet.Auto)] [DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni); public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
} }
'@ -ErrorAction SilentlyContinue "@ -ErrorAction SilentlyContinue
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Collect system info # Collect system info
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
`$hostname = `$env:COMPUTERNAME Write-Log "Collecting system info"
`$username = `$env:USERNAME $hostname = $env:COMPUTERNAME
`$ipAddress = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | $userDomain = $env:USERDOMAIN
Where-Object { `$_.IPAddress -ne "127.0.0.1" -and `$_.PrefixOrigin -ne "WellKnown" } | $userName = $env:USERNAME
Select-Object -First 1).IPAddress $loggedUser = if ($userDomain -and $userDomain -ne $hostname) { "$userDomain\$userName" } else { "$hostname\$userName" }
if (-not `$ipAddress) { `$ipAddress = "N/A" }
`$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue $osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
`$osName = if (`$osInfo) { `$osInfo.Caption -replace "Microsoft ", "" } else { "Windows" } $osName = if ($osInfo) { $osInfo.Caption -replace "^Microsoft\s*", "" } else { "Windows" }
`$osBuild = if (`$osInfo) { `$osInfo.BuildNumber } else { "" } $ramGB = if ($osInfo) { [math]::Round($osInfo.TotalVisibleMemorySize / 1024 / 1024, 1) } else { "?" }
# Deployment date = when script was first run, stored in registry $cpuInfo = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
`$deployRegPath = "HKLM:\SOFTWARE\X9\Deployment" $cpuCount = if ($cpuInfo) { $cpuInfo.NumberOfLogicalProcessors } else { "?" }
`$deployDate = (Get-ItemProperty -Path `$deployRegPath -Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate $cpuSpeed = if ($cpuInfo) { $cpuInfo.MaxClockSpeed } else { "?" }
if (-not `$deployDate) { `$deployDate = "N/A" }
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } |
Select-Object -ExpandProperty IPAddress) -join ", "
if (-not $ips) { $ips = "N/A" }
$csInfo = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
$domain = if ($csInfo -and $csInfo.PartOfDomain) { $csInfo.Domain } `
elseif ($csInfo -and $csInfo.Workgroup) { $csInfo.Workgroup.ToLower() } `
else { "N/A" }
Write-Log "hostname=$hostname user=$loggedUser os=$osName ram=$($ramGB)GB cpu=${cpuCount}x${cpuSpeed}MHz ips=$ips domain=$domain"
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Build info lines # Screen dimensions
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
`$lines = @( $screen = [System.Windows.Forms.Screen]::PrimaryScreen
"Computer : `$hostname" $width = if ($screen) { $screen.Bounds.Width } else { 1920 }
"User : `$username" $height = if ($screen) { $screen.Bounds.Height } else { 1080 }
"IP : `$ipAddress" Write-Log "screen=${width}x${height}"
"OS : `$osName (build `$osBuild)"
"Deployed : `$deployDate" # -----------------------------------------------------------------------
# Create bitmap and graphics context
# -----------------------------------------------------------------------
$bmp = New-Object System.Drawing.Bitmap($width, $height)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
$g.Clear([System.Drawing.ColorTranslator]::FromHtml("#556364"))
# -----------------------------------------------------------------------
# Fonts and brushes
# -----------------------------------------------------------------------
$fontName = "Segoe UI"
$fontTitle = New-Object System.Drawing.Font($fontName, 36, [System.Drawing.FontStyle]::Bold)
$fontBold = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Bold)
$fontReg = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Regular)
$brushWhite = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
$brushGray = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("#C8D2D2"))
# -----------------------------------------------------------------------
# Lines: text, font, brush
# -----------------------------------------------------------------------
$texts = @(
$hostname
"Logged on user: $loggedUser"
"OS: $osName"
"CPU: $cpuCount at $cpuSpeed MHz RAM: $($ramGB)GB"
"IPv4 address: $ips Machine domain: $domain"
) )
$fonts = @( $fontTitle, $fontReg, $fontBold, $fontReg, $fontReg )
$brushes = @( $brushWhite, $brushGray, $brushGray, $brushGray, $brushGray )
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Render bitmap # Measure total block height, then center vertically
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue $lineSpacing = 8
$heights = @()
`$screen = [System.Windows.Forms.Screen]::PrimaryScreen for ($i = 0; $i -lt $texts.Count; $i++) {
`$width = if (`$screen) { `$screen.Bounds.Width } else { 1920 } $heights += [int]($g.MeasureString($texts[$i], $fonts[$i]).Height)
`$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) { $totalH = ($heights | Measure-Object -Sum).Sum + $lineSpacing * ($texts.Count - 1)
"bottom*" { `$height - `$blockH - `$margin } $currentY = [int](($height - $totalH) / 2)
"top*" { `$margin }
default { `$height - `$blockH - `$margin } # -----------------------------------------------------------------------
# Draw each line centered horizontally
# -----------------------------------------------------------------------
for ($i = 0; $i -lt $texts.Count; $i++) {
$sz = $g.MeasureString($texts[$i], $fonts[$i])
$x = [int](($width - $sz.Width) / 2)
$g.DrawString($texts[$i], $fonts[$i], $brushes[$i], [float]$x, [float]$currentY)
$currentY += $heights[$i] + $lineSpacing
} }
# Draw shadow then text $g.Dispose()
foreach (`$line in `$lines) {
`$g.DrawString(`$line, `$font, `$shadowBrush, (`$x + 1), (`$y + 1)) # -----------------------------------------------------------------------
`$g.DrawString(`$line, `$font, `$brush, `$x, `$y) # Save and set as wallpaper
`$y += `$lineHeight # -----------------------------------------------------------------------
$bmpPath = "C:\Windows\Setup\Scripts\desktopinfo.bmp"
Write-Log "Saving BMP: $bmpPath"
$bmp.Save($bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
$bmp.Dispose()
# Clear Windows wallpaper cache so it reloads from our BMP
# Without this, Windows reuses TranscodedWallpaper and ignores the updated file
$transcodedPath = "$env:APPDATA\Microsoft\Windows\Themes\TranscodedWallpaper"
if (Test-Path $transcodedPath) {
Remove-Item $transcodedPath -Force -ErrorAction SilentlyContinue
Write-Log "Cleared TranscodedWallpaper cache"
} }
`$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 # SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
[WallpaperApi]::SystemParametersInfo(20, 0, `$bmpPath, 3) | Out-Null $result = [WallpaperApi]::SystemParametersInfo(20, 0, $bmpPath, 3)
"@ Write-Log "SystemParametersInfo result: $result"
Write-Log "DesktopInfo render complete" -Level INFO
'@
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force $renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
Write-Log "Render script written" -Level OK Write-Log "Render script written" -Level OK
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Store deployment date in registry (used by render script) # Store deployment date in registry (used for reference)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Log "Storing deployment date in registry" -Level INFO Write-Log "Storing deployment date in registry" -Level INFO
try { try {
@ -183,7 +200,8 @@ try {
$action = New-ScheduledTaskAction -Execute "powershell.exe" ` $action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`"" -Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
$trigger = New-ScheduledTaskTrigger -AtLogOn $trigger = New-ScheduledTaskTrigger -AtLogOn
$trigger.Delay = "PT20S" # wait for network to be available
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) ` $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
-MultipleInstances IgnoreNew ` -MultipleInstances IgnoreNew `
-StartWhenAvailable -StartWhenAvailable

View file

@ -211,6 +211,15 @@ Test-Check "File extensions visible" {
Test-Check "Explorer opens to This PC" { Test-Check "Explorer opens to This PC" {
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1 (Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1
} }
Test-Check "This PC icon on desktop" {
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" "{20D04FE0-3AEA-1069-A2D8-08002B30309D}") -eq 0
}
Test-Check "Start menu Recommended section hidden" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" "HideRecommendedSection") -eq 1
}
Test-Check "Start menu recently added hidden" {
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "Start_TrackProgs") -eq 0
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Scheduled tasks # Scheduled tasks