Implement full deployment script suite (steps 1-7)
- Deploy-Windows.ps1: master script with Write-Log, Invoke-Step, summary report, DryRun support - 01-bloatware.ps1: remove AppX packages, Windows Capabilities, Optional Features - 02-software.ps1: winget installs from config.json, set Adobe Reader as default PDF app - 03-system-registry.ps1: HKLM tweaks (NRO bypass, Teams, Widgets, Edge, OneDrive, GameDVR, Recall, timezone) - 04-default-profile.ps1: NTUSER.DAT changes for taskbar, Explorer, Start menu, NumLock, Copilot - 05-personalization.ps1: dark/light theme, accent color #223B47, transparency off, wallpaper - 06-scheduled-tasks.ps1: ShowAllTrayIcons, PDF-DefaultApp, UnlockStartLayout tasks - 07-desktop-info.ps1: DesktopInfo render script (System.Drawing BMP), scheduled task, deploy date registry - tests/Test-Deployment.ps1: post-deployment verification, 30+ checks - CLAUDE.md: add Czech communication preference Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fb74a820dc
commit
30d930c667
10 changed files with 1698 additions and 10 deletions
|
|
@ -38,6 +38,13 @@ windows-deployment/
|
|||
|
||||
---
|
||||
|
||||
## Communication
|
||||
|
||||
- Communicate with the user in Czech
|
||||
- Code, comments, log messages: English only (no diacritics rule still applies)
|
||||
|
||||
---
|
||||
|
||||
## Conventions and rules
|
||||
|
||||
### PowerShell
|
||||
|
|
|
|||
|
|
@ -1,2 +1,196 @@
|
|||
# TODO: master deployment script
|
||||
# See SPEC.md for full specification
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$SkipBloatware,
|
||||
[switch]$SkipSoftware,
|
||||
[switch]$SkipDefaultProfile,
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Paths
|
||||
# -----------------------------------------------------------------------
|
||||
$ScriptRoot = $PSScriptRoot
|
||||
$LogDir = "C:\Windows\Setup\Scripts"
|
||||
$LogFile = "$LogDir\Deploy.log"
|
||||
$ConfigFile = "$ScriptRoot\config\config.json"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Logging
|
||||
# -----------------------------------------------------------------------
|
||||
function Write-Log {
|
||||
param(
|
||||
[string]$Message,
|
||||
[ValidateSet("INFO","OK","ERROR","WARN","STEP")]
|
||||
[string]$Level = "INFO"
|
||||
)
|
||||
$timestamp = Get-Date -Format "HH:mm:ss"
|
||||
$line = "[$timestamp] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
switch ($Level) {
|
||||
"OK" { Write-Host $line -ForegroundColor Green }
|
||||
"ERROR" { Write-Host $line -ForegroundColor Red }
|
||||
"WARN" { Write-Host $line -ForegroundColor Yellow }
|
||||
"STEP" { Write-Host $line -ForegroundColor Cyan }
|
||||
default { Write-Host $line }
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step runner - catches errors, logs, always continues
|
||||
# -----------------------------------------------------------------------
|
||||
$StepResults = [System.Collections.Generic.List[hashtable]]::new()
|
||||
|
||||
function Invoke-Step {
|
||||
param(
|
||||
[string]$Name,
|
||||
[scriptblock]$Action
|
||||
)
|
||||
|
||||
Write-Log "---- $Name ----" -Level STEP
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Log "DryRun - skipping execution" -Level WARN
|
||||
$StepResults.Add(@{ Name = $Name; Status = "DRYRUN" })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
& $Action
|
||||
Write-Log "$Name - OK" -Level OK
|
||||
$StepResults.Add(@{ Name = $Name; Status = "OK" })
|
||||
}
|
||||
catch {
|
||||
Write-Log "$Name - ERROR: $_" -Level ERROR
|
||||
$StepResults.Add(@{ Name = $Name; Status = "ERROR" })
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Init
|
||||
# -----------------------------------------------------------------------
|
||||
if (-not (Test-Path $LogDir)) {
|
||||
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Log "========================================" -Level INFO
|
||||
Write-Log "Deploy-Windows.ps1 started" -Level INFO
|
||||
Write-Log "Computer: $env:COMPUTERNAME" -Level INFO
|
||||
Write-Log "User: $env:USERNAME" -Level INFO
|
||||
Write-Log "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level INFO
|
||||
if ($DryRun) { Write-Log "Mode: DRY RUN" -Level WARN }
|
||||
Write-Log "========================================" -Level INFO
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Load config
|
||||
# -----------------------------------------------------------------------
|
||||
$Config = $null
|
||||
Invoke-Step -Name "Load config.json" -Action {
|
||||
if (-not (Test-Path $ConfigFile)) {
|
||||
throw "config.json not found: $ConfigFile"
|
||||
}
|
||||
$script:Config = Get-Content $ConfigFile -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 1 - Bloatware removal
|
||||
# -----------------------------------------------------------------------
|
||||
if ($SkipBloatware) {
|
||||
Write-Log "Step 1 - Bloatware removal: SKIPPED (-SkipBloatware)" -Level WARN
|
||||
$StepResults.Add(@{ Name = "Step 1 - Bloatware removal"; Status = "SKIPPED" })
|
||||
} else {
|
||||
Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
|
||||
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 2 - Software installation
|
||||
# -----------------------------------------------------------------------
|
||||
if ($SkipSoftware) {
|
||||
Write-Log "Step 2 - Software installation: SKIPPED (-SkipSoftware)" -Level WARN
|
||||
$StepResults.Add(@{ Name = "Step 2 - Software installation"; Status = "SKIPPED" })
|
||||
} else {
|
||||
Invoke-Step -Name "Step 2 - Software installation" -Action {
|
||||
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 3 - System registry (HKLM)
|
||||
# -----------------------------------------------------------------------
|
||||
Invoke-Step -Name "Step 3 - System registry" -Action {
|
||||
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 4 - Default profile (NTUSER.DAT)
|
||||
# -----------------------------------------------------------------------
|
||||
if ($SkipDefaultProfile) {
|
||||
Write-Log "Step 4 - Default profile: SKIPPED (-SkipDefaultProfile)" -Level WARN
|
||||
$StepResults.Add(@{ Name = "Step 4 - Default profile"; Status = "SKIPPED" })
|
||||
} else {
|
||||
Invoke-Step -Name "Step 4 - Default profile" -Action {
|
||||
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 5 - Personalization
|
||||
# -----------------------------------------------------------------------
|
||||
Invoke-Step -Name "Step 5 - Personalization" -Action {
|
||||
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 6 - Scheduled tasks
|
||||
# -----------------------------------------------------------------------
|
||||
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
|
||||
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Step 7 - DesktopInfo
|
||||
# -----------------------------------------------------------------------
|
||||
Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
|
||||
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,184 @@
|
|||
# TODO: 01-bloatware.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 1a - AppX packages
|
||||
# -----------------------------------------------------------------------
|
||||
$AppxToRemove = @(
|
||||
"Microsoft.Microsoft3DViewer"
|
||||
"Microsoft.BingSearch"
|
||||
"Microsoft.WindowsCamera"
|
||||
"Clipchamp.Clipchamp"
|
||||
"Microsoft.WindowsAlarms"
|
||||
"Microsoft.Copilot"
|
||||
"Microsoft.549981C3F5F10"
|
||||
"Microsoft.Windows.DevHome"
|
||||
"MicrosoftCorporationII.MicrosoftFamily"
|
||||
"Microsoft.WindowsFeedbackHub"
|
||||
"Microsoft.Edge.GameAssist"
|
||||
"Microsoft.GetHelp"
|
||||
"Microsoft.Getstarted"
|
||||
"microsoft.windowscommunicationsapps"
|
||||
"Microsoft.WindowsMaps"
|
||||
"Microsoft.MixedReality.Portal"
|
||||
"Microsoft.BingNews"
|
||||
"Microsoft.MicrosoftOfficeHub"
|
||||
"Microsoft.Office.OneNote"
|
||||
"Microsoft.OutlookForWindows"
|
||||
"Microsoft.Paint"
|
||||
"Microsoft.MSPaint"
|
||||
"Microsoft.People"
|
||||
"Microsoft.Windows.Photos"
|
||||
"Microsoft.PowerAutomateDesktop"
|
||||
"MicrosoftCorporationII.QuickAssist"
|
||||
"Microsoft.SkypeApp"
|
||||
"Microsoft.ScreenSketch"
|
||||
"Microsoft.MicrosoftSolitaireCollection"
|
||||
"Microsoft.MicrosoftStickyNotes"
|
||||
"MicrosoftTeams"
|
||||
"MSTeams"
|
||||
"Microsoft.Todos"
|
||||
"Microsoft.WindowsSoundRecorder"
|
||||
"Microsoft.Wallet"
|
||||
"Microsoft.BingWeather"
|
||||
"Microsoft.WindowsTerminal"
|
||||
"Microsoft.Xbox.TCUI"
|
||||
"Microsoft.XboxApp"
|
||||
"Microsoft.XboxGameOverlay"
|
||||
"Microsoft.XboxGamingOverlay"
|
||||
"Microsoft.XboxIdentityProvider"
|
||||
"Microsoft.XboxSpeechToTextOverlay"
|
||||
"Microsoft.GamingApp"
|
||||
"Microsoft.YourPhone"
|
||||
"Microsoft.ZuneMusic"
|
||||
"Microsoft.ZuneVideo"
|
||||
)
|
||||
|
||||
# Packages to always keep
|
||||
$KeepPackages = @("Microsoft.WindowsCalculator")
|
||||
if ($Config -and $Config.bloatware -and $Config.bloatware.keepPackages) {
|
||||
$KeepPackages += $Config.bloatware.keepPackages
|
||||
}
|
||||
$KeepPackages = $KeepPackages | Select-Object -Unique
|
||||
|
||||
Write-Log "1a - Removing AppX packages" -Level STEP
|
||||
|
||||
foreach ($pkg in $AppxToRemove) {
|
||||
if ($KeepPackages -contains $pkg) {
|
||||
Write-Log " KEEP $pkg" -Level INFO
|
||||
continue
|
||||
}
|
||||
|
||||
# Installed packages (current user + all users)
|
||||
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
|
||||
if ($installed) {
|
||||
try {
|
||||
$installed | Remove-AppxPackage -AllUsers -ErrorAction Stop
|
||||
Write-Log " Removed AppxPackage: $pkg" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to remove AppxPackage $pkg - $_" -Level WARN
|
||||
}
|
||||
}
|
||||
|
||||
# Provisioned packages (for new users)
|
||||
$provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.DisplayName -eq $pkg }
|
||||
if ($provisioned) {
|
||||
try {
|
||||
$provisioned | Remove-AppxProvisionedPackage -Online -ErrorAction Stop | Out-Null
|
||||
Write-Log " Removed provisioned: $pkg" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to remove provisioned $pkg - $_" -Level WARN
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $installed -and -not $provisioned) {
|
||||
Write-Log " Not found (already removed): $pkg" -Level INFO
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 1b - Windows Capabilities
|
||||
# -----------------------------------------------------------------------
|
||||
$CapabilitiesToRemove = @(
|
||||
"Print.Fax.Scan"
|
||||
"Language.Handwriting"
|
||||
"Browser.InternetExplorer"
|
||||
"MathRecognizer"
|
||||
"OneCoreUAP.OneSync"
|
||||
"OpenSSH.Client"
|
||||
"Microsoft.Windows.MSPaint"
|
||||
"Microsoft.Windows.PowerShell.ISE"
|
||||
"App.Support.QuickAssist"
|
||||
"Microsoft.Windows.SnippingTool"
|
||||
"App.StepsRecorder"
|
||||
"Hello.Face"
|
||||
"Media.WindowsMediaPlayer"
|
||||
"Microsoft.Windows.WordPad"
|
||||
)
|
||||
|
||||
Write-Log "1b - Removing Windows Capabilities" -Level STEP
|
||||
|
||||
$installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue
|
||||
|
||||
foreach ($cap in $CapabilitiesToRemove) {
|
||||
# Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0)
|
||||
$matches = $installedCaps | Where-Object {
|
||||
$_.Name -like "$cap*" -and $_.State -eq "Installed"
|
||||
}
|
||||
if ($matches) {
|
||||
foreach ($c in $matches) {
|
||||
try {
|
||||
Remove-WindowsCapability -Online -Name $c.Name -ErrorAction Stop | Out-Null
|
||||
Write-Log " Removed capability: $($c.Name)" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to remove capability $($c.Name) - $_" -Level WARN
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Log " Not found or not installed: $cap" -Level INFO
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 1c - Windows Optional Features
|
||||
# -----------------------------------------------------------------------
|
||||
$FeaturesToDisable = @(
|
||||
"MediaPlayback"
|
||||
"MicrosoftWindowsPowerShellV2Root"
|
||||
"Microsoft-RemoteDesktopConnection"
|
||||
"Recall"
|
||||
"Microsoft-SnippingTool"
|
||||
)
|
||||
|
||||
Write-Log "1c - Disabling Windows Optional Features" -Level STEP
|
||||
|
||||
foreach ($feat in $FeaturesToDisable) {
|
||||
$feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue
|
||||
if ($feature -and $feature.State -eq "Enabled") {
|
||||
try {
|
||||
Disable-WindowsOptionalFeature -Online -FeatureName $feat -NoRestart -ErrorAction Stop | Out-Null
|
||||
Write-Log " Disabled feature: $feat" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to disable feature $feat - $_" -Level WARN
|
||||
}
|
||||
} else {
|
||||
Write-Log " Not enabled or not found: $feat" -Level INFO
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Step 1 complete" -Level OK
|
||||
|
|
|
|||
|
|
@ -1 +1,122 @@
|
|||
# TODO: 02-software.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Check winget availability
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Checking winget availability" -Level INFO
|
||||
|
||||
$winget = Get-Command winget -ErrorAction SilentlyContinue
|
||||
if (-not $winget) {
|
||||
# Try to find winget in known locations
|
||||
$wingetPaths = @(
|
||||
"$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
|
||||
"$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller*\winget.exe"
|
||||
)
|
||||
foreach ($p in $wingetPaths) {
|
||||
$found = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($found) { $winget = $found.FullName; break }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $winget) {
|
||||
Write-Log "winget not found - software installation skipped" -Level ERROR
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Log "winget found: $($winget.Source -or $winget)" -Level OK
|
||||
|
||||
# Accept agreements upfront
|
||||
& winget source update --accept-source-agreements 2>&1 | Out-Null
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Install packages from config
|
||||
# -----------------------------------------------------------------------
|
||||
if (-not $Config -or -not $Config.software -or -not $Config.software.install) {
|
||||
Write-Log "No software list in config - skipping installs" -Level WARN
|
||||
} else {
|
||||
foreach ($pkg in $Config.software.install) {
|
||||
Write-Log "Installing $($pkg.name) ($($pkg.wingetId))" -Level INFO
|
||||
$result = & winget install --id $pkg.wingetId `
|
||||
--silent `
|
||||
--accept-package-agreements `
|
||||
--accept-source-agreements `
|
||||
--disable-interactivity `
|
||||
2>&1
|
||||
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Log " Installed OK: $($pkg.name)" -Level OK
|
||||
} elseif ($exitCode -eq -1978335189) {
|
||||
# 0x8A150011 = already installed
|
||||
Write-Log " Already installed: $($pkg.name)" -Level OK
|
||||
} else {
|
||||
Write-Log " Failed: $($pkg.name) (exit $exitCode)" -Level ERROR
|
||||
Write-Log " Output: $($result -join ' ')" -Level ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Set Adobe Reader as default PDF app
|
||||
# -----------------------------------------------------------------------
|
||||
$forcePdf = $true
|
||||
if ($Config -and $Config.pdfDefault) {
|
||||
$forcePdf = [bool]$Config.pdfDefault.forceAdobeReader
|
||||
}
|
||||
|
||||
if ($forcePdf) {
|
||||
Write-Log "Setting Adobe Reader as default PDF app" -Level INFO
|
||||
|
||||
# Find AcroRd32.exe
|
||||
$acroPaths = @(
|
||||
"${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||
"$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||
"${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
|
||||
)
|
||||
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
|
||||
if (-not $acroExe) {
|
||||
Write-Log " AcroRd32.exe not found - PDF default not set" -Level WARN
|
||||
} else {
|
||||
Write-Log " Found: $acroExe" -Level INFO
|
||||
|
||||
# Set file type association via HKCR (system-wide, requires admin)
|
||||
$progId = "AcroExch.Document.DC"
|
||||
$openCmd = "`"$acroExe`" `"%1`""
|
||||
|
||||
# HKCR\.pdf -> progId
|
||||
if (-not (Test-Path "HKCR:\.pdf")) {
|
||||
New-Item -Path "HKCR:\.pdf" -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId
|
||||
|
||||
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||
if (-not (Test-Path $cmdPath)) {
|
||||
New-Item -Path $cmdPath -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
|
||||
|
||||
# Also set in HKCU for current user (UserChoice)
|
||||
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
|
||||
if (-not (Test-Path $ucPath)) {
|
||||
New-Item -Path $ucPath -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId
|
||||
|
||||
Write-Log " PDF default set to AcroRd32" -Level OK
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Step 2 complete" -Level OK
|
||||
|
|
|
|||
|
|
@ -1 +1,179 @@
|
|||
# TODO: 03-system-registry.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
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 Remove-Reg {
|
||||
param([string]$Path, [string]$Name)
|
||||
try {
|
||||
if (Test-Path $Path) {
|
||||
Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue
|
||||
Write-Log " REMOVED $Path\$Name" -Level OK
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log " FAILED removing $Path\$Name - $_" -Level ERROR
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "3 - Applying HKLM system registry tweaks" -Level STEP
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Bypass Network Requirement on OOBE (BypassNRO)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" `
|
||||
-Name "BypassNRO" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disable auto-install of Teams (Chat)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" `
|
||||
-Name "ConfigureChatAutoInstall" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disable Cloud Optimized Content (ads in Start menu etc.)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||
-Name "DisableCloudOptimizedContent" -Value 1
|
||||
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||
-Name "DisableWindowsConsumerFeatures" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disable Widgets (News and Interests)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" `
|
||||
-Name "AllowNewsAndInterests" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Microsoft Edge - hide First Run Experience
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" `
|
||||
-Name "HideFirstRunExperience" -Value 1
|
||||
|
||||
# Also disable Edge desktop shortcut creation after install
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
|
||||
-Name "CreateDesktopShortcutDefault" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Password - no expiration
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log " Setting password max age to UNLIMITED" -Level INFO
|
||||
$pwResult = & net accounts /maxpwage:UNLIMITED 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log " Password max age set to UNLIMITED" -Level OK
|
||||
} else {
|
||||
Write-Log " Failed to set password max age: $pwResult" -Level ERROR
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Time zone
|
||||
# -----------------------------------------------------------------------
|
||||
$tz = "Central Europe Standard Time"
|
||||
if ($Config -and $Config.deployment -and $Config.deployment.timezone) {
|
||||
$tz = $Config.deployment.timezone
|
||||
}
|
||||
Write-Log " Setting time zone: $tz" -Level INFO
|
||||
try {
|
||||
Set-TimeZone -Id $tz -ErrorAction Stop
|
||||
Write-Log " Time zone set: $tz" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to set time zone: $_" -Level ERROR
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# OneDrive - prevent setup and remove shortcuts
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log " Disabling OneDrive" -Level INFO
|
||||
|
||||
# Disable OneDrive via policy
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" `
|
||||
-Name "DisableFileSyncNGSC" -Value 1
|
||||
|
||||
# Remove OneDriveSetup.exe if present
|
||||
$oneDrivePaths = @(
|
||||
"$env:SystemRoot\System32\OneDriveSetup.exe"
|
||||
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
|
||||
)
|
||||
foreach ($odPath in $oneDrivePaths) {
|
||||
if (Test-Path $odPath) {
|
||||
try {
|
||||
# Uninstall first
|
||||
& $odPath /uninstall 2>&1 | Out-Null
|
||||
Write-Log " OneDrive uninstalled via $odPath" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " OneDrive uninstall failed: $_" -Level WARN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Remove OneDrive Start Menu shortcut
|
||||
$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
|
||||
if (Test-Path $odLnk) {
|
||||
Remove-Item $odLnk -Force -ErrorAction SilentlyContinue
|
||||
Write-Log " Removed OneDrive Start Menu shortcut" -Level OK
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Outlook (new) - disable auto-install via UScheduler
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log " Disabling Outlook (new) auto-install" -Level INFO
|
||||
|
||||
$uschedulerPaths = @(
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate"
|
||||
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate"
|
||||
)
|
||||
foreach ($uPath in $uschedulerPaths) {
|
||||
if (Test-Path $uPath) {
|
||||
try {
|
||||
Remove-Item -Path $uPath -Recurse -Force
|
||||
Write-Log " Removed UScheduler key: $uPath" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to remove UScheduler key: $_" -Level WARN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disable GameDVR
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
|
||||
-Name "AllowGameDVR" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disable Recall (Windows AI feature)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||
-Name "DisableAIDataAnalysis" -Value 1
|
||||
|
||||
Write-Log "Step 3 complete" -Level OK
|
||||
|
|
|
|||
|
|
@ -1 +1,244 @@
|
|||
# TODO: 04-default-profile.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Helper - apply a registry setting to both Default hive and current HKCU
|
||||
# -----------------------------------------------------------------------
|
||||
function Set-ProfileReg {
|
||||
param(
|
||||
[string]$SubKey, # relative to HKCU (e.g. "Software\Microsoft\...")
|
||||
[string]$Name,
|
||||
$Value,
|
||||
[string]$Type = "DWord"
|
||||
)
|
||||
|
||||
# Apply to loaded Default hive
|
||||
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||
try {
|
||||
if (-not (Test-Path $defPath)) {
|
||||
New-Item -Path $defPath -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force
|
||||
}
|
||||
catch {
|
||||
Write-Log " DEFAULT HIVE failed $SubKey\$Name - $_" -Level ERROR
|
||||
}
|
||||
|
||||
# Apply to current user as well
|
||||
$hkcuPath = "HKCU:\$SubKey"
|
||||
try {
|
||||
if (-not (Test-Path $hkcuPath)) {
|
||||
New-Item -Path $hkcuPath -Force | Out-Null
|
||||
}
|
||||
Set-ItemProperty -Path $hkcuPath -Name $Name -Value $Value -Type $Type -Force
|
||||
Write-Log " SET $SubKey\$Name = $Value" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " HKCU failed $SubKey\$Name - $_" -Level ERROR
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-ProfileReg {
|
||||
param([string]$SubKey, [string]$Name)
|
||||
|
||||
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||
try {
|
||||
if (Test-Path $defPath) {
|
||||
Remove-ItemProperty -Path $defPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
$hkcuPath = "HKCU:\$SubKey"
|
||||
try {
|
||||
if (Test-Path $hkcuPath) {
|
||||
Remove-ItemProperty -Path $hkcuPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||
Write-Log " REMOVED $SubKey\$Name" -Level OK
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log " FAILED removing $SubKey\$Name - $_" -Level ERROR
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Load Default profile hive
|
||||
# -----------------------------------------------------------------------
|
||||
$hivePath = "C:\Users\Default\NTUSER.DAT"
|
||||
$hiveKey = "DefaultProfile"
|
||||
|
||||
Write-Log "Loading Default hive: $hivePath" -Level INFO
|
||||
|
||||
# Unload first in case previous run left it mounted
|
||||
& reg unload "HKU\$hiveKey" 2>&1 | Out-Null
|
||||
|
||||
$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Log "Failed to load Default hive: $loadResult" -Level ERROR
|
||||
exit 1
|
||||
}
|
||||
Write-Log "Default hive loaded" -Level OK
|
||||
|
||||
try {
|
||||
# -----------------------------------------------------------------------
|
||||
# Taskbar settings (Win10 + Win11)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Applying taskbar settings" -Level STEP
|
||||
|
||||
$tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||
|
||||
# Win11: align taskbar to left (0 = left, 1 = center)
|
||||
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
|
||||
|
||||
# Hide Search box / button (0 = hidden, 1 = icon, 2 = full box)
|
||||
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
|
||||
|
||||
# Hide Task View button
|
||||
Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0
|
||||
|
||||
# Hide Widgets button
|
||||
Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0
|
||||
|
||||
# Hide Chat / Teams button
|
||||
Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0
|
||||
|
||||
# Hide Copilot button
|
||||
Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0
|
||||
|
||||
# Show file extensions in Explorer
|
||||
Set-ProfileReg -SubKey $tbPath -Name "HideFileExt" -Value 0
|
||||
|
||||
# Open Explorer to This PC instead of Quick Access
|
||||
Set-ProfileReg -SubKey $tbPath -Name "LaunchTo" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# System tray - show all icons
|
||||
# -----------------------------------------------------------------------
|
||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||
-Name "EnableAutoTray" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Start menu settings
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Applying Start menu settings" -Level STEP
|
||||
|
||||
# Disable Bing search suggestions in Start menu
|
||||
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" `
|
||||
-Name "DisableSearchBoxSuggestions" -Value 1
|
||||
|
||||
# Win11: empty Start menu pins
|
||||
$startPinsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
|
||||
-Name "ConfigureStartPins" `
|
||||
-Value '{"pinnedList":[]}' `
|
||||
-Type "String"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Copilot - disable
|
||||
# -----------------------------------------------------------------------
|
||||
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" `
|
||||
-Name "TurnOffWindowsCopilot" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# GameDVR - disable
|
||||
# -----------------------------------------------------------------------
|
||||
Set-ProfileReg -SubKey "System\GameConfigStore" `
|
||||
-Name "GameDVR_Enabled" -Value 0
|
||||
|
||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" `
|
||||
-Name "AppCaptureEnabled" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Num Lock on startup
|
||||
# -----------------------------------------------------------------------
|
||||
Set-ProfileReg -SubKey "Control Panel\Keyboard" `
|
||||
-Name "InitialKeyboardIndicators" -Value 2 -Type "String"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Accent color on title bars
|
||||
# -----------------------------------------------------------------------
|
||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
|
||||
-Name "ColorPrevalence" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# OneDrive - remove RunOnce key from Default profile
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Removing OneDrive from Default profile RunOnce" -Level INFO
|
||||
Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Run" `
|
||||
-Name "OneDriveSetup"
|
||||
|
||||
Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\RunOnce" `
|
||||
-Name "Delete Cached Standalone Update Binary"
|
||||
|
||||
# Remove OneDrive from Explorer namespace (left panel)
|
||||
$oneDriveClsid = "{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
|
||||
$nsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$oneDriveClsid"
|
||||
$defNsPath = "Registry::HKU\DefaultProfile\$nsPath"
|
||||
if (Test-Path $defNsPath) {
|
||||
Remove-Item -Path $defNsPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Log " Removed OneDrive from Explorer namespace (Default)" -Level OK
|
||||
}
|
||||
$hkcuNsPath = "HKCU:\$nsPath"
|
||||
if (Test-Path $hkcuNsPath) {
|
||||
Remove-Item -Path $hkcuNsPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Log " Removed OneDrive from Explorer namespace (HKCU)" -Level OK
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Empty taskbar pinned apps (Win10/11)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Clearing taskbar pinned apps layout" -Level INFO
|
||||
|
||||
$taskbarLayoutDir = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell"
|
||||
if (-not (Test-Path $taskbarLayoutDir)) {
|
||||
New-Item -ItemType Directory -Path $taskbarLayoutDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$taskbarLayoutXml = @"
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LayoutModificationTemplate
|
||||
xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
|
||||
xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
|
||||
xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
|
||||
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
|
||||
Version="1">
|
||||
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
|
||||
<defaultlayout:TaskbarLayout>
|
||||
<taskbar:TaskbarPinList>
|
||||
</taskbar:TaskbarPinList>
|
||||
</defaultlayout:TaskbarLayout>
|
||||
</CustomTaskbarLayoutCollection>
|
||||
</LayoutModificationTemplate>
|
||||
"@
|
||||
$taskbarLayoutXml | Set-Content -Path "$taskbarLayoutDir\LayoutModification.xml" -Encoding UTF8 -Force
|
||||
Write-Log " Taskbar LayoutModification.xml written" -Level OK
|
||||
|
||||
}
|
||||
finally {
|
||||
# -----------------------------------------------------------------------
|
||||
# Unload Default hive - always, even on error
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Unloading Default hive" -Level INFO
|
||||
[GC]::Collect()
|
||||
[GC]::WaitForPendingFinalizers()
|
||||
Start-Sleep -Milliseconds 500
|
||||
|
||||
$unloadResult = & reg unload "HKU\$hiveKey" 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Log "Default hive unloaded" -Level OK
|
||||
} else {
|
||||
Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR
|
||||
}
|
||||
}
|
||||
|
||||
Write-Log "Step 4 complete" -Level OK
|
||||
|
|
|
|||
|
|
@ -1 +1,166 @@
|
|||
# TODO: 05-personalization.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
# Accent color #223B47 stored as ABGR DWORD: 0xFF473B22
|
||||
# A=FF B=47 G=3B R=22 -> 0xFF473B22 = 4283612962
|
||||
$AccentColorABGR = 0xFF473B22
|
||||
|
||||
# Gradient colors (Windows generates these automatically but we set them explicitly)
|
||||
# AccentPalette is 32 bytes - 8 shades of the accent color (BGRA each)
|
||||
# We use the same color for all shades as a safe default
|
||||
$AccentColorHex = "#223B47"
|
||||
|
||||
function Set-Reg {
|
||||
param([string]$Path, [string]$Name, $Value, [string]$Type = "DWord")
|
||||
try {
|
||||
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
|
||||
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force
|
||||
Write-Log " SET $Path\$Name = $Value" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log " FAILED $Path\$Name - $_" -Level ERROR
|
||||
}
|
||||
}
|
||||
|
||||
function Apply-ThemeSettings {
|
||||
param([string]$HiveRoot) # "HKCU:" or "Registry::HKU\DefaultProfile"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# System theme - Dark (taskbar, Start, action center)
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||
-Name "SystemUsesLightTheme" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# App theme - Light
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||
-Name "AppsUseLightTheme" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Accent color on Start and taskbar
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||
-Name "ColorPrevalence" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Transparency effects - disabled
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||
-Name "EnableTransparency" -Value 0
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Accent color
|
||||
# -----------------------------------------------------------------------
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||
-Name "AccentColor" -Value $AccentColorABGR -Type "DWord"
|
||||
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||
-Name "ColorizationColor" -Value $AccentColorABGR -Type "DWord"
|
||||
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||
-Name "ColorizationAfterglow" -Value $AccentColorABGR -Type "DWord"
|
||||
|
||||
# Accent color on title bars and borders
|
||||
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||
-Name "ColorPrevalence" -Value 1
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Wallpaper - solid color #223B47 (fallback before DesktopInfo runs)
|
||||
# -----------------------------------------------------------------------
|
||||
# Background color as decimal RGB
|
||||
Set-Reg -Path "$HiveRoot\Control Panel\Colors" `
|
||||
-Name "Background" -Value "34 59 71" -Type "String"
|
||||
|
||||
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||
-Name "WallpaperStyle" -Value "0" -Type "String"
|
||||
|
||||
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||
-Name "TileWallpaper" -Value "0" -Type "String"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -1 +1,161 @@
|
|||
# TODO: 06-scheduled-tasks.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||
if (-not (Test-Path $ScriptDir)) {
|
||||
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||
}
|
||||
|
||||
function Register-Task {
|
||||
param(
|
||||
[string]$TaskName,
|
||||
[string]$Description,
|
||||
[object]$Action,
|
||||
[object]$Trigger,
|
||||
[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 $Trigger `
|
||||
-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 + every 1 minute, sets EnableAutoTray=0 so all tray icons
|
||||
# are always visible (Win11 hides them by default)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
|
||||
|
||||
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
|
||||
@'
|
||||
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
|
||||
Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force
|
||||
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||
'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
|
||||
|
||||
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
|
||||
$showTrayTrigger = @(
|
||||
$(New-ScheduledTaskTrigger -AtLogOn),
|
||||
$(New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -Once -At (Get-Date))
|
||||
)
|
||||
|
||||
Register-Task -TaskName "ShowAllTrayIcons" `
|
||||
-Description "Show all system tray icons for current user" `
|
||||
-Action $showTrayAction `
|
||||
-Trigger $showTrayTrigger[0]
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Task: PDF-DefaultApp
|
||||
# Runs on every logon, restores .pdf -> Adobe Reader association
|
||||
# Guards against Edge overwriting it
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Registering task: PDF-DefaultApp" -Level STEP
|
||||
|
||||
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1"
|
||||
@'
|
||||
# Restore .pdf -> Adobe Reader association
|
||||
$acroPaths = @(
|
||||
"${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"
|
||||
|
||||
# Check current association
|
||||
$current = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" `
|
||||
-Name "ProgId" -ErrorAction SilentlyContinue).ProgId
|
||||
|
||||
if ($current -ne $progId) {
|
||||
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
|
||||
if (-not (Test-Path $ucPath)) { New-Item -Path $ucPath -Force | Out-Null }
|
||||
Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId -Force
|
||||
}
|
||||
'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
|
||||
|
||||
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
|
||||
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||
|
||||
Register-Task -TaskName "PDF-DefaultApp" `
|
||||
-Description "Restore Adobe Reader as default PDF app on logon" `
|
||||
-Action $pdfAction `
|
||||
-Trigger $pdfTrigger
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# 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
|
||||
|
|
|
|||
|
|
@ -1 +1,217 @@
|
|||
# TODO: 07-desktop-info.ps1
|
||||
param(
|
||||
[object]$Config,
|
||||
[string]$LogFile
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
function Write-Log {
|
||||
param([string]$Message, [string]$Level = "INFO")
|
||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||
}
|
||||
|
||||
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||
$RenderScript = "$ScriptDir\DesktopInfo-Render.ps1"
|
||||
$BmpPath = "$ScriptDir\desktopinfo.bmp"
|
||||
|
||||
if (-not (Test-Path $ScriptDir)) {
|
||||
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Read display settings from config
|
||||
# -----------------------------------------------------------------------
|
||||
$fontSize = 13
|
||||
$fontColor = "#FFFFFF"
|
||||
$position = "bottomRight"
|
||||
|
||||
if ($Config -and $Config.desktopInfo) {
|
||||
if ($Config.desktopInfo.fontSize) { $fontSize = [int]$Config.desktopInfo.fontSize }
|
||||
if ($Config.desktopInfo.fontColor) { $fontColor = $Config.desktopInfo.fontColor }
|
||||
if ($Config.desktopInfo.position) { $position = $Config.desktopInfo.position }
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Write the rendering script (runs on every logon as the user)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
|
||||
|
||||
$renderContent = @"
|
||||
# DesktopInfo-Render.ps1
|
||||
# Collects system info and renders it onto the desktop wallpaper.
|
||||
# Runs on every user logon via Scheduled Task.
|
||||
|
||||
`$ErrorActionPreference = "Continue"
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
Add-Type -TypeDefinition @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
public class WallpaperApi {
|
||||
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||
}
|
||||
'@ -ErrorAction SilentlyContinue
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Collect system info
|
||||
# -----------------------------------------------------------------------
|
||||
`$hostname = `$env:COMPUTERNAME
|
||||
`$username = `$env:USERNAME
|
||||
`$ipAddress = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||
Where-Object { `$_.IPAddress -ne "127.0.0.1" -and `$_.PrefixOrigin -ne "WellKnown" } |
|
||||
Select-Object -First 1).IPAddress
|
||||
if (-not `$ipAddress) { `$ipAddress = "N/A" }
|
||||
|
||||
`$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
||||
`$osName = if (`$osInfo) { `$osInfo.Caption -replace "Microsoft ", "" } else { "Windows" }
|
||||
`$osBuild = if (`$osInfo) { `$osInfo.BuildNumber } else { "" }
|
||||
|
||||
# Deployment date = when script was first run, stored in registry
|
||||
`$deployRegPath = "HKLM:\SOFTWARE\X9\Deployment"
|
||||
`$deployDate = (Get-ItemProperty -Path `$deployRegPath -Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate
|
||||
if (-not `$deployDate) { `$deployDate = "N/A" }
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Build info lines
|
||||
# -----------------------------------------------------------------------
|
||||
`$lines = @(
|
||||
"Computer : `$hostname"
|
||||
"User : `$username"
|
||||
"IP : `$ipAddress"
|
||||
"OS : `$osName (build `$osBuild)"
|
||||
"Deployed : `$deployDate"
|
||||
)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Render bitmap
|
||||
# -----------------------------------------------------------------------
|
||||
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue
|
||||
|
||||
`$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
||||
`$width = if (`$screen) { `$screen.Bounds.Width } else { 1920 }
|
||||
`$height = if (`$screen) { `$screen.Bounds.Height } else { 1080 }
|
||||
|
||||
`$bmp = New-Object System.Drawing.Bitmap(`$width, `$height)
|
||||
`$g = [System.Drawing.Graphics]::FromImage(`$bmp)
|
||||
|
||||
# Background: solid accent color #223B47
|
||||
`$bgColor = [System.Drawing.ColorTranslator]::FromHtml("#223B47")
|
||||
`$g.Clear(`$bgColor)
|
||||
|
||||
# Font and colors
|
||||
`$fontFamily = "Consolas"
|
||||
`$fontSize = $fontSize
|
||||
`$font = New-Object System.Drawing.Font(`$fontFamily, `$fontSize, [System.Drawing.FontStyle]::Regular)
|
||||
`$brush = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("$fontColor"))
|
||||
`$shadowBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(180, 0, 0, 0))
|
||||
|
||||
# Measure text block
|
||||
`$lineHeight = `$font.GetHeight(`$g) + 4
|
||||
`$blockH = `$lines.Count * `$lineHeight
|
||||
`$maxWidth = (`$lines | ForEach-Object { `$g.MeasureString(`$_, `$font).Width } | Measure-Object -Maximum).Maximum
|
||||
|
||||
# Position
|
||||
`$margin = 24
|
||||
`$pos = "$position"
|
||||
`$x = switch -Wildcard (`$pos) {
|
||||
"*Right" { `$width - `$maxWidth - `$margin }
|
||||
"*Left" { `$margin }
|
||||
default { `$margin }
|
||||
}
|
||||
`$y = switch -Wildcard (`$pos) {
|
||||
"bottom*" { `$height - `$blockH - `$margin }
|
||||
"top*" { `$margin }
|
||||
default { `$height - `$blockH - `$margin }
|
||||
}
|
||||
|
||||
# Draw shadow then text
|
||||
foreach (`$line in `$lines) {
|
||||
`$g.DrawString(`$line, `$font, `$shadowBrush, (`$x + 1), (`$y + 1))
|
||||
`$g.DrawString(`$line, `$font, `$brush, `$x, `$y)
|
||||
`$y += `$lineHeight
|
||||
}
|
||||
|
||||
`$g.Dispose()
|
||||
|
||||
# Save BMP
|
||||
`$bmpPath = "$BmpPath"
|
||||
`$bmp.Save(`$bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
|
||||
`$bmp.Dispose()
|
||||
|
||||
# Set as wallpaper
|
||||
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||
[WallpaperApi]::SystemParametersInfo(20, 0, `$bmpPath, 3) | Out-Null
|
||||
"@
|
||||
|
||||
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
|
||||
Write-Log "Render script written" -Level OK
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Store deployment date in registry (used by render script)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Storing deployment date in registry" -Level INFO
|
||||
try {
|
||||
if (-not (Test-Path "HKLM:\SOFTWARE\X9\Deployment")) {
|
||||
New-Item -Path "HKLM:\SOFTWARE\X9\Deployment" -Force | Out-Null
|
||||
}
|
||||
$existingDate = (Get-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||
-Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate
|
||||
if (-not $existingDate) {
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||
-Name "DeployDate" `
|
||||
-Value (Get-Date -Format "yyyy-MM-dd") `
|
||||
-Force
|
||||
Write-Log " DeployDate set: $(Get-Date -Format 'yyyy-MM-dd')" -Level OK
|
||||
} else {
|
||||
Write-Log " DeployDate already set: $existingDate" -Level INFO
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log " Failed to set DeployDate: $_" -Level ERROR
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Register scheduled task: DesktopInfo
|
||||
# Runs the render script on every user logon
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Registering task: DesktopInfo" -Level STEP
|
||||
|
||||
try {
|
||||
Unregister-ScheduledTask -TaskName "DesktopInfo" -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
|
||||
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||
-MultipleInstances IgnoreNew `
|
||||
-StartWhenAvailable
|
||||
$principal = New-ScheduledTaskPrincipal -GroupId "Users" -RunLevel Limited
|
||||
|
||||
$task = New-ScheduledTask -Action $action `
|
||||
-Trigger $trigger `
|
||||
-Settings $settings `
|
||||
-Principal $principal `
|
||||
-Description "Render system info onto desktop wallpaper on logon"
|
||||
|
||||
Register-ScheduledTask -TaskName "DesktopInfo" -InputObject $task -Force | Out-Null
|
||||
Write-Log "Task DesktopInfo registered" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log "Failed to register DesktopInfo task: $_" -Level ERROR
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Run once immediately for current user
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Log "Running DesktopInfo render now for current user" -Level INFO
|
||||
try {
|
||||
& powershell.exe -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File $RenderScript
|
||||
Write-Log "DesktopInfo rendered" -Level OK
|
||||
}
|
||||
catch {
|
||||
Write-Log "DesktopInfo render failed: $_" -Level WARN
|
||||
}
|
||||
|
||||
Write-Log "Step 7 complete" -Level OK
|
||||
|
|
|
|||
|
|
@ -1 +1,222 @@
|
|||
# TODO: Test-Deployment.ps1
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
# Post-deployment verification script.
|
||||
# Checks that all deployment steps completed correctly.
|
||||
# Outputs a pass/fail report.
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
|
||||
$PassCount = 0
|
||||
$FailCount = 0
|
||||
$WarnCount = 0
|
||||
|
||||
function Test-Check {
|
||||
param(
|
||||
[string]$Name,
|
||||
[scriptblock]$Check,
|
||||
[switch]$WarnOnly
|
||||
)
|
||||
try {
|
||||
$result = & $Check
|
||||
if ($result) {
|
||||
Write-Host " [PASS] $Name" -ForegroundColor Green
|
||||
$script:PassCount++
|
||||
} else {
|
||||
if ($WarnOnly) {
|
||||
Write-Host " [WARN] $Name" -ForegroundColor Yellow
|
||||
$script:WarnCount++
|
||||
} else {
|
||||
Write-Host " [FAIL] $Name" -ForegroundColor Red
|
||||
$script:FailCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host " [FAIL] $Name (exception: $_)" -ForegroundColor Red
|
||||
$script:FailCount++
|
||||
}
|
||||
}
|
||||
|
||||
function Get-RegValue {
|
||||
param([string]$Path, [string]$Name)
|
||||
try {
|
||||
return (Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop).$Name
|
||||
}
|
||||
catch { return $null }
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================"
|
||||
Write-Host " Deployment Verification"
|
||||
Write-Host " Computer: $env:COMPUTERNAME"
|
||||
Write-Host " Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm')"
|
||||
Write-Host "========================================"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Log file
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- Log ---"
|
||||
Test-Check "Deploy.log exists" {
|
||||
Test-Path "C:\Windows\Setup\Scripts\Deploy.log"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Software
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- Software ---"
|
||||
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(x86)}\7-Zip\7z.exe")
|
||||
}
|
||||
|
||||
Test-Check "Adobe Acrobat Reader installed" {
|
||||
(Test-Path "${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe") -or
|
||||
(Test-Path "$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe")
|
||||
}
|
||||
|
||||
Test-Check "OpenVPN Connect installed" {
|
||||
(Test-Path "$env:ProgramFiles\OpenVPN Connect\OpenVPNConnect.exe") -or
|
||||
(Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" `
|
||||
-ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like "OpenVPN*" })
|
||||
} -WarnOnly
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Bloatware
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- Bloatware removal ---"
|
||||
|
||||
$bloatwareToCheck = @(
|
||||
"Microsoft.549981C3F5F10" # Cortana
|
||||
"Microsoft.BingNews"
|
||||
"MicrosoftTeams"
|
||||
"Microsoft.XboxApp"
|
||||
"Microsoft.YourPhone"
|
||||
"Microsoft.ZuneMusic"
|
||||
"Microsoft.GamingApp"
|
||||
)
|
||||
|
||||
foreach ($pkg in $bloatwareToCheck) {
|
||||
Test-Check "Removed: $pkg" {
|
||||
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
|
||||
-not $installed
|
||||
} -WarnOnly
|
||||
}
|
||||
|
||||
Test-Check "Calculator kept" {
|
||||
Get-AppxPackage -Name "Microsoft.WindowsCalculator" -AllUsers -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# System registry (HKLM)
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- System registry ---"
|
||||
|
||||
Test-Check "BypassNRO set" {
|
||||
(Get-RegValue "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" "BypassNRO") -eq 1
|
||||
}
|
||||
|
||||
Test-Check "Teams auto-install disabled" {
|
||||
(Get-RegValue "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" "ConfigureChatAutoInstall") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Widgets disabled" {
|
||||
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" "AllowNewsAndInterests") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Edge First Run hidden" {
|
||||
(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" {
|
||||
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" "AllowGameDVR") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Time zone set" {
|
||||
(Get-TimeZone).Id -eq "Central Europe Standard Time"
|
||||
}
|
||||
|
||||
Test-Check "Deployment date in registry" {
|
||||
(Get-RegValue "HKLM:\SOFTWARE\X9\Deployment" "DeployDate") -ne $null
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Current user (HKCU) - personalization
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- User settings (current user) ---"
|
||||
|
||||
Test-Check "Dark system theme" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "SystemUsesLightTheme") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Light app theme" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "AppsUseLightTheme") -eq 1
|
||||
}
|
||||
|
||||
Test-Check "Transparency disabled" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" "EnableTransparency") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Taskbar aligned left" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "TaskbarAl") -eq 0
|
||||
} -WarnOnly
|
||||
|
||||
Test-Check "File extensions visible" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "HideFileExt") -eq 0
|
||||
}
|
||||
|
||||
Test-Check "Explorer opens to This PC" {
|
||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Scheduled tasks
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- Scheduled tasks ---"
|
||||
|
||||
$tasks = @("ShowAllTrayIcons", "PDF-DefaultApp", "DesktopInfo", "UnlockStartLayout")
|
||||
foreach ($t in $tasks) {
|
||||
Test-Check "Task registered: $t" {
|
||||
Get-ScheduledTask -TaskName $t -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# DesktopInfo
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "--- DesktopInfo ---"
|
||||
|
||||
Test-Check "Render script exists" {
|
||||
Test-Path "C:\Windows\Setup\Scripts\DesktopInfo-Render.ps1"
|
||||
}
|
||||
|
||||
Test-Check "BMP file exists" {
|
||||
Test-Path "C:\Windows\Setup\Scripts\desktopinfo.bmp"
|
||||
} -WarnOnly
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Summary
|
||||
# -----------------------------------------------------------------------
|
||||
Write-Host ""
|
||||
Write-Host "========================================"
|
||||
Write-Host " PASS: $PassCount FAIL: $FailCount WARN: $WarnCount"
|
||||
Write-Host "========================================"
|
||||
|
||||
if ($FailCount -gt 0) {
|
||||
Write-Host "Deployment verification FAILED. Review items above." -ForegroundColor Red
|
||||
exit 1
|
||||
} else {
|
||||
Write-Host "Deployment verification PASSED." -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue