Compare commits

..

No commits in common. "2f0e176e820c4be07b902c4321fbec5d118ec52e" and "8b795547d33f686fcbc19784f4804228a3e3b0d2" have entirely different histories.

26 changed files with 1695 additions and 952 deletions

242
CLAUDE.md
View file

@ -2,16 +2,14 @@
## Project context ## Project context
MSP deployment tool for X9.cz - automated preparation of new Windows 10/11 computers. MSP deployment script for X9.cz - automated preparation of new Windows 10/11 computers for clients.
Go GUI launcher (xetup.exe) embeds PowerShell scripts, runs them sequentially, handles Replaces ~3 hours of manual setup with a single PowerShell script (evolving toward Go TUI launcher).
reboot cycles for Windows Update, and sends an email report when done.
**Key parameters:** **Key parameters:**
- Target OS: Windows 10 and Windows 11 (x64), including unsupported HW - Target OS: Windows 10 and Windows 11 (x64), including unsupported HW
- Execution: as Administrator on already-installed Windows (not WinPE/autounattend, not OOBE) - Execution: as Administrator on already-installed Windows (not WinPE/autounattend)
- Volume: ~20 machines per month, various clients - Volume: ~20 machines per month, various clients
- Operator: MSP technician on-site at client - Operator: MSP technician on-site at client
- Entry point: xetup.exe only (no CLI script entry point)
--- ---
@ -25,76 +23,27 @@ reboot cycles for Windows Update, and sends an email report when done.
## Repo structure ## Repo structure
``` ```
xetup/ windows-deployment-new/
├── CLAUDE.md <- this file ├── CLAUDE.md <- this file
├── SPEC.md <- technical specification ├── SPEC.md <- technical specification
├── embed.go <- embeds scripts/ and assets/ into binary ├── Deploy-Windows.ps1 <- master script (entry point)
├── cmd/xetup/
│ ├── main.go <- entry point: extract, load config, launch GUI
│ └── app.manifest <- Windows manifest (requireAdministrator)
├── internal/
│ ├── config/config.go <- Config struct, Load/Save, DefaultConfig
│ ├── gui/gui.go <- Walk GUI: form run summary (3 phases)
│ ├── runner/runner.go <- sequential PS script executor with log streaming
│ ├── state/state.go <- JSON state file for reboot-resume persistence
│ ├── prereboot/ <- autologon + X9-Resume scheduled task for reboot cycle
│ ├── preflight/ <- pre-run checks (admin, winget, network, disk)
│ └── report/report.go <- HTML email report via SMTP2Go
├── scripts/ ├── scripts/
│ ├── common.ps1 <- shared functions (Write-Log, Get-Feature, Load-Config) │ ├── 00-admin-account.ps1 <- create hidden admin account
│ ├── 00-admin-account.ps1 <- create hidden admin account (adminx9, no password)
│ ├── 01-bloatware.ps1 <- remove AppX, Capabilities, Features │ ├── 01-bloatware.ps1 <- remove AppX, Capabilities, Features
│ ├── 02-software.ps1 <- parallel winget installs + Adobe PDF default + Atera │ ├── 02-software.ps1 <- winget installs + Adobe PDF default
│ ├── 03-system-registry.ps1 <- HKLM tweaks, Edge policies, OneDrive, powercfg │ ├── 03-system-registry.ps1 <- HKLM tweaks
│ ├── 04-default-profile.ps1 <- NTUSER.DAT + HKCU + personalization (merged) │ ├── 04-default-profile.ps1 <- C:\Users\Default\NTUSER.DAT changes
│ ├── 07-backinfo.ps1 <- deploy BackInfo.exe + startup shortcut │ ├── 05-personalization.ps1 <- colors, wallpaper, theme
│ ├── 08-activation.ps1 <- Windows activation (OA3 config key GVLK) │ ├── 06-scheduled-tasks.ps1 <- register scheduled tasks
│ ├── 09-pc-identity.ps1 <- rename PC + C:\X9 folder (exit 9 on rename) │ ├── 07-desktop-info.ps1 <- TO BE DELETED (replaced by BackInfo)
│ ├── 10-network.ps1 <- Private profile, ping, Network Discovery │ └── 08-activation.ps1 <- Windows activation via slmgr
│ ├── 11-dell-update.ps1 <- Dell Command | Update (auto-skip on non-Dell)
│ └── 12-windows-update.ps1 <- PSWindowsUpdate reboot cycle (exit 9)
├── config/ ├── config/
│ └── config.json <- default config template │ └── config.json <- per-client config
├── assets/ ├── assets/
│ ├── Backinfo/ <- BackInfo.exe + .ini │ ├── Backinfo/ <- BackInfo.exe + .ini + backinfo_W11.ps1
│ └── Logo/ <- X9-ikona.ico, X9-logo.jpeg │ └── Logo/ <- X9-ikona.ico, X9-logo.jpeg
├── tests/ └── tests/
│ └── Test-Deployment.ps1 <- post-deployment verification └── Test-Deployment.ps1 <- post-deployment verification
└── web/ <- xetup.x9.cz static site
```
---
## Execution flow
```
xetup.exe start
→ extract scripts/ and assets/ to temp dir
→ state file exists? → resume mode (skip form, run pending steps)
→ normal mode:
1. Pre-flight checks (admin, winget, network, disk) shown in GUI
2. Config form (PC name, key, profile, step checkboxes)
3. Write runtime config JSON (reflects GUI selections)
4. Run steps sequentially via powershell.exe -File -ConfigPath -LogFile
5. Step exits 9? → save state, setup autologon + X9-Resume task, reboot
6. After reboot → xetup resumes, runs remaining steps
7. All done → cleanup autologon, send email report, show summary
```
## Step execution order
```
00 Admin account (adminx9)
08 Windows activation
01 Bloatware removal
02 Software (parallel winget + Atera + PDF default)
03 System Registry (HKLM + Edge policies)
04 Default Profile + Personalization (single hive load)
07 BackInfo
10 Network discovery
11 Dell Command | Update
09 PC identity (rename triggers reboot via exit 9)
12 Windows Update (reboot cycle via exit 9)
``` ```
--- ---
@ -102,79 +51,115 @@ xetup.exe start
## Conventions and rules ## Conventions and rules
### PowerShell ### PowerShell
- All scripts use `common.ps1` (dot-sourced): Write-Log, Get-Feature, Load-Config - Always `#Requires -RunAsAdministrator` in master script
- Scripts receive `-ConfigPath` (path to JSON) and `-LogFile` params - `$ErrorActionPreference = "Continue"` - script must survive partial failures
- Scripts parse config themselves via `Load-Config $ConfigPath` - Log every step to `C:\Windows\Setup\Scripts\Deploy.log`
- `$ErrorActionPreference = "Continue"` - scripts survive partial failures - Logging via `Write-Log` function defined in master script
- Exit code 9 = "reboot required" - runner saves state and triggers restart - `Invoke-Step` function wraps every step - catches errors, logs, continues
- Log to `C:\Windows\Setup\Scripts\Deploy.log` - Comments in English, code in English
- NO diacritics anywhere (encoding issues across systems) - NO diacritics - no accented characters anywhere: not in comments, not in user messages, not in log output
- NO emoticons - NO emoticons - not in comments, not in output messages
- Reason: encoding issues across systems, log readability, compatibility
### Go / GUI ### Master script structure
- Walk-based GUI (Windows only, CGO required) ```powershell
- Cross-compile: `CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64` # 1. Load config.json
- Three phases: config form → live log → summary with reboot countdown # 2. Run individual scripts in order
- Features system: steps can have sub-features (checkboxes in GUI), controlled via config.features # 3. Print summary report at end (OK/ERROR counts)
```
### Config ### Master script switches
- `config.json` is the template, `config-runtime.json` is written to temp at runtime | Switch | Behavior |
- GUI regenerates runtime config before starting the run |---|---|
- `DefaultConfig()` in config.go provides sensible defaults when config.json is absent | `-SkipBloatware` | Skip step 1 |
- Features default to `true` when missing from config | `-SkipSoftware` | Skip step 2 |
| `-SkipDefaultProfile` | Skip step 4 |
| `-DryRun` | Run without changes, log only |
### Testing ### Testing
- Test VM: Windows 10/11 x64 on VMware ESXi - Test VM: Windows 10/11 x64 on VMware ESXi (X9.cz internal infrastructure)
- Before each test: take snapshot, after test: revert - Before each test: take snapshot
- Dev environment: x64 VM only (not ARM) - After test: revert snapshot
- Dev environment: x64 VM only - NOT ARM (no Parallels/Apple Silicon for testing)
--- ---
## Key implementation details ## Important notes
### BackInfo (replaces custom DesktopInfo)
BackInfo.exe IS used. Located in assets/Backinfo/. Deployment:
1. Copy assets/Backinfo/ to C:\Program Files\Backinfo\
2. Run backinfo_W11.ps1 (detects OS, writes registry, creates Startup shortcut)
3. BackInfo.exe auto-starts on every logon, reads INI, renders BMP with system info
- Configurable via BackInfo.ini (fonts, positions, data sources)
- Displays: hostname (centered, large), username, OS, HW info, network info
- DELETE 07-desktop-info.ps1 - no longer needed
### Adobe Reader as default PDF app
- After install: set .pdf -> AcroRd32 association
- Scheduled task PDF-DefaultApp restores association on every logon (guard against Edge overwriting it)
- NOTE: UCPD.sys (kernel driver since Feb 2024) blocks UserChoice writes. Consider disabling UCPD during deployment.
### Default Profile
- Changes to C:\Users\Default\NTUSER.DAT via reg load / reg unload
- Applies to all new users - critical for MSP deployment
- Currently logged-in user gets changes via direct write to HKCU
### Winget
- Always use --accept-package-agreements --accept-source-agreements
- Check winget availability before running installs
- Log result of every install
### Atera Agent
- Download: `Invoke-WebRequest -Uri "https://x9.servicedesk.atera.com/api/utils/agent-install/windows/?cid=31&aeid=50b72e7113e54a63ac76b96c54c7e337" -OutFile setup.msi`
- Install: `msiexec /i setup.msi /qn`
### Admin account (adminx9) ### Admin account (adminx9)
- No password (empty), hidden from login screen, Administrators group - NO PASSWORD (changed from previous version)
- FullName = "X9.cz s.r.o." (via ADSI) - FullName = "X9.cz s.r.o." (via ADSI)
- Also used by prereboot for autologon during reboot cycles - Hidden from login screen
- Added to Administrators group
### Edge policies
- Mandatory (`Policies\Microsoft\Edge`): HideFirstRunExperience, DefaultBrowserSettingEnabled, DiagnosticData, FeedbackSurveysEnabled
- Recommended (`Policies\Microsoft\Edge\Recommended`): everything else (user can override)
### PDF default
- Adobe Reader set via HKCR\.pdf after install
- UCPD driver stopped during association write, restarted after
### Reboot-resume cycle
- `prereboot_windows.go`: copies xetup.exe to stable path, sets autologon for adminx9, registers X9-Resume scheduled task
- `state.go`: persists pending steps + accumulated results across reboots
- Steps 09 (pcIdentity on rename) and 12 (windowsUpdate) can trigger exit 9
- Cleanup: disables autologon, removes X9-Resume task
### Email report
- Sent via SMTP2Go (mail-eu.smtp2go.com:2525) at end of deployment
- From: xetup@x9.cz, To: net@x9.cz
- Subject: "xetup report HOSTNAME"
- HTML body with per-step status table
### Parallel winget
- 02-software.ps1 launches all winget installs as background jobs (Start-Job)
- Jobs run simultaneously, results collected after all complete
--- ---
## DO NOT ## DO NOT
- Do not use `$ErrorActionPreference = "Stop"` - scripts must survive partial failure - Do not use $ErrorActionPreference = "Stop" - script must survive partial failure
- Do not remove Calculator (Microsoft.WindowsCalculator) - Do not remove Calculator (Microsoft.WindowsCalculator) - intentionally kept
- Do not use ARM VM for testing - Do not use ARM VM for testing
- Do not write scripts depending on specific username - Do not write scripts depending on specific username - script is universal
- Do not use hardcoded paths that do not exist on clean Windows - Do not use hardcoded paths that do not exist on clean Windows
- NO diacritics in any file - NO diacritics - no accented characters in any part of any script
- NO emoticons - NO emoticons - none in comments, log messages or output
- Do not remove OneDrive policy-block-free (M365 must be able to reinstall it) - Do not remove OneDrive - must remain installable for M365
- Do not remove RDP/RDS or Microsoft-RemoteDesktopConnection - Do not remove RDP/RDS - must remain functional
- Do not create Deploy-Windows.ps1 or other CLI entry points (xetup.exe is sole entry point) - Do not remove Microsoft-RemoteDesktopConnection from Optional Features
---
## Planned changes (from review v2, 2026-04-15)
### Must fix
- [ ] Remove OneDrive uninstall from 03-system-registry.ps1 and 04-default-profile.ps1
- [ ] Remove password from admin account, add FullName = "X9.cz s.r.o."
- [ ] Delete 07-desktop-info.ps1, replace with BackInfo deployment step
- [ ] Add powercfg settings (standby-timeout-ac 0, monitor-timeout-ac 60, etc.)
- [ ] Add proxy auto-detect disable (AutoDetect = 0)
- [ ] Add Atera Agent install step
- [ ] Extend Edge policies (~15 more keys)
### New features (from colleague spec v2)
- [ ] Taskbar pinned apps: admin vs user variants via XML layout + -ProfileType parameter
- [ ] Explorer: ShowRecent=0, ShowFrequent=0, FullPath=1 in CabinetState
- [ ] Network discovery: enable ping, set private network profile (post-restart step)
- [ ] PC rename: Rename-Computer as final step before restart
- [ ] C:\X9 directory structure with custom folder icon
### Architecture evolution
- [ ] Go TUI launcher (xetup.exe) embedding PS scripts
- [ ] spec.yaml as single source of truth
- [ ] Web platform at xetup.x9.cz (Forgejo + docs + comments)
- [ ] Self-update mechanism in xetup.exe
--- ---
@ -182,6 +167,9 @@ xetup.exe start
| # | Question | Status | | # | Question | Status |
|---|---|---| |---|---|---|
| 1 | Complete SW list for winget | TODO - list may be incomplete | | 1 | BackInfo replacement | DONE - using BackInfo.exe from assets/ |
| 2 | Atera MFA bypass | OPEN - does aeid parameter avoid MFA? | | 2 | Complete SW list for winget | TODO - list incomplete |
| 3 | `--resume` flag | Passed by prereboot task but not parsed - resume detected via state file | | 3 | Per-client variability via config.json | FUTURE |
| 4 | Admin account adminx9 | DECIDED - no password, FullName "X9.cz s.r.o." |
| 5 | UCPD driver workaround for PDF default | TODO - disable during deployment |
| 6 | Atera MFA bypass | OPEN - does aeid parameter avoid MFA? |

276
Deploy-Windows.ps1 Normal file
View file

@ -0,0 +1,276 @@
#Requires -RunAsAdministrator
[CmdletBinding()]
param(
[switch]$SkipBloatware,
[switch]$SkipSoftware,
[switch]$SkipDefaultProfile,
[switch]$DryRun,
[ValidateSet("default","admin","user")]
[string]$ProfileType = "default"
)
$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
backinfo = $true
network = $true
pcIdentity = $true
activation = $true
dellUpdate = $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 -ProfileType $ProfileType
}
} 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 - BackInfo
# -----------------------------------------------------------------------
if ($stepsEnabled['backinfo']) {
Invoke-Step -Name "Step 7 - BackInfo" -Action {
& "$ScriptRoot\scripts\07-backinfo.ps1" -Config $Config -LogFile $LogFile
}
} else { Skip-Step "Step 7 - BackInfo" }
# -----------------------------------------------------------------------
# Step 9 - Network
# -----------------------------------------------------------------------
if ($stepsEnabled['network']) {
Invoke-Step -Name "Step 9 - Network" -Action {
& "$ScriptRoot\scripts\10-network.ps1" -Config $Config -LogFile $LogFile
}
} else { Skip-Step "Step 9 - Network" }
# -----------------------------------------------------------------------
# Step 11 - Dell Command | Update (auto-skipped on non-Dell hardware)
# -----------------------------------------------------------------------
if ($stepsEnabled['dellUpdate']) {
Invoke-Step -Name "Step 11 - Dell Command | Update" -Action {
& "$ScriptRoot\scripts\11-dell-update.ps1" -Config $Config -LogFile $LogFile
}
} else { Skip-Step "Step 11 - Dell Command | Update" }
# -----------------------------------------------------------------------
# Step 10 - PC identity (rename + C:\X9) - runs last, rename needs restart
# -----------------------------------------------------------------------
if ($stepsEnabled['pcIdentity']) {
Invoke-Step -Name "Step 10 - PC identity" -Action {
& "$ScriptRoot\scripts\09-pc-identity.ps1" -Config $Config -LogFile $LogFile
}
} else { Skip-Step "Step 10 - PC identity" }
# -----------------------------------------------------------------------
# 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
}

319
SPEC.md
View file

@ -1,14 +1,16 @@
# MSP Windows Deployment - Specification # MSP Windows Deployment - Specification (SPEC.md)
> Purpose: Automated preparation of new Windows 10/11 computers for X9.cz clients > Version: 0.2 (draft)
> Author: X9.cz
> Purpose: Automated preparation of new Windows 10/11 computers for clients
--- ---
## Overview ## Overview
xetup.exe replaces ~3 hours of manual computer setup. GUI launcher embeds PowerShell Script replaces ~3 hours of manual computer setup. Run once as Administrator on
scripts, runs them sequentially, handles reboot cycles, sends email report when done. already-installed Windows, performs everything automatically, saves result to Default
Settings are applied to Default Profile (NTUSER.DAT) so every new user inherits them. Profile so settings apply to every subsequent user.
--- ---
@ -16,157 +18,276 @@ Settings are applied to Default Profile (NTUSER.DAT) so every new user inherits
- Windows 10 or Windows 11 (x64) - Windows 10 or Windows 11 (x64)
- Run as Administrator - Run as Administrator
- Internet connection (for winget installs, Atera agent, Windows Update) - Internet connection (for winget installs)
- Post-OOBE (fully installed Windows with at least one user account) - Computer received either as clean OEM install or with manufacturer pre-installed Windows
--- ---
## Step execution order ## What the script does NOT do
| # | Step | Script | Notes | - Does not install Windows (not an autounattend.xml for clean install)
|---|---|---|---| - Does not create images
| 00 | Admin account | 00-admin-account.ps1 | adminx9, no password, hidden | - Does not manage the computer ongoing (one-time deployment)
| 08 | Windows activation | 08-activation.ps1 | OA3 → config key → GVLK fallback |
| 01 | Bloatware removal | 01-bloatware.ps1 | AppX + Capabilities + Features |
| 02 | Software install | 02-software.ps1 | Parallel winget + Atera + PDF default |
| 03 | System registry | 03-system-registry.ps1 | HKLM tweaks, Edge, powercfg, WPAD |
| 04 | Profile + personalization | 04-default-profile.ps1 | NTUSER.DAT + HKCU + theme |
| 07 | BackInfo | 07-backinfo.ps1 | System info wallpaper overlay |
| 10 | Network | 10-network.ps1 | Private profile, ping, discovery |
| 11 | Dell Update | 11-dell-update.ps1 | Auto-skip on non-Dell HW |
| 09 | PC identity | 09-pc-identity.ps1 | Rename + C:\X9 (reboot on rename) |
| 12 | Windows Update | 12-windows-update.ps1 | Multi-round reboot cycle |
--- ---
## Step 00 - Admin account ## Script structure
Script is divided into steps. Each step logs its result. Steps can be skipped with switches.
---
## STEP 0a - Admin account
Creates local admin account `adminx9`: Creates local admin account `adminx9`:
- No password (empty) - account is hidden, only accessible to technicians - Password from `config.json` (`adminAccount.password`)
- FullName = "X9.cz s.r.o." (via ADSI)
- Added to Administrators group - Added to Administrators group
- Hidden from login screen (SpecialAccounts\UserList = 0) - Password never expires, user cannot change password
- Password never expires - Hidden from Windows login screen (SpecialAccounts\UserList = 0)
--- ---
## Step 08 - Windows activation ## STEP 0b - Windows activation
Priority: OA3 embedded key (BIOS/UEFI) → config.json productKey → GVLK by edition. Activates Windows using product key from config:
Optional KMS server via config.json. Skips if already activated (LicenseStatus = 1). - Key from `config.json` (`activation.productKey`) - set to real MAK/retail key for production
- Falls back to GVLK (KMS client key) matched by detected OS edition
- Optional KMS server via `activation.kmsServer`
- If already activated, skips silently
--- ---
## Step 01 - Bloatware removal ## STEP 1 - Bloatware removal
Removes ~35 AppX packages (Cortana, Copilot, Teams, Xbox, Skype, News, etc.), ### 1a - AppX packages (UWP apps)
~14 Windows Capabilities (Fax, IE, WordPad, etc.), and Optional Features
(PowerShell 2.0, Recall). Calculator intentionally kept.
--- Removed for all users (-AllUsers) and from provisioned packages (so they do not return for new users).
## Step 02 - Software installation | Package | Description |
Parallel winget installs (Start-Job):
| Software | Winget ID |
|---|---| |---|---|
| 7-Zip | 7zip.7zip | | Microsoft.Microsoft3DViewer | 3D Viewer |
| Adobe Acrobat Reader 64-bit | Adobe.Acrobat.Reader.64-bit | | Microsoft.BingSearch | Bing Search |
| OpenVPN Connect | OpenVPNTechnologies.OpenVPNConnect | | Microsoft.WindowsCamera | Camera |
| Clipchamp.Clipchamp | Clipchamp video editor |
| Microsoft.WindowsAlarms | Clock / Alarm |
| Microsoft.Copilot | Copilot AI |
| Microsoft.549981C3F5F10 | Cortana |
| Microsoft.Windows.DevHome | Dev Home |
| MicrosoftCorporationII.MicrosoftFamily | Family Safety |
| Microsoft.WindowsFeedbackHub | Feedback Hub |
| Microsoft.Edge.GameAssist | Game Assist |
| Microsoft.GetHelp | Help |
| Microsoft.Getstarted | Tips / Get Started |
| microsoft.windowscommunicationsapps | Mail and Calendar |
| Microsoft.WindowsMaps | Maps |
| Microsoft.MixedReality.Portal | Mixed Reality |
| Microsoft.BingNews | News |
| Microsoft.MicrosoftOfficeHub | Office Hub |
| Microsoft.Office.OneNote | OneNote |
| Microsoft.OutlookForWindows | Outlook (new) |
| Microsoft.Paint | Paint (new UWP) |
| Microsoft.MSPaint | Paint (legacy) |
| Microsoft.People | People |
| Microsoft.Windows.Photos | Photos |
| Microsoft.PowerAutomateDesktop | Power Automate |
| MicrosoftCorporationII.QuickAssist | Quick Assist |
| Microsoft.SkypeApp | Skype |
| Microsoft.ScreenSketch | Snipping Tool |
| Microsoft.MicrosoftSolitaireCollection | Solitaire |
| Microsoft.MicrosoftStickyNotes | Sticky Notes |
| MicrosoftTeams / MSTeams | Teams (personal) |
| Microsoft.Todos | To Do |
| Microsoft.WindowsSoundRecorder | Voice Recorder |
| Microsoft.Wallet | Wallet |
| Microsoft.BingWeather | Weather |
| Microsoft.WindowsTerminal | Windows Terminal |
| Microsoft.Xbox.TCUI | Xbox UI |
| Microsoft.XboxApp | Xbox |
| Microsoft.XboxGameOverlay | Xbox Game Overlay |
| Microsoft.XboxGamingOverlay | Xbox Gaming Overlay |
| Microsoft.XboxIdentityProvider | Xbox Identity |
| Microsoft.XboxSpeechToTextOverlay | Xbox Speech |
| Microsoft.GamingApp | Gaming App |
| Microsoft.YourPhone | Phone Link |
| Microsoft.ZuneMusic | Music |
| Microsoft.ZuneVideo | Movies and TV |
After Acrobat: UCPD driver stopped, .pdf → AcroExch.Document.DC set via HKCR, UCPD restarted. NOTE: Microsoft.WindowsCalculator is intentionally KEPT.
Atera RMM agent: downloaded from x9.servicedesk.atera.com, installed via msiexec /qn with -Wait. ### 1b - Windows Capabilities
| Capability | Description |
|---|---|
| Print.Fax.Scan | Fax and Scan |
| Language.Handwriting | Handwriting |
| Browser.InternetExplorer | Internet Explorer |
| MathRecognizer | Math Input |
| OneCoreUAP.OneSync | OneSync |
| OpenSSH.Client | OpenSSH client |
| Microsoft.Windows.MSPaint | Paint (Win32) |
| Microsoft.Windows.PowerShell.ISE | PowerShell ISE |
| App.Support.QuickAssist | Quick Assist |
| Microsoft.Windows.SnippingTool | Snipping Tool |
| App.StepsRecorder | Steps Recorder |
| Hello.Face.* | Windows Hello face |
| Media.WindowsMediaPlayer | Windows Media Player |
| Microsoft.Windows.WordPad | WordPad |
### 1c - Windows Optional Features
| Feature | Description |
|---|---|
| MediaPlayback | Media playback |
| MicrosoftWindowsPowerShellV2Root | PowerShell 2.0 |
| Microsoft-RemoteDesktopConnection | RDP client |
| Recall | Windows Recall (AI) |
| Microsoft-SnippingTool | Snipping Tool (feature) |
--- ---
## Step 03 - System registry (HKLM) ## STEP 2 - Software installation (winget)
Always applied: password max age unlimited, timezone (Central Europe Standard Time). | Software | Winget ID | Notes |
|---|---|---|
| 7-Zip | `7zip.7zip` | OK |
| Adobe Acrobat Reader | `Adobe.Acrobat.Reader.64-bit` | OK, see note |
| OpenVPN Connect | `OpenVPNTechnologies.OpenVPNConnect` | OK |
| ... | ... | TODO: complete list |
Feature-toggled sections: > Adobe Acrobat Reader: After install, script sets .pdf -> AcroRd32 as default.
- **systemTweaks**: BypassNRO, disable Teams auto-install, Widgets, GameDVR, Recall, Copilot search > Scheduled task PDF-DefaultApp restores this association on every logon as a guard
- **edgePolicies**: mandatory (first-run, telemetry) + recommended (UI defaults user can change) > against Edge overwriting it.
- **oneDriveUninstall**: removes consumer OneDrive (no policy block - M365 can reinstall)
- **powercfg**: standby-ac=0, monitor-ac=60, standby-dc=30, monitor-dc=15 > BackInfo: NOT used. Replaced by custom PowerShell scheduled task DesktopInfo.
- **proxyDisable**: WPAD auto-detect off > See STEP 7.
--- ---
## Step 04 - Default Profile + Personalization ## STEP 3 - System settings (HKLM - applies to whole system)
Single hive load of C:\Users\Default\NTUSER.DAT. All changes applied to both Default | Setting | Value | Notes |
hive and current HKCU. Feature-toggled sections: |---|---|---|
| Disable NRO (bypass network check) | HKLM\...\OOBE\BypassNRO = 1 | |
- **taskbarTweaks**: left alignment, hide Search/Copilot/TaskView/Widgets/Chat, show all | Disable auto-install of Teams | ConfigureChatAutoInstall = 0 | |
tray icons, taskbar layout XML per ProfileType (default/admin/user), NumLock on | Disable Cloud Optimized Content | DisableCloudOptimizedContent = 1 | |
- **startMenuTweaks**: empty pins, disable Bing search, disable Copilot, disable GameDVR | Disable Widgets (News and Interests) | HKLM\...\Dsh\AllowNewsAndInterests = 0 | |
- **explorerTweaks**: show extensions, LaunchTo=ThisPC, hide Recent/Frequent, full path | Edge - hide First Run Experience | HKLM\Policies\Edge\HideFirstRunExperience = 1 | |
| Passwords - no expiration | net accounts /maxpwage:UNLIMITED | |
Personalization (always): dark shell / light apps, accent #223B47, transparency off, | Time zone | Central Europe Standard Time | |
solid wallpaper #223B47 (BackInfo overwrites on logon). | OneDrive - remove | Delete OneDriveSetup.exe + Start Menu lnk | |
| Outlook (new) - disable auto-install | Delete UScheduler registry key | |
| Disable GameDVR | AppCaptureEnabled = 0 | |
--- ---
## Step 07 - BackInfo ## STEP 4 - Default Profile (NTUSER.DAT)
Copies BackInfo.exe + INI to C:\Program Files\Backinfo\. Detects OS, writes OSName to Settings applied to C:\Users\Default\NTUSER.DAT - inherited by every new user on first logon.
registry. Creates startup shortcut for all users. BackInfo renders system info BMP as
desktop wallpaper on every logon. Method: script loads Default hive (reg load), makes changes, unloads (reg unload).
| Setting | Key / Value | Description |
|---|---|---|
| Taskbar - align left | TaskbarAl = 0 | Win11 default is center |
| Taskbar - hide Search box | SearchboxTaskbarMode = 0 | |
| Taskbar - hide Copilot button | ShowCopilotButton = 0 | |
| Taskbar - hide Task View button | ShowTaskViewButton = 0 | |
| Taskbar - hide Widgets | TaskbarDa = 0 | |
| Taskbar - hide Chat/Teams button | TaskbarMn = 0 | |
| Taskbar - show all tray icons | Scheduled task ShowAllTrayIcons | Runs on every logon |
| Taskbar - empty pinlist | TaskbarLayoutModification.xml | Removes default pinned apps |
| Explorer - show file extensions | HideFileExt = 0 | |
| Explorer - open to This PC | LaunchTo = 1 | Instead of Quick Access |
| Start menu - empty pins | ConfigureStartPins = {"pinnedList":[]} | Win11 |
| Start menu - disable Bing results | DisableSearchBoxSuggestions = 1 | |
| Copilot - disable | TurnOffWindowsCopilot = 1 | |
| GameDVR - disable | AppCaptureEnabled = 0 | |
| OneDrive - remove RunOnce key | Delete OneDriveSetup from Run | |
| Num Lock on startup - enable | InitialKeyboardIndicators = 2 | |
| Accent color on title bars | ColorPrevalence = 1 | |
--- ---
## Step 10 - Network ## STEP 5 - Personalization (colors, wallpaper)
Sets all connected adapters to Private profile. Enables ICMP echo (ping) and Network Applied to both Default Profile and currently logged-in user.
Discovery firewall rules.
| Setting | Value |
|---|---|
| System theme (taskbar, Start) | Dark |
| App theme | Light |
| Accent color | #223B47 (dark blue-gray) |
| Accent color on Start and taskbar | Yes |
| Accent color on title bars | Yes |
| Transparency | Disabled |
| Wallpaper | Solid color #223B47 (no image) |
NOTE: DesktopInfo scheduled task (STEP 7) will overwrite the wallpaper with a system
info BMP. The solid color here is only a fallback if DesktopInfo is not running.
--- ---
## Step 11 - Dell Command | Update ## STEP 6 - Scheduled Tasks
Detects Dell hardware via Win32_ComputerSystem.Manufacturer. On non-Dell: skips silently. | Task | Trigger | Purpose |
On Dell: installs DCU Universal via winget, runs dcu-cli.exe /applyUpdates with |---|---|---|
-reboot=disable. Feature-toggled: drivers/firmware and BIOS separately. Exit 9 when | ShowAllTrayIcons | Every logon, every 1 min | Show all icons in system tray (Win11) |
BIOS/firmware updates are staged (finalize on next restart). | UnlockStartLayout | Once after layout is applied | Unlock Start menu layout |
| PDF-DefaultApp | Every logon | Restore .pdf -> Adobe Reader if Edge overwrote it |
| DesktopInfo | Every logon | Render system info onto desktop wallpaper |
--- ---
## Step 09 - PC identity ## STEP 7 - DesktopInfo (BackInfo replacement)
Creates C:\X9\ directory (Logs, Scripts, Assets) with custom folder icon. Custom PowerShell scheduled task. No external dependencies.
Sets computer description. Renames computer if config.json pcName is set and differs
from current. Exit 9 only when rename actually happened (restart required). **What it displays:**
- Computer name (hostname)
- IP address
- Windows version and build
- Logged-in username
- Deployment date
**How it works:**
1. PS script collects system info
2. Renders text onto bitmap via WPF / System.Drawing
3. Saves BMP to C:\Windows\Setup\Scripts\desktopinfo.bmp
4. Sets BMP as desktop wallpaper via SystemParametersInfo
5. Runs on every user logon via Scheduled Task
**Why not BackInfo:**
- BackInfo has Win11 rendering issues requiring registry hacks
- External EXE dependency is hard to distribute
- Custom PS solution = full control, no dependencies, works on Win10 and Win11
--- ---
## Step 12 - Windows Update ## STEP 8 - Logging and output
Installs PSWindowsUpdate module, runs one update pass. Exit 9 when updates were installed - Every step writes to C:\Windows\Setup\Scripts\Deploy.log
(reboot needed for next round). Exit 0 when fully up to date. xetup state machine handles - Format: [HH:mm:ss] Step description - OK / ERROR: ...
the reboot cycle automatically. - At end: summary report (how many steps OK, how many failed)
- Log stays on disk for diagnostics
--- ---
## Config structure ## Script switches
```json | Switch | Behavior |
{ |---|---|
"deployment": { "pcName": "", "pcDescription": "", "timezone": "...", "profileType": "default" }, | `-SkipBloatware` | Skip step 1 |
"adminAccount": { "username": "adminx9" }, | `-SkipSoftware` | Skip step 2 |
"activation": { "productKey": "", "kmsServer": "" }, | `-SkipDefaultProfile` | Skip step 4 |
"software": { "install": [{ "name": "...", "wingetId": "..." }] }, | `-DryRun` | Run through steps without changes, log only |
"steps": { "adminAccount": true, ... },
"features": { "software": { "wingetInstalls": true, "pdfDefault": true, "ateraAgent": true }, ... },
"bloatware": { "keepPackages": ["Microsoft.WindowsCalculator"] }
}
```
--- ---
## Email report ## Open questions
Sent via SMTP2Go at end of deployment. HTML with per-step status table, timestamps, | # | Question | Status |
OK/ERROR/SKIPPED counts. Subject: "xetup report HOSTNAME". |---|---|---|
From: xetup@x9.cz, To: net@x9.cz. | 1 | BackInfo replacement | DONE - custom PS scheduled task DesktopInfo |
| 2 | Complete SW list for winget | TODO |
| 3 | Per-client variability via config.json | FUTURE |
| 4 | Admin account adminx9 - script or manual? | DONE - script (00-admin-account.ps1) |

View file

@ -1,62 +1,49 @@
{ {
"deployment": {
"pcName": "",
"pcDescription": "",
"timezone": "Central Europe Standard Time",
"profileType": "default"
},
"adminAccount": {
"username": "adminx9"
},
"activation": {
"productKey": ""
},
"software": {
"install": [
{ "name": "7-Zip", "wingetId": "7zip.7zip" },
{ "name": "Adobe Acrobat Reader", "wingetId": "Adobe.Acrobat.Reader.64-bit" },
{ "name": "OpenVPN Connect", "wingetId": "OpenVPNTechnologies.OpenVPNConnect" }
]
},
"steps": { "steps": {
"adminAccount": true, "adminAccount": true,
"activation": true,
"bloatware": true, "bloatware": true,
"software": true, "software": true,
"systemRegistry": true, "systemRegistry": true,
"defaultProfile": true, "defaultProfile": true,
"backinfo": true, "personalization": true,
"network": true, "scheduledTasks": true,
"dellUpdate": true, "desktopInfo": true,
"pcIdentity": true, "activation": true
"windowsUpdate": 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": ""
}, },
"features": {
"software": { "software": {
"wingetInstalls": true, "install": [
"pdfDefault": true, { "name": "7-Zip", "wingetId": "7zip.7zip" },
"ateraAgent": true { "name": "Adobe Acrobat Reader","wingetId": "Adobe.Acrobat.Reader.64-bit" },
}, { "name": "OpenVPN Connect", "wingetId": "OpenVPNTechnologies.OpenVPNConnect" }
"systemRegistry": { ]
"systemTweaks": true,
"edgePolicies": true,
"oneDriveUninstall": true,
"powercfg": true,
"proxyDisable": true
},
"defaultProfile": {
"taskbarTweaks": true,
"startMenuTweaks": true,
"explorerTweaks": true
},
"dellUpdate": {
"drivers": true,
"bios": true
}
}, },
"bloatware": { "bloatware": {
"keepPackages": [ "keepPackages": [
"Microsoft.WindowsCalculator" "Microsoft.WindowsCalculator"
] ]
},
"desktopInfo": {
"enabled": true,
"position": "bottomRight",
"fontSize": 12,
"fontColor": "#FFFFFF",
"backgroundColor": "transparent"
},
"pdfDefault": {
"forceAdobeReader": true,
"scheduledTaskEnabled": true
} }
} }

View file

@ -68,16 +68,18 @@ func DefaultConfig() Config {
}, },
Steps: map[string]bool{ Steps: map[string]bool{
"adminAccount": true, "adminAccount": true,
"activation": true,
"bloatware": true, "bloatware": true,
"software": true, "software": true,
"systemRegistry": true, "systemRegistry": true,
"defaultProfile": true, "defaultProfile": true,
"personalization": true,
"scheduledTasks": true,
"backinfo": true, "backinfo": true,
"network": true, "activation": true,
"dellUpdate": true, "dellUpdate": true,
"pcIdentity": true,
"windowsUpdate": true, "windowsUpdate": true,
"network": true,
"pcIdentity": true,
}, },
Features: Features{ Features: Features{
"software": { "software": {

View file

@ -27,9 +27,7 @@ import (
. "github.com/lxn/walk/declarative" . "github.com/lxn/walk/declarative"
"git.xetup.x9.cz/x9/xetup/internal/config" "git.xetup.x9.cz/x9/xetup/internal/config"
"git.xetup.x9.cz/x9/xetup/internal/preflight"
"git.xetup.x9.cz/x9/xetup/internal/prereboot" "git.xetup.x9.cz/x9/xetup/internal/prereboot"
"git.xetup.x9.cz/x9/xetup/internal/report"
"git.xetup.x9.cz/x9/xetup/internal/runner" "git.xetup.x9.cz/x9/xetup/internal/runner"
"git.xetup.x9.cz/x9/xetup/internal/state" "git.xetup.x9.cz/x9/xetup/internal/state"
) )
@ -53,8 +51,6 @@ func Run(cfg config.Config, runCfg runner.RunConfig, cfgPath string) {
cfgPath = res.cfgPath cfgPath = res.cfgPath
case "run": case "run":
runCfg.ProfileType = res.cfg.Deployment.ProfileType runCfg.ProfileType = res.cfg.Deployment.ProfileType
// Update runtime config so scripts see the user's GUI selections
_ = config.Save(res.cfg, runCfg.ConfigPath)
results, needsReboot := runPhase(runCfg, res.steps, false) results, needsReboot := runPhase(runCfg, res.steps, false)
if needsReboot { if needsReboot {
prepareRebootAndRestart(res.cfg, res.steps, results, cfgPath, runCfg) prepareRebootAndRestart(res.cfg, res.steps, results, cfgPath, runCfg)
@ -154,47 +150,12 @@ func formPhase(cfg config.Config, runCfg runner.RunConfig, cfgPath string) formR
return out return out
} }
// Run pre-flight checks and build status widgets
pfResults := preflight.RunAll()
var pfWidgets []Widget
allOK := true
for _, r := range pfResults {
color := walk.RGB(0, 140, 0)
prefix := "\u2713 "
if !r.OK {
color = walk.RGB(200, 40, 40)
prefix = "\u2717 "
allOK = false
}
text := prefix + r.Name + ": " + r.Detail
lbl := Label{Text: text, Font: Font{PointSize: 9}}
_ = color // applied after window creation
pfWidgets = append(pfWidgets, lbl)
}
_ = allOK
// Collect label pointers to set color after creation
pfLabels := make([]*walk.Label, len(pfResults))
for i := range pfWidgets {
pfWidgets[i] = Label{
AssignTo: &pfLabels[i],
Text: pfWidgets[i].(Label).Text,
Font: Font{PointSize: 9},
}
}
if err := (MainWindow{ if err := (MainWindow{
AssignTo: &mw, AssignTo: &mw,
Title: "xetup \u2014 Windows deployment", Title: "xetup \u2014 Windows deployment",
Size: Size{Width: 760, Height: 740}, Size: Size{Width: 760, Height: 740},
Layout: VBox{}, Layout: VBox{},
Children: []Widget{ Children: []Widget{
// ── Pre-flight checks ────────────────────────────────────────────
Composite{
Layout: VBox{MarginsZero: true},
Children: pfWidgets,
},
HSeparator{},
// ── Form fields ────────────────────────────────────────────────── // ── Form fields ──────────────────────────────────────────────────
Composite{ Composite{
Layout: Grid{Columns: 2}, Layout: Grid{Columns: 2},
@ -316,17 +277,6 @@ func formPhase(cfg config.Config, runCfg runner.RunConfig, cfgPath string) formR
return result return result
} }
// Apply colors to pre-flight labels (must be done after window creation)
for i, r := range pfResults {
if pfLabels[i] != nil {
if r.OK {
pfLabels[i].SetTextColor(walk.RGB(0, 140, 0))
} else {
pfLabels[i].SetTextColor(walk.RGB(200, 40, 40))
}
}
}
mw.Run() mw.Run()
return result return result
} }
@ -596,17 +546,14 @@ func donePhase(currentResults []runner.Result, prevResults []state.StepResult) {
} }
var rows []displayRow var rows []displayRow
var emailRows []report.StepResult
for _, r := range prevResults { for _, r := range prevResults {
rows = append(rows, displayRow{r.Num, r.Name, r.Status, r.Elapsed}) rows = append(rows, displayRow{r.Num, r.Name, r.Status, r.Elapsed})
emailRows = append(emailRows, report.StepResult{Num: r.Num, Name: r.Name, Status: r.Status, Elapsed: r.Elapsed})
} }
for _, r := range currentResults { for _, r := range currentResults {
if r.NeedsReboot { if r.NeedsReboot {
continue continue
} }
rows = append(rows, displayRow{r.Step.Num, r.Step.Name, r.Status, r.Elapsed}) rows = append(rows, displayRow{r.Step.Num, r.Step.Name, r.Status, r.Elapsed})
emailRows = append(emailRows, report.StepResult{Num: r.Step.Num, Name: r.Step.Name, Status: r.Status, Elapsed: r.Elapsed})
} }
ok, errs, skipped := 0, 0, 0 ok, errs, skipped := 0, 0, 0
@ -636,14 +583,6 @@ func donePhase(currentResults []runner.Result, prevResults []state.StepResult) {
summaryText := fmt.Sprintf("OK: %d CHYBY: %d PRESKOCENO: %d", ok, errs, skipped) summaryText := fmt.Sprintf("OK: %d CHYBY: %d PRESKOCENO: %d", ok, errs, skipped)
// Send email report (non-blocking, best-effort)
go func() {
if err := report.Send(emailRows); err != nil {
// Log but don't block - deployment is done
_ = err
}
}()
cancelReboot := make(chan struct{}) cancelReboot := make(chan struct{})
if err := (MainWindow{ if err := (MainWindow{

View file

@ -1,111 +0,0 @@
//go:build windows
// Package preflight runs quick environment checks before deployment starts.
// Each check returns a human-readable result; failures are warnings, not blockers.
package preflight
import (
"fmt"
"os/exec"
"strings"
"syscall"
"unsafe"
)
// Result is one pre-flight check outcome.
type Result struct {
Name string
OK bool
Detail string
}
// RunAll executes all pre-flight checks and returns results.
func RunAll() []Result {
return []Result{
checkAdmin(),
checkWinget(),
checkNetwork(),
checkDisk(),
}
}
func checkAdmin() Result {
r := Result{Name: "Spusteno jako Administrator"}
// If we got here via #Requires -RunAsAdministrator or manifested exe,
// we're admin. Double-check via net session.
cmd := exec.Command("net", "session")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
if err := cmd.Run(); err != nil {
r.OK = false
r.Detail = "Neni spusteno jako Administrator"
return r
}
r.OK = true
r.Detail = "OK"
return r
}
func checkWinget() Result {
r := Result{Name: "Winget dostupny"}
cmd := exec.Command("winget", "--version")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
out, err := cmd.CombinedOutput()
if err != nil {
r.OK = false
r.Detail = "winget nenalezen - software se nenainstaluje"
return r
}
ver := strings.TrimSpace(string(out))
r.OK = true
r.Detail = ver
return r
}
func checkNetwork() Result {
r := Result{Name: "Pripojeni k internetu"}
// Try to resolve a well-known hostname
cmd := exec.Command("powershell.exe", "-NonInteractive", "-Command",
"[System.Net.Dns]::GetHostEntry('www.google.com').AddressList[0].IPAddressToString")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
out, err := cmd.CombinedOutput()
if err != nil || strings.TrimSpace(string(out)) == "" {
r.OK = false
r.Detail = "DNS resolve selhal - Atera a winget nebudou fungovat"
return r
}
r.OK = true
r.Detail = "OK"
return r
}
func checkDisk() Result {
r := Result{Name: "Volne misto na C:"}
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getDiskFreeSpaceEx := kernel32.NewProc("GetDiskFreeSpaceExW")
var freeBytesAvailable, totalBytes, totalFreeBytes uint64
drive, _ := syscall.UTF16PtrFromString("C:\\")
ret, _, _ := getDiskFreeSpaceEx.Call(
uintptr(unsafe.Pointer(drive)),
uintptr(unsafe.Pointer(&freeBytesAvailable)),
uintptr(unsafe.Pointer(&totalBytes)),
uintptr(unsafe.Pointer(&totalFreeBytes)),
)
if ret == 0 {
r.OK = false
r.Detail = "Nelze zjistit volne misto"
return r
}
freeGB := float64(freeBytesAvailable) / (1024 * 1024 * 1024)
if freeGB < 5 {
r.OK = false
r.Detail = fmt.Sprintf("%.1f GB - malo mista (min 5 GB)", freeGB)
return r
}
r.OK = true
r.Detail = fmt.Sprintf("%.1f GB volnych", freeGB)
return r
}

View file

@ -1,13 +0,0 @@
//go:build !windows
package preflight
// Result is one pre-flight check outcome.
type Result struct {
Name string
OK bool
Detail string
}
// RunAll returns empty results on non-Windows platforms.
func RunAll() []Result { return nil }

View file

@ -1,131 +0,0 @@
// Package report sends a deployment summary email via SMTP.
package report
import (
"fmt"
"net/smtp"
"os"
"strings"
"time"
)
// SMTP2Go relay configuration for X9.cz deployment reports.
const (
smtpHost = "mail-eu.smtp2go.com"
smtpPort = "2525"
smtpUser = "xetup"
smtpPass = "M9ahxHOnJ8fM0CEF"
mailFrom = "xetup@x9.cz"
mailTo = "net@x9.cz"
)
// StepResult holds one row of the deployment report.
type StepResult struct {
Num string
Name string
Status string // OK, ERROR, SKIPPED, CANCELLED
Elapsed time.Duration
}
// Send emails the deployment report. Non-fatal: returns error but caller
// should log it and continue (deployment is already done).
func Send(results []StepResult) error {
hostname, _ := os.Hostname()
now := time.Now().Format("2006-01-02 15:04")
subject := fmt.Sprintf("xetup report %s", hostname)
body := buildHTML(results, hostname, now)
msg := strings.Join([]string{
"From: " + mailFrom,
"To: " + mailTo,
"Subject: " + subject,
"MIME-Version: 1.0",
"Content-Type: text/html; charset=UTF-8",
"",
body,
}, "\r\n")
auth := smtp.PlainAuth("", smtpUser, smtpPass, smtpHost)
return smtp.SendMail(
smtpHost+":"+smtpPort,
auth,
mailFrom,
[]string{mailTo},
[]byte(msg),
)
}
func buildHTML(results []StepResult, hostname, dateTime string) string {
var ok, errs, skipped int
var rows strings.Builder
for _, r := range results {
color := "#333"
icon := ""
switch r.Status {
case "OK":
ok++
color = "#2e7d32"
icon = "&#10003;"
case "ERROR":
errs++
color = "#c62828"
icon = "&#10007;"
default:
skipped++
color = "#9e9e9e"
icon = "&#8211;"
}
elapsed := ""
if r.Elapsed > 0 {
elapsed = r.Elapsed.Round(time.Second).String()
}
fmt.Fprintf(&rows,
`<tr><td style="padding:4px 8px;color:%s;font-size:16px;text-align:center">%s</td>`+
`<td style="padding:4px 8px;color:#666">%s</td>`+
`<td style="padding:4px 8px">%s</td>`+
`<td style="padding:4px 8px;color:%s;font-weight:bold">%s</td>`+
`<td style="padding:4px 8px;color:#999;text-align:right">%s</td></tr>`,
color, icon, r.Num, r.Name, color, r.Status, elapsed)
}
summaryColor := "#2e7d32"
summaryText := "Deployment OK"
if errs > 0 {
summaryColor = "#c62828"
summaryText = fmt.Sprintf("Deployment finished with %d error(s)", errs)
}
return fmt.Sprintf(`<!DOCTYPE html>
<html><head><meta charset="UTF-8"></head>
<body style="font-family:Segoe UI,Arial,sans-serif;margin:0;padding:20px;background:#f5f5f5">
<div style="max-width:640px;margin:0 auto;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.1)">
<div style="background:#223B47;padding:20px 24px;color:#fff">
<h1 style="margin:0;font-size:20px">xetup report</h1>
<p style="margin:6px 0 0;opacity:0.8">%s &mdash; %s</p>
</div>
<table style="width:100%%;border-collapse:collapse;margin:16px 0">
<tr style="background:#f9f9f9">
<th style="padding:6px 8px;text-align:center;width:30px"></th>
<th style="padding:6px 8px;text-align:left;width:40px">Krok</th>
<th style="padding:6px 8px;text-align:left">Nazev</th>
<th style="padding:6px 8px;text-align:left;width:70px">Status</th>
<th style="padding:6px 8px;text-align:right;width:60px">Cas</th>
</tr>
%s
</table>
<div style="padding:16px 24px;background:%s;color:#fff;text-align:center;font-weight:bold">
%s &mdash; OK: %d &nbsp; CHYBY: %d &nbsp; PRESKOCENO: %d
</div>
</div>
<p style="text-align:center;color:#999;font-size:12px;margin-top:16px">
Odeslano z xetup.exe &mdash; log: C:\Windows\Setup\Scripts\Deploy.log
</p>
</body></html>`,
hostname, dateTime,
rows.String(),
summaryColor, summaryText, ok, errs, skipped)
}

View file

@ -30,20 +30,20 @@ type Step struct {
} }
// AllSteps returns the ordered list of deployment steps. // AllSteps returns the ordered list of deployment steps.
// Order matters: activation early (unlocks features), pcIdentity late (rename
// needs reboot), windowsUpdate last (reboot cycle).
func AllSteps() []Step { func AllSteps() []Step {
return []Step{ return []Step{
{ID: "adminAccount", Num: "00", Name: "Admin ucet", ScriptName: "00-admin-account.ps1"}, {ID: "adminAccount", Num: "00", Name: "Admin ucet", ScriptName: "00-admin-account.ps1"},
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
{ID: "bloatware", Num: "01", Name: "Bloatware removal", ScriptName: "01-bloatware.ps1"}, {ID: "bloatware", Num: "01", Name: "Bloatware removal", ScriptName: "01-bloatware.ps1"},
{ID: "software", Num: "02", Name: "Software (winget)", ScriptName: "02-software.ps1"}, {ID: "software", Num: "02", Name: "Software (winget)", ScriptName: "02-software.ps1"},
{ID: "systemRegistry", Num: "03", Name: "System Registry (HKLM)", ScriptName: "03-system-registry.ps1"}, {ID: "systemRegistry", Num: "03", Name: "System Registry (HKLM)", ScriptName: "03-system-registry.ps1"},
{ID: "defaultProfile", Num: "04", Name: "Profil + personalizace", ScriptName: "04-default-profile.ps1"}, {ID: "defaultProfile", Num: "04", Name: "Default Profile", ScriptName: "04-default-profile.ps1"},
{ID: "personalization", Num: "05", Name: "Personalizace", ScriptName: "05-personalization.ps1"},
{ID: "scheduledTasks", Num: "06", Name: "Scheduled Tasks", ScriptName: "06-scheduled-tasks.ps1"},
{ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"}, {ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"},
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
{ID: "pcIdentity", Num: "09", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
{ID: "network", Num: "10", Name: "Network discovery", ScriptName: "10-network.ps1"}, {ID: "network", Num: "10", Name: "Network discovery", ScriptName: "10-network.ps1"},
{ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"}, {ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"},
{ID: "pcIdentity", Num: "09", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
{ID: "windowsUpdate", Num: "12", Name: "Windows Update", ScriptName: "12-windows-update.ps1"}, {ID: "windowsUpdate", Num: "12", Name: "Windows Update", ScriptName: "12-windows-update.ps1"},
} }
} }
@ -228,9 +228,9 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error
"-LogFile", r.cfg.LogFile, "-LogFile", r.cfg.LogFile,
} }
// Pass config path - script loads JSON itself via common.ps1 Load-Config // Pass config object as JSON string (script reads it inline)
if cfgArg != "" { if cfgArg != "" {
args = append(args, "-ConfigPath", cfgArg) args = append(args, "-Config", fmt.Sprintf("(Get-Content '%s' | ConvertFrom-Json)", cfgArg))
} }
// ProfileType for step 04 // ProfileType for step 04

View file

@ -18,12 +18,18 @@
fullname-x9-cz-s-r-o-via-adsi: Sets FullName property via [ADSI] so the account shows as "X9.cz s.r.o." in User Accounts panel, Event Viewer, and audit logs. fullname-x9-cz-s-r-o-via-adsi: Sets FullName property via [ADSI] so the account shows as "X9.cz s.r.o." in User Accounts panel, Event Viewer, and audit logs.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Account config - no password by design # Account config - no password by design

View file

@ -14,12 +14,18 @@
windows-optional-features-ps-2-0-mediapl: Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component. windows-optional-features-ps-2-0-mediapl: Disabled via Disable-WindowsOptionalFeature: PowerShell 2.0 (security risk - allows unsigned script execution bypass on older hosts), MediaPlayback, Windows Recall (AI screenshot surveillance), Snipping Tool optional component.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# 1a - AppX packages # 1a - AppX packages

View file

@ -19,12 +19,30 @@
ucpd-sys-kernel-driver-od-feb-2024-bloku: UCPD.sys (User Choice Protection Driver) is stopped before the PDF association write and restarted after. Pattern: Stop-Service ucpd -> set HKCR\.pdf -> Start-Service ucpd. Implemented in this script. ucpd-sys-kernel-driver-od-feb-2024-bloku: UCPD.sys (User Choice Protection Driver) is stopped before the PDF association write and restarted after. Pattern: Stop-Service ucpd -> set HKCR\.pdf -> Start-Service ucpd. Implemented in this script.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
function Get-Feature {
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
try {
if ($null -eq $Cfg) { return $Default }
$stepFeatures = $Cfg.features.$StepID
if ($null -eq $stepFeatures) { return $Default }
$val = $stepFeatures.$FeatureID
if ($null -eq $val) { return $Default }
return [bool]$val
} catch { return $Default }
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Check winget availability # Check winget availability
@ -61,45 +79,25 @@ if (Get-Feature $Config "software" "wingetInstalls") {
if (-not $Config -or -not $Config.software -or -not $Config.software.install) { if (-not $Config -or -not $Config.software -or -not $Config.software.install) {
Write-Log "No software list in config - skipping installs" -Level WARN Write-Log "No software list in config - skipping installs" -Level WARN
} else { } else {
$packages = @($Config.software.install) foreach ($pkg in $Config.software.install) {
Write-Log "Installing $($packages.Count) packages in parallel" -Level INFO Write-Log "Installing $($pkg.name) ($($pkg.wingetId))" -Level INFO
$result = & winget install --id $pkg.wingetId `
# Launch all winget installs as parallel background jobs
$jobs = @()
foreach ($pkg in $packages) {
Write-Log " Starting: $($pkg.name) ($($pkg.wingetId))" -Level INFO
$jobs += Start-Job -ArgumentList $pkg.wingetId, $pkg.name -ScriptBlock {
param($wingetId, $name)
$output = & winget install --id $wingetId `
--silent ` --silent `
--accept-package-agreements ` --accept-package-agreements `
--accept-source-agreements ` --accept-source-agreements `
--disable-interactivity ` --disable-interactivity `
2>&1 2>&1
[PSCustomObject]@{
Name = $name
WingetId = $wingetId
ExitCode = $LASTEXITCODE
Output = ($output -join "`n")
}
}
}
# Wait for all jobs and collect results $exitCode = $LASTEXITCODE
Write-Log " Waiting for $($jobs.Count) installs to complete..." -Level INFO if ($exitCode -eq 0) {
$jobs | Wait-Job | Out-Null Write-Log " Installed OK: $($pkg.name)" -Level OK
} elseif ($exitCode -eq -1978335189) {
foreach ($job in $jobs) { # 0x8A150011 = already installed
$r = Receive-Job -Job $job Write-Log " Already installed: $($pkg.name)" -Level OK
if ($r.ExitCode -eq 0) {
Write-Log " Installed OK: $($r.Name)" -Level OK
} elseif ($r.ExitCode -eq -1978335189) {
Write-Log " Already installed: $($r.Name)" -Level OK
} else { } else {
Write-Log " Failed: $($r.Name) (exit $($r.ExitCode))" -Level ERROR Write-Log " Failed: $($pkg.name) (exit $exitCode)" -Level ERROR
Write-Log " Output: $($r.Output)" -Level ERROR Write-Log " Output: $($result -join ' ')" -Level ERROR
} }
Remove-Job -Job $job
} }
} }
} else { } else {
@ -197,20 +195,15 @@ if (Get-Feature $Config "software" "ateraAgent") {
Invoke-WebRequest -Uri $ateraUrl -OutFile $ateraMsi -UseBasicParsing -ErrorAction Stop Invoke-WebRequest -Uri $ateraUrl -OutFile $ateraMsi -UseBasicParsing -ErrorAction Stop
Write-Log " Download complete" -Level OK Write-Log " Download complete" -Level OK
$msiProc = Start-Process msiexec -ArgumentList "/i `"$ateraMsi`" /qn" -Wait -PassThru $msiResult = & msiexec /i $ateraMsi /qn 2>&1
if ($msiProc.ExitCode -eq 0) { Start-Sleep -Seconds 5
Write-Log " Atera agent installed (msiexec exit 0)" -Level OK
} else {
Write-Log " Atera agent install exit code: $($msiProc.ExitCode)" -Level WARN
}
# Verify binary exists
$ateraExe = "$env:ProgramFiles\ATERA Networks\AteraAgent\AteraAgent.exe" $ateraExe = "$env:ProgramFiles\ATERA Networks\AteraAgent\AteraAgent.exe"
$ateraExe86 = "${env:ProgramFiles(x86)}\ATERA Networks\AteraAgent\AteraAgent.exe" if (Test-Path $ateraExe) {
if ((Test-Path $ateraExe) -or (Test-Path $ateraExe86)) { Write-Log " Atera agent installed" -Level OK
Write-Log " Atera agent binary verified" -Level OK
} else { } else {
Write-Log " Atera agent binary not found at expected paths" -Level WARN Write-Log " Atera agent install may have failed - binary not found at expected path" -Level WARN
Write-Log " msiexec output: $($msiResult -join ' ')" -Level WARN
} }
} }
catch { catch {

View file

@ -28,12 +28,30 @@
proxy-auto-detect-zakaz-autodetect-0: HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\AutoDetect = 0. Disables WPAD (Web Proxy Auto-Discovery). Eliminates startup delays from WPAD DNS lookup and prevents MITM via rogue WPAD on untrusted networks. proxy-auto-detect-zakaz-autodetect-0: HKLM\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\AutoDetect = 0. Disables WPAD (Web Proxy Auto-Discovery). Eliminates startup delays from WPAD DNS lookup and prevents MITM via rogue WPAD on untrusted networks.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
function Get-Feature {
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
try {
if ($null -eq $Cfg) { return $Default }
$stepFeatures = $Cfg.features.$StepID
if ($null -eq $stepFeatures) { return $Default }
$val = $stepFeatures.$FeatureID
if ($null -eq $val) { return $Default }
return [bool]$val
} catch { return $Default }
}
Add-Type -TypeDefinition @" Add-Type -TypeDefinition @"
using System; using System;
@ -292,60 +310,62 @@ if (Get-Feature $Config "systemRegistry" "systemTweaks") {
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
if (Get-Feature $Config "systemRegistry" "edgePolicies") { if (Get-Feature $Config "systemRegistry" "edgePolicies") {
Write-Log " Applying Edge policies" -Level INFO Write-Log " Applying Edge policies" -Level INFO
$edgeMandatory = "HKLM:\SOFTWARE\Policies\Microsoft\Edge" $edgePath = "HKLM:\SOFTWARE\Policies\Microsoft\Edge"
$edgeRecommended = "HKLM:\SOFTWARE\Policies\Microsoft\Edge\Recommended"
# --- Mandatory (user cannot override, locked in Edge UI) --- # UI / first run
# First run / default browser nag Set-Reg -Path $edgePath -Name "HideFirstRunExperience" -Value 1
Set-Reg -Path $edgeMandatory -Name "HideFirstRunExperience" -Value 1 Set-Reg -Path $edgePath -Name "DefaultBrowserSettingEnabled" -Value 0
Set-Reg -Path $edgeMandatory -Name "DefaultBrowserSettingEnabled" -Value 0
# Telemetry / feedback - always off # New tab page - disable all visual clutter
Set-Reg -Path $edgeMandatory -Name "DiagnosticData" -Value 0 Set-Reg -Path $edgePath -Name "NewTabPageContentEnabled" -Value 0 # feed / obsah
Set-Reg -Path $edgeMandatory -Name "FeedbackSurveysEnabled" -Value 0 Set-Reg -Path $edgePath -Name "NewTabPageQuickLinksEnabled" -Value 0 # rychle odkazy
Set-Reg -Path $edgePath -Name "NewTabPageBackgroundEnabled" -Value 0 # pozadi
Set-Reg -Path $edgePath -Name "NewTabPageAllowedBackgroundTypes" -Value 3 # 3 = only solid color
Set-Reg -Path $edgePath -Name "ShowRecommendationsEnabled" -Value 0
Set-Reg -Path $edgePath -Name "SpotlightExperiencesAndRecommendationsEnabled" -Value 0
Set-Reg -Path $edgePath -Name "PersonalizationReportingEnabled" -Value 0
# Shopping / rewards / sidebar
Set-Reg -Path $edgePath -Name "EdgeShoppingAssistantEnabled" -Value 0
Set-Reg -Path $edgePath -Name "ShowMicrosoftRewards" -Value 0
Set-Reg -Path $edgePath -Name "HubsSidebarEnabled" -Value 0
# Search suggestions
Set-Reg -Path $edgePath -Name "SearchSuggestEnabled" -Value 0
Set-Reg -Path $edgePath -Name "ImportOnEachLaunch" -Value 0
# Telemetry / feedback
Set-Reg -Path $edgePath -Name "DiagnosticData" -Value 0
Set-Reg -Path $edgePath -Name "FeedbackSurveysEnabled" -Value 0
Set-Reg -Path $edgePath -Name "EdgeCollectionsEnabled" -Value 0
# Toolbar buttons - show
Set-Reg -Path $edgePath -Name "FavoritesBarEnabled" -Value 1 # Favorites bar always visible
Set-Reg -Path $edgePath -Name "DownloadsButtonEnabled" -Value 1
Set-Reg -Path $edgePath -Name "HistoryButtonEnabled" -Value 1
Set-Reg -Path $edgePath -Name "PerformanceButtonEnabled" -Value 1 # Sleeping Tabs / Performance
# Toolbar buttons - hide
Set-Reg -Path $edgePath -Name "HomeButtonEnabled" -Value 0
Set-Reg -Path $edgePath -Name "SplitScreenEnabled" -Value 0
Set-Reg -Path $edgePath -Name "EdgeEDropEnabled" -Value 0 # Drop
Set-Reg -Path $edgePath -Name "WebCaptureEnabled" -Value 0 # Screenshot
Set-Reg -Path $edgePath -Name "ShareAllowed" -Value 0 # Share
# Default search engine: Google
# SearchProviderEnabled must be 1, SearchProviderName + URL set the provider
Set-Reg -Path $edgePath -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
Set-Reg -Path $edgePath -Name "DefaultSearchProviderName" -Value "Google" -Type "String"
Set-Reg -Path $edgePath -Name "DefaultSearchProviderSearchURL" `
-Value "https://www.google.com/search?q={searchTerms}" -Type "String"
# Remove other search engines (empty list = no other providers besides default)
Set-Reg -Path $edgePath -Name "ManagedSearchEngines" `
-Value '[{"is_default":true,"name":"Google","search_url":"https://www.google.com/search?q={searchTerms}","keyword":"google.com"}]' `
-Type "String"
# Disable desktop shortcut on install/update # Disable desktop shortcut on install/update
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" ` Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
-Name "CreateDesktopShortcutDefault" -Value 0 -Name "CreateDesktopShortcutDefault" -Value 0
# --- Recommended (sets default, user can change in Edge settings) ---
# New tab page - clean defaults
Set-Reg -Path $edgeRecommended -Name "NewTabPageContentEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "NewTabPageQuickLinksEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "NewTabPageBackgroundEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "NewTabPageAllowedBackgroundTypes" -Value 3
Set-Reg -Path $edgeRecommended -Name "ShowRecommendationsEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "SpotlightExperiencesAndRecommendationsEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "PersonalizationReportingEnabled" -Value 0
# Shopping / rewards / sidebar
Set-Reg -Path $edgeRecommended -Name "EdgeShoppingAssistantEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "ShowMicrosoftRewards" -Value 0
Set-Reg -Path $edgeRecommended -Name "HubsSidebarEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "EdgeCollectionsEnabled" -Value 0
# Search suggestions / import
Set-Reg -Path $edgeRecommended -Name "SearchSuggestEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "ImportOnEachLaunch" -Value 0
# Toolbar buttons - show
Set-Reg -Path $edgeRecommended -Name "FavoritesBarEnabled" -Value 1
Set-Reg -Path $edgeRecommended -Name "DownloadsButtonEnabled" -Value 1
Set-Reg -Path $edgeRecommended -Name "HistoryButtonEnabled" -Value 1
Set-Reg -Path $edgeRecommended -Name "PerformanceButtonEnabled" -Value 1
# Toolbar buttons - hide
Set-Reg -Path $edgeRecommended -Name "HomeButtonEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "SplitScreenEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "EdgeEDropEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "WebCaptureEnabled" -Value 0
Set-Reg -Path $edgeRecommended -Name "ShareAllowed" -Value 0
# Default search engine: Google
Set-Reg -Path $edgeRecommended -Name "DefaultSearchProviderEnabled" -Value 1 -Type "DWord"
Set-Reg -Path $edgeRecommended -Name "DefaultSearchProviderName" -Value "Google" -Type "String"
Set-Reg -Path $edgeRecommended -Name "DefaultSearchProviderSearchURL" `
-Value "https://www.google.com/search?q={searchTerms}" -Type "String"
} else { } else {
Write-Log "edgePolicies feature disabled - skipping" -Level INFO Write-Log "edgePolicies feature disabled - skipping" -Level INFO
} }

View file

@ -1,48 +1,63 @@
<# <#
.SYNOPSIS .SYNOPSIS
Applies registry settings to the Default User profile, current user, and sets visual theme. Applies registry settings to the Default User profile and the current logged-in user.
.DESCRIPTION .DESCRIPTION
Loads C:\Users\Default\NTUSER.DAT as a temporary hive (HKU\DefaultProfile), applies Loads C:\Users\Default\NTUSER.DAT as a temporary hive (HKU\DefaultProfile), applies
all taskbar, Start menu, Explorer, and personalization settings, then unloads it. all settings, then unloads it. Every new user account created on this machine inherits
Every new user account inherits these settings on first logon. The same settings are these settings on first logon. The same settings are applied directly to the current
applied directly to the current user's HKCU. Visual identity: dark taskbar/Start with user's HKCU. Does NOT block OneDrive re-launch - the Explorer namespace CLSID and RunOnce entries have been removed.
accent color #223B47 (deep blue-gray), light app mode, no transparency. Wallpaper is
set to a solid color matching the accent - BackInfo.exe overwrites it on every logon.
.ITEMS .ITEMS
taskbar-zarovnat-vlevo-taskbaral-0: TaskbarAl = 0 in Explorer\Advanced. Left alignment matches Windows 10 muscle memory. taskbar-zarovnat-vlevo-taskbaral-0: TaskbarAl = 0 in Explorer\Advanced. Windows 11 default is center-aligned (TaskbarAl = 1). Left alignment matches Windows 10 muscle memory and is strongly preferred by business users transitioning from Win10.
taskbar-skryt-search-copilot-task-view-w: Hides Search box, Copilot, Task View, Widgets, Chat/Teams buttons. taskbar-skryt-search-copilot-task-view-w: Hides Search box (SearchboxTaskbarMode=0), Copilot button (ShowCopilotButton=0), Task View (ShowTaskViewButton=0), Widgets (TaskbarDa=0), Chat/Teams (TaskbarMn=0). Reduces taskbar clutter to just pinned apps and running processes.
taskbar-zobrazit-vsechny-ikonky-v-tray: EnableAutoTray=0 and TrayNotify icon streams cleared. taskbar-zobrazit-vsechny-ikonky-v-tray-s: Registers scheduled task that sets EnableAutoTray=0 on logon (repeat every 1 min). Windows 11 periodically re-hides tray icons - this task forces all icons visible so users can see VPN status, antivirus, backup, etc.
taskbar-vyprazdnit-pinlist-taskbarlayout: Deploys TaskbarLayoutModification.xml per ProfileType. taskbar-vyprazdnit-pinlist-taskbarlayout: Deploys TaskbarLayoutModification.xml. ProfileType=default: empty pins (clean slate). ProfileType=admin: Explorer+PowerShell+Edge. ProfileType=user: Explorer+Edge. Lock is removed by UnlockStartLayout task 5 min after first boot so users can customize.
explorer-zobrazovat-pripony-souboru-hide: HideFileExt = 0. Shows file extensions in Explorer. explorer-zobrazovat-pripony-souboru-hide: HideFileExt = 0 in Explorer\Advanced. Shows file extensions (.docx, .exe, .pdf, .ps1) in File Explorer. Essential for recognizing file types, avoiding phishing (fake .pdf.exe), and general IT work.
explorer-otevrit-na-this-pc-launchto-1: LaunchTo = 1. Explorer opens to This PC. explorer-otevrit-na-this-pc-launchto-1: LaunchTo = 1. File Explorer opens to "This PC" (drives view) instead of Quick Access. More useful on fresh machines where Quick Access history is empty and irrelevant.
explorer-showrecent-0-showfrequent-0: ShowRecent=0, ShowFrequent=0. Hides recent/frequent from Quick Access. start-menu-vyprazdnit-piny-win11: ConfigureStartPins = {"pinnedList":[]} applied via registry. Removes all default Start menu tiles (Edge, Teams, Store, Office, Solitaire, etc.) from the Windows 11 Start grid. User starts with an empty, clean Start menu.
explorer-fullpath-1-cabinetstate: FullPath=1 in CabinetState. Full path in Explorer title bar. start-menu-zakaz-bing-vyhledavani: DisableSearchBoxSuggestions = 1 in Software\Policies\Microsoft\Windows. Disables web search, Bing suggestions, and online results in Start menu search. Search returns only local apps, files, and settings.
start-menu-vyprazdnit-piny-win11: ConfigureStartPins = {"pinnedList":[]}. Empty Start menu grid. copilot-zakaz-turnoffwindowscopilot-1: TurnOffWindowsCopilot = 1 in SOFTWARE\Policies\Microsoft\Windows\WindowsCopilot. Disables the Windows Copilot sidebar entirely. Not suitable for most client environments (data privacy, AI usage policies).
start-menu-zakaz-bing-vyhledavani: DisableSearchBoxSuggestions = 1. Local search only. numlock-zapnout-pri-startu-initialkeyboa: InitialKeyboardIndicators = 2 in Default profile. Ensures NumLock is enabled when Windows starts. Standard expectation for users working with numeric data - prevents confusion on data entry.
copilot-zakaz-turnoffwindowscopilot-1: TurnOffWindowsCopilot = 1. Disables Copilot sidebar. accent-barva-na-titulnich-listech-colorp: ColorPrevalence = 1 in Personalize key. Shows the X9.cz accent color (#223B47) on window title bars and borders. Gives all windows a consistent branded appearance.
numlock-zapnout-pri-startu: InitialKeyboardIndicators = 2. onedrive-runonce-klic-je-tady-smazat: REMOVED. The RunOnce key deletion and Explorer namespace CLSID removal were deleted - those registry tweaks prevented a freshly installed OneDrive (e.g. for M365) from launching. OneDrive AppX uninstall in step 01 is intentional; blocking re-launch is not.
system-tema-taskbar-start-dark: SystemUsesLightTheme=0. Dark shell, light apps. explorer-showrecent-0-showfrequent-0: ShowRecent=0 and ShowFrequent=0 in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer. Hides Recent files and Frequent folders from Quick Access. Privacy improvement and cleaner File Explorer on fresh deployments.
accent-barva-223b47: AccentColor 0xFF473B22, ColorPrevalence=1, taskbar/Start branded. explorer-fullpath-1-cabinetstate: FullPath=1 in HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState. Displays the full directory path (e.g. C:\Users\jan\Documents\Projekty) in the File Explorer title bar instead of just the folder name.
pruhlednost-vypnuta: EnableTransparency=0.
tapeta-jednobarevna-223b47: Solid color #223B47 via SystemParametersInfo. BackInfo overwrites on logon.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile, [string]$LogFile,
[ValidateSet("default","admin","user")] [ValidateSet("default","admin","user")]
[string]$ProfileType = "default" [string]$ProfileType = "default"
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
function Get-Feature {
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
try {
if ($null -eq $Cfg) { return $Default }
$stepFeatures = $Cfg.features.$StepID
if ($null -eq $stepFeatures) { return $Default }
$val = $stepFeatures.$FeatureID
if ($null -eq $val) { return $Default }
return [bool]$val
} catch { return $Default }
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Helpers - apply registry to both Default hive and current HKCU # Helper - apply a registry setting to both Default hive and current HKCU
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
function Grant-HiveWriteAccess { function Grant-HiveWriteAccess {
param([string]$HivePath) param([string]$HivePath) # full path e.g. "Registry::HKU\DefaultProfile\Software\..."
# Grants Administrators FullControl on a loaded hive key with restricted ACL.
try { try {
$acl = Get-Acl -Path $HivePath -ErrorAction Stop $acl = Get-Acl -Path $HivePath -ErrorAction Stop
$rule = New-Object System.Security.AccessControl.RegistryAccessRule( $rule = New-Object System.Security.AccessControl.RegistryAccessRule(
@ -62,7 +77,7 @@ function Grant-HiveWriteAccess {
function Set-ProfileReg { function Set-ProfileReg {
param( param(
[string]$SubKey, [string]$SubKey, # relative to HKCU (e.g. "Software\Microsoft\...")
[string]$Name, [string]$Name,
$Value, $Value,
[string]$Type = "DWord" [string]$Type = "DWord"
@ -77,6 +92,7 @@ function Set-ProfileReg {
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
} }
catch { catch {
# Retry after granting write access to parent key
try { try {
$parentPath = $defPath -replace '\\[^\\]+$', '' $parentPath = $defPath -replace '\\[^\\]+$', ''
if (Test-Path $parentPath) { Grant-HiveWriteAccess -HivePath $parentPath } if (Test-Path $parentPath) { Grant-HiveWriteAccess -HivePath $parentPath }
@ -104,8 +120,28 @@ function Set-ProfileReg {
} }
} }
# Accent color #223B47 stored as ABGR DWORD: 0xFF473B22 function Remove-ProfileReg {
$AccentColorABGR = 0xFF473B22 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 # Load Default profile hive
@ -126,33 +162,41 @@ if ($LASTEXITCODE -ne 0) {
Write-Log "Default hive loaded" -Level OK Write-Log "Default hive loaded" -Level OK
try { try {
# =================================================================== # -----------------------------------------------------------------------
# TASKBAR TWEAKS # Taskbar tweaks (alignment, buttons, tray, layout XML)
# =================================================================== # -----------------------------------------------------------------------
if (Get-Feature $Config "defaultProfile" "taskbarTweaks") { if (Get-Feature $Config "defaultProfile" "taskbarTweaks") {
Write-Log "Applying taskbar tweaks" -Level STEP Write-Log "Applying taskbar tweaks" -Level STEP
$tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" $tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
# Win11: align taskbar to left # 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 - set both Win10 and Win11 locations # 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" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Search" `
-Name "SearchboxTaskbarMode" -Value 0 -Name "SearchboxTaskbarMode" -Value 0
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
# Hide Task View, Widgets, Chat/Teams, Copilot buttons # Hide Task View button
Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0
# Hide Widgets button
Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0
# Hide Chat / Teams button
Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0
# Hide Copilot button
Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0 Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0
# Show all tray icons # Show all tray 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: clear cached tray icon streams # Win11 workaround: clear cached tray icon streams so all icons appear on next login
$trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify" $trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
if (Test-Path $trayNotifyKey) { if (Test-Path $trayNotifyKey) {
Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
@ -167,12 +211,21 @@ try {
} }
# Desktop icons - show This PC # Desktop icons - show This PC
# CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} = This PC / Computer
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0 -Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
# Taskbar pinned apps layout # Taskbar pinned apps layout (Win10/11)
# ProfileType: default = empty, admin = Explorer+PS+Edge, user = Explorer+Edge
# Note: TaskbarLayoutModification.xml locks the taskbar temporarily.
# UnlockStartLayout scheduled task removes the lock 5 min after first boot
# so users can then customize pins freely.
# Win11 24H2+ may require ProvisionedLayoutModification.xml format instead.
Write-Log " Writing taskbar layout (ProfileType=$ProfileType)" -Level INFO Write-Log " Writing taskbar layout (ProfileType=$ProfileType)" -Level INFO
# Ensure File Explorer shortcut exists in Default profile's Start Menu.
# On a clean Windows 11 install the System Tools folder may be missing
# from C:\Users\Default\AppData\Roaming - without it the XML pin is silently skipped.
$wsh = New-Object -ComObject WScript.Shell $wsh = New-Object -ComObject WScript.Shell
$defRoaming = "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu\Programs" $defRoaming = "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu\Programs"
@ -186,6 +239,7 @@ try {
Write-Log " Created File Explorer.lnk in Default profile Start Menu" -Level OK Write-Log " Created File Explorer.lnk in Default profile Start Menu" -Level OK
} }
# Same for PowerShell (admin profile)
if ($ProfileType -eq "admin") { if ($ProfileType -eq "admin") {
$psLnkDir = "$defRoaming\Windows PowerShell" $psLnkDir = "$defRoaming\Windows PowerShell"
$psLnk = "$psLnkDir\Windows PowerShell.lnk" $psLnk = "$psLnkDir\Windows PowerShell.lnk"
@ -211,12 +265,18 @@ try {
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/> <taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
'@ '@
} }
default { "user" {
@' @'
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"/> <taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"/>
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/> <taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
'@ '@
} }
default {
@'
<taskbar:DesktopApp DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\System Tools\File Explorer.lnk"/>
<taskbar:DesktopApp DesktopApplicationLinkPath="%PROGRAMDATA%\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk"/>
'@
} # explicit pins with Replace = no Store, no other defaults
} }
$taskbarLayoutXml = @" $taskbarLayoutXml = @"
@ -242,33 +302,43 @@ $pinList
# NumLock on startup # NumLock on startup
Set-ProfileReg -SubKey "Control Panel\Keyboard" ` Set-ProfileReg -SubKey "Control Panel\Keyboard" `
-Name "InitialKeyboardIndicators" -Value 2 -Type "String" -Name "InitialKeyboardIndicators" -Value 2 -Type "String"
# Accent color on title bars
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
-Name "ColorPrevalence" -Value 1
} else { } else {
Write-Log "taskbarTweaks feature disabled - skipping" -Level INFO Write-Log "taskbarTweaks feature disabled - skipping" -Level INFO
} }
# =================================================================== # -----------------------------------------------------------------------
# START MENU TWEAKS # Start menu tweaks (pins, Bing, Copilot, GameDVR)
# =================================================================== # -----------------------------------------------------------------------
if (Get-Feature $Config "defaultProfile" "startMenuTweaks") { if (Get-Feature $Config "defaultProfile" "startMenuTweaks") {
Write-Log "Applying Start menu tweaks" -Level STEP Write-Log "Applying Start menu tweaks" -Level STEP
# Disable Bing search suggestions in Start menu
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" ` Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" `
-Name "DisableSearchBoxSuggestions" -Value 1 -Name "DisableSearchBoxSuggestions" -Value 1
# Win11: empty Start menu pins
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" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
-Name "Start_TrackProgs" -Value 0 -Name "Start_TrackProgs" -Value 0
# Hide recently opened files/docs from Start menu
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
-Name "Start_TrackDocs" -Value 0 -Name "Start_TrackDocs" -Value 0
# Disable Copilot
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" ` Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" `
-Name "TurnOffWindowsCopilot" -Value 1 -Name "TurnOffWindowsCopilot" -Value 1
# Disable GameDVR
Set-ProfileReg -SubKey "System\GameConfigStore" ` Set-ProfileReg -SubKey "System\GameConfigStore" `
-Name "GameDVR_Enabled" -Value 0 -Name "GameDVR_Enabled" -Value 0
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" `
@ -277,72 +347,34 @@ $pinList
Write-Log "startMenuTweaks feature disabled - skipping" -Level INFO Write-Log "startMenuTweaks feature disabled - skipping" -Level INFO
} }
# =================================================================== # -----------------------------------------------------------------------
# EXPLORER TWEAKS # Explorer tweaks (file extensions, LaunchTo, ShowRecent, FullPath)
# =================================================================== # -----------------------------------------------------------------------
if (Get-Feature $Config "defaultProfile" "explorerTweaks") { if (Get-Feature $Config "defaultProfile" "explorerTweaks") {
Write-Log "Applying Explorer tweaks" -Level STEP Write-Log "Applying Explorer tweaks" -Level STEP
$advPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" $advPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
# Show file extensions in Explorer
Set-ProfileReg -SubKey $advPath -Name "HideFileExt" -Value 0 Set-ProfileReg -SubKey $advPath -Name "HideFileExt" -Value 0
# Open Explorer to This PC instead of Quick Access
Set-ProfileReg -SubKey $advPath -Name "LaunchTo" -Value 1 Set-ProfileReg -SubKey $advPath -Name "LaunchTo" -Value 1
# Hide Recent files from Quick Access
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
-Name "ShowRecent" -Value 0 -Name "ShowRecent" -Value 0
# Hide Frequent folders from Quick Access
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
-Name "ShowFrequent" -Value 0 -Name "ShowFrequent" -Value 0
# Show full path in Explorer title bar
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState" ` Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState" `
-Name "FullPath" -Value 1 -Name "FullPath" -Value 1
} else { } else {
Write-Log "explorerTweaks feature disabled - skipping" -Level INFO Write-Log "explorerTweaks feature disabled - skipping" -Level INFO
} }
# ===================================================================
# PERSONALIZATION (theme, accent color, wallpaper)
# ===================================================================
Write-Log "Applying personalization (theme, accent, wallpaper)" -Level STEP
# Dark shell (taskbar, Start), light apps
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
-Name "SystemUsesLightTheme" -Value 0
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
-Name "AppsUseLightTheme" -Value 1
# Accent color on Start and taskbar
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
-Name "ColorPrevalence" -Value 1
# Transparency disabled
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
-Name "EnableTransparency" -Value 0
# Accent color #223B47
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
-Name "AccentColor" -Value $AccentColorABGR -Type "DWord"
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
-Name "ColorizationColor" -Value $AccentColorABGR -Type "DWord"
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
-Name "ColorizationAfterglow" -Value $AccentColorABGR -Type "DWord"
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
-Name "ColorPrevalence" -Value 1
# Taskbar accent color (Explorer\Accent, not DWM)
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Accent" `
-Name "AccentColorMenu" -Value $AccentColorABGR -Type "DWord"
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Accent" `
-Name "StartColorMenu" -Value $AccentColorABGR -Type "DWord"
# Wallpaper - solid color #223B47 (BackInfo overwrites on logon)
Set-ProfileReg -SubKey "Control Panel\Colors" `
-Name "Background" -Value "34 59 71" -Type "String"
Set-ProfileReg -SubKey "Control Panel\Desktop" `
-Name "Wallpaper" -Value "" -Type "String"
Set-ProfileReg -SubKey "Control Panel\Desktop" `
-Name "WallpaperStyle" -Value "0" -Type "String"
Set-ProfileReg -SubKey "Control Panel\Desktop" `
-Name "TileWallpaper" -Value "0" -Type "String"
} }
finally { finally {
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -362,23 +394,17 @@ finally {
} }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Apply wallpaper (solid color) to current desktop session # Restart Explorer to apply taskbar/tray changes to current session
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Log "Setting desktop wallpaper to solid color" -Level INFO Write-Log "Restarting Explorer to apply taskbar changes" -Level INFO
try { try {
Add-Type -TypeDefinition @" Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
using System; Start-Sleep -Seconds 2
using System.Runtime.InteropServices; Start-Process explorer
public class WallpaperHelper { Write-Log "Explorer restarted" -Level OK
[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
Write-Log " Desktop wallpaper updated" -Level OK
} }
catch { catch {
Write-Log " Failed to update wallpaper: $_" -Level WARN 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

@ -0,0 +1,208 @@
<#
.SYNOPSIS
Sets system colors, wallpaper, and visual theme.
.DESCRIPTION
Applies X9.cz visual identity: dark taskbar/Start with accent color #223B47
(deep blue-gray), light app mode, no transparency. Wallpaper is set to a solid
color matching the accent. BackInfo.exe (Step 07) overwrites the wallpaper with
a live system info BMP on every logon - solid color is only the fallback.
.ITEMS
system-tema-taskbar-start-dark: SystemUsesLightTheme=0 in Themes\Personalize. Dark mode for shell (taskbar, Start menu, Action Center, notification area). Does NOT affect application windows - those stay light. Reduces eye strain in dim environments.
aplikacni-tema-light: AppsUseLightTheme=1. Application windows (File Explorer, Settings, Calculator, etc.) use white/light backgrounds. Majority of business applications (Office, browsers) also respect this and show light mode.
accent-barva-223b47-tmave-modroseda: AccentColor DWORD = 0xFF473B22 (stored as ABGR: A=FF, B=47, G=3B, R=22). The deep blue-gray #223B47 is the X9.cz brand color, also used as the solid wallpaper background.
accent-barva-na-start-a-taskbaru-ano: ColorPrevalence=1. Applies accent color to taskbar background and Start menu surface. The taskbar becomes the brand color instead of default black, creating a distinct recognizable look on X9.cz-deployed machines.
pruhlednost-vypnuta: EnableTransparency=0. Disables Aero translucency on taskbar and Start. Improves text readability on the taskbar, reduces subtle GPU usage, and looks more professional/consistent on business machines.
tapeta-jednobarevna-223b47-bez-obrazku: Wallpaper set to solid color #223B47 via SystemParametersInfo(SPI_SETDESKWALLPAPER). BackInfo.exe generates a BMP with hostname, username, OS, network info and sets it as wallpaper on every logon. Solid color = fallback only.
#>
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"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
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
# -----------------------------------------------------------------------
# Accent color for taskbar
# Windows taskbar reads AccentColorMenu from Explorer\Accent, NOT DWM\AccentColor.
# Without this key the taskbar keeps the Windows default accent (light blue).
# -----------------------------------------------------------------------
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent" `
-Name "AccentColorMenu" -Value $AccentColorABGR -Type "DWord"
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent" `
-Name "StartColorMenu" -Value $AccentColorABGR -Type "DWord"
# -----------------------------------------------------------------------
# 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"
# Empty Wallpaper path = solid color from Background key above.
# Without this, new users created from Default hive inherit a broken/missing
# wallpaper path and Windows falls back to black desktop.
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
-Name "Wallpaper" -Value "" -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,206 @@
<#
.SYNOPSIS
Registers logon scheduled tasks to maintain per-user settings that Windows resets.
.DESCRIPTION
Creates scheduled tasks under Task Scheduler that run at user logon (and optionally
on a timer) to enforce settings that Windows tends to revert. Tasks are registered
in the Default profile task store so new user accounts inherit them automatically.
Note: PDF-DefaultApp task has been removed - PDF default is set once during deployment.
.ITEMS
showalltrayicons-pri-logonu-kazdou-1-min: Task 'ShowAllTrayIcons': runs at logon, repeats every 1 minute. Sets HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\EnableAutoTray=0. Windows 11 re-enables auto-hiding of tray icons after updates and sometimes after logon - the 1-min repeat ensures permanent override.
unlockstartlayout-jednou-po-aplikaci-lay: Task 'UnlockStartLayout': runs once, 30 seconds after logon. Clears the Start menu layout lock bit that is set when ConfigureStartPins is applied. Without this, users cannot pin or unpin apps from Start after deployment.
pdf-defaultapp-pri-kazdem-logonu: REMOVED. PDF default is set once during deployment (step 02) with UCPD service stopped. The scheduled task is no longer needed.
#>
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"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
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
# -----------------------------------------------------------------------
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

@ -16,12 +16,18 @@
07-desktop-info-ps1-smazat-nahrazeno: 07-desktop-info.ps1 is superseded by this script. BackInfo.exe is the preferred approach - stable on Win10 and Win11, configurable via INI, already present in assets. 07-desktop-info-ps1-smazat-nahrazeno: 07-desktop-info.ps1 is superseded by this script. BackInfo.exe is the preferred approach - stable on Win10 and Win11, configurable via INI, already present in assets.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Copy BackInfo assets to Program Files # Copy BackInfo assets to Program Files

253
scripts/07-desktop-info.ps1 Normal file
View file

@ -0,0 +1,253 @@
<#
.SYNOPSIS
DEPRECATED - delete this script. Replaced by BackInfo.exe.
.DESCRIPTION
Original custom PowerShell approach to render system info onto the desktop wallpaper
using WPF (System.Windows.Media / System.Drawing). Superseded by BackInfo.exe which
is already present in assets/Backinfo/ and handles Win10/Win11 natively.
ACTION REQUIRED: Delete this file. Add a BackInfo deployment step to the master script.
.ITEMS
07-desktop-info-ps1-smazat-stary-pristup: DELETE THIS FILE. The WPF rendering approach had compatibility issues on some Windows editions and required maintaining complex PS rendering code. BackInfo.exe is a mature, stable replacement already bundled in assets/Backinfo/.
zkopirovat-assets-backinfo-do-c-program-: NEW STEP (in master script): Copy assets/Backinfo/ to C:\Program Files\Backinfo\ on the target machine. Includes BackInfo.exe, BackInfo.ini (display config), and backinfo_W11.ps1 (setup helper).
spustit-backinfo-w11-ps1-detekce-os-regi: Run backinfo_W11.ps1 after file copy. Detects Win10 vs Win11, writes the required registry key for wallpaper rendering compatibility, and creates a Startup shortcut in the All Users Startup folder.
backinfo-exe-v-assets-backinfo-k-dispozi: BackInfo.exe reads BackInfo.ini on each run. INI configures: font size and family, position of each info block, which data sources to show (hostname, username, OS version, CPU, RAM, disk, IP address, domain).
backinfo-auto-start-pri-kazdem-logonu-vi: The Startup shortcut created by backinfo_W11.ps1 ensures BackInfo.exe runs on every user logon. It re-reads live system data each time, so the wallpaper BMP always shows current information (username changes, IP changes, etc.).
#>
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"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
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

@ -17,12 +17,18 @@
typ-klice-mak-vs-kms-vs-retail: Key type selection depends on client's Microsoft licensing: MAK = volume license key activates online against Microsoft (limited activations), KMS = requires KMS server on network (VLSC subscription), Retail = individual license from Microsoft Store or OEM. typ-klice-mak-vs-kms-vs-retail: Key type selection depends on client's Microsoft licensing: MAK = volume license key activates online against Microsoft (limited activations), KMS = requires KMS server on network (VLSC subscription), Retail = individual license from Microsoft Store or OEM.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# KMS Generic Volume License Keys (GVLK) # KMS Generic Volume License Keys (GVLK)

View file

@ -17,12 +17,18 @@
cx9-vlastni-ikonka-desktop-ini: Copies X9-ikona.ico to C:\X9\ and creates Desktop.ini with IconResource entry. Sets System+Hidden attributes on Desktop.ini and ReadOnly on C:\X9\ so Explorer displays the custom folder icon. cx9-vlastni-ikonka-desktop-ini: Copies X9-ikona.ico to C:\X9\ and creates Desktop.ini with IconResource entry. Sets System+Hidden attributes on Desktop.ini and ReadOnly on C:\X9\ so Explorer displays the custom folder icon.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# C:\X9 directory structure # C:\X9 directory structure
@ -111,7 +117,6 @@ if ($Config -and $Config.deployment -and $Config.deployment.pcName) {
$pcName = $Config.deployment.pcName.Trim() $pcName = $Config.deployment.pcName.Trim()
} }
$renamed = $false
if ($pcName -and $pcName -ne "") { if ($pcName -and $pcName -ne "") {
$currentName = $env:COMPUTERNAME $currentName = $env:COMPUTERNAME
if ($currentName -eq $pcName) { if ($currentName -eq $pcName) {
@ -121,7 +126,6 @@ if ($pcName -and $pcName -ne "") {
try { try {
Rename-Computer -NewName $pcName -Force -ErrorAction Stop Rename-Computer -NewName $pcName -Force -ErrorAction Stop
Write-Log " Computer renamed to '$pcName' (restart required)" -Level OK Write-Log " Computer renamed to '$pcName' (restart required)" -Level OK
$renamed = $true
} }
catch { catch {
Write-Log " Failed to rename computer: $_" -Level ERROR Write-Log " Failed to rename computer: $_" -Level ERROR
@ -132,9 +136,3 @@ if ($pcName -and $pcName -ne "") {
} }
Write-Log "Step 9 complete" -Level OK Write-Log "Step 9 complete" -Level OK
# Signal reboot only when rename actually happened
if ($renamed) {
Write-Log "Step 9 - reboot required for rename (exit 9)" -Level OK
exit 9
}

View file

@ -15,12 +15,18 @@
zapnout-network-discovery: Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines. zapnout-network-discovery: Enables the Network Discovery firewall rule group (FPS-NB_Name-In-UDP, LLMNR, etc.) for Private and Domain profiles via Set-NetFirewallRule. Allows this PC to appear in Network Neighborhood and browse other machines.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Set network profiles to Private # Set network profiles to Private

View file

@ -17,12 +17,37 @@
bios-firmware-staging-reboot: BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed. bios-firmware-staging-reboot: BIOS and firmware updates are staged by DCU and finalize on the next system restart. The deployment already ends with a restart (step 09 - computer rename), so no extra reboot is needed.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
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 }
}
}
function Get-Feature {
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
try {
if ($null -eq $Cfg) { return $Default }
$stepFeatures = $Cfg.features.$StepID
if ($null -eq $stepFeatures) { return $Default }
$val = $stepFeatures.$FeatureID
if ($null -eq $val) { return $Default }
return [bool]$val
} catch { return $Default }
}
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Detect Dell hardware # Detect Dell hardware

View file

@ -18,12 +18,19 @@
spustit-kolo-windows-update: One update pass without reboot. Exits 9 when updates were applied (more rounds needed). Exits 0 when system is fully up to date. spustit-kolo-windows-update: One update pass without reboot. Exits 9 when updates were applied (more rounds needed). Exits 0 when system is fully up to date.
#> #>
param( param(
[string]$ConfigPath, [object]$Config,
[string]$LogFile [string]$LogFile
) )
. "$PSScriptRoot\common.ps1" $ErrorActionPreference = "Continue"
$Config = Load-Config $ConfigPath
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
Write-Output $line
}
Write-Log "=== Step 12 - Windows Update ===" -Level STEP Write-Log "=== Step 12 - Windows Update ===" -Level STEP

View file

@ -1,44 +0,0 @@
# common.ps1 - shared functions for all deployment scripts
# Dot-source at the top of each script: . "$PSScriptRoot\common.ps1"
# Requires $LogFile variable set in the calling script's scope.
$ErrorActionPreference = "Continue"
function Write-Log {
param([string]$Message, [string]$Level = "INFO")
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
if ($LogFile) {
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
Write-Output $line
}
function Get-Feature {
param([object]$Cfg, [string]$StepID, [string]$FeatureID, [bool]$Default = $true)
try {
if ($null -eq $Cfg) { return $Default }
$stepFeatures = $Cfg.features.$StepID
if ($null -eq $stepFeatures) { return $Default }
$val = $stepFeatures.$FeatureID
if ($null -eq $val) { return $Default }
return [bool]$val
} catch { return $Default }
}
function Load-Config {
param([string]$Path)
if (-not $Path -or -not (Test-Path $Path)) {
Write-Log "No config file at: $Path" -Level WARN
return $null
}
try {
$cfg = Get-Content $Path -Raw -Encoding UTF8 | ConvertFrom-Json
Write-Log "Config loaded from $Path" -Level INFO
return $cfg
}
catch {
Write-Log "Failed to parse config: $_" -Level ERROR
return $null
}
}

View file

@ -99,6 +99,7 @@ Test-Check "Windows activated" {
Write-Host "" Write-Host ""
Write-Host "--- Software ---" Write-Host "--- Software ---"
Test-Check "7-Zip installed" { Test-Check "7-Zip installed" {
(Get-AppxPackage -Name "7zip.7zip" -ErrorAction SilentlyContinue) -or
(Test-Path "${env:ProgramFiles}\7-Zip\7z.exe") -or (Test-Path "${env:ProgramFiles}\7-Zip\7z.exe") -or
(Test-Path "${env:ProgramFiles(x86)}\7-Zip\7z.exe") (Test-Path "${env:ProgramFiles(x86)}\7-Zip\7z.exe")
} }
@ -116,24 +117,6 @@ Test-Check "OpenVPN Connect installed" {
-ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "OpenVPN*" }) -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "OpenVPN*" })
} -WarnOnly } -WarnOnly
Test-Check "Atera agent installed" {
(Test-Path "$env:ProgramFiles\ATERA Networks\AteraAgent\AteraAgent.exe") -or
(Test-Path "${env:ProgramFiles(x86)}\ATERA Networks\AteraAgent\AteraAgent.exe")
} -WarnOnly
# -----------------------------------------------------------------------
# PDF default
# -----------------------------------------------------------------------
Write-Host ""
Write-Host "--- PDF default ---"
Test-Check "HKCR .pdf set to AcroExch" {
if (-not (Get-PSDrive -Name HKCR -ErrorAction SilentlyContinue)) {
New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT | Out-Null
}
$val = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)"
$val -eq "AcroExch.Document.DC"
} -WarnOnly
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Bloatware # Bloatware
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@ -183,6 +166,10 @@ Test-Check "Edge First Run hidden" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Edge" "HideFirstRunExperience") -eq 1 (Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Edge" "HideFirstRunExperience") -eq 1
} }
Test-Check "OneDrive disabled via policy" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" "DisableFileSyncNGSC") -eq 1
}
Test-Check "GameDVR disabled" { Test-Check "GameDVR disabled" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" "AllowGameDVR") -eq 0 (Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" "AllowGameDVR") -eq 0
} }
@ -191,17 +178,12 @@ Test-Check "Time zone set" {
(Get-TimeZone).Id -eq "Central Europe Standard Time" (Get-TimeZone).Id -eq "Central Europe Standard Time"
} }
Test-Check "Standby timeout AC = 0 (never)" { Test-Check "Deployment date in registry" {
$val = & powercfg /query SCHEME_CURRENT SUB_SLEEP STANDBYIDLE 2>&1 | Select-String "Current AC Power Setting Index" (Get-RegValue "HKLM:\SOFTWARE\X9\Deployment" "DeployDate") -ne $null
$val -match "0x00000000" }
} -WarnOnly
Test-Check "WPAD proxy disabled" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CurrentVersion\Internet Settings" "AutoDetect") -eq 0
} -WarnOnly
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Current user (HKCU) - profile + personalization # Current user (HKCU) - personalization
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Host "" Write-Host ""
Write-Host "--- User settings (current user) ---" Write-Host "--- User settings (current user) ---"
@ -229,60 +211,41 @@ 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" { Test-Check "This PC icon on desktop" {
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" "{20D04FE0-3AEA-1069-A2D8-08002B30309D}") -eq 0 (Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" "{20D04FE0-3AEA-1069-A2D8-08002B30309D}") -eq 0
} }
Test-Check "Start menu Recommended section hidden" {
Test-Check "Start menu Recommended hidden" {
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" "HideRecommendedSection") -eq 1 (Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" "HideRecommendedSection") -eq 1
} }
Test-Check "Start menu recently added hidden" { Test-Check "Start menu recently added hidden" {
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "Start_TrackProgs") -eq 0 (Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "Start_TrackProgs") -eq 0
} }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# BackInfo # Scheduled tasks
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Host "" Write-Host ""
Write-Host "--- BackInfo ---" Write-Host "--- Scheduled tasks ---"
Test-Check "BackInfo.exe deployed" { $tasks = @("ShowAllTrayIcons", "PDF-DefaultApp", "DesktopInfo", "UnlockStartLayout")
Test-Path "C:\Program Files\Backinfo\BackInfo.exe" foreach ($t in $tasks) {
} Test-Check "Task registered: $t" {
Get-ScheduledTask -TaskName $t -ErrorAction SilentlyContinue
Test-Check "BackInfo startup shortcut" { }
Test-Path "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\BackInfo.lnk"
}
Test-Check "BackInfo OSName registry" {
(Get-RegValue "HKLM:\SOFTWARE\BackInfo" "OSName") -ne $null
} }
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Network # DesktopInfo
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
Write-Host "" Write-Host ""
Write-Host "--- Network ---" Write-Host "--- DesktopInfo ---"
Test-Check "Network profile Private" { Test-Check "Render script exists" {
$profiles = Get-NetConnectionProfile -ErrorAction SilentlyContinue Test-Path "C:\Windows\Setup\Scripts\DesktopInfo-Render.ps1"
-not ($profiles | Where-Object { $_.NetworkCategory -ne "Private" })
} -WarnOnly
# -----------------------------------------------------------------------
# C:\X9 directory
# -----------------------------------------------------------------------
Write-Host ""
Write-Host "--- PC identity ---"
Test-Check "C:\\X9 directory exists" {
Test-Path "C:\X9"
} }
Test-Check "C:\\X9 has custom icon" { Test-Check "BMP file exists" {
Test-Path "C:\X9\desktop.ini" Test-Path "C:\Windows\Setup\Scripts\desktopinfo.bmp"
} -WarnOnly } -WarnOnly
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------