Compare commits
10 commits
d853df0aa4
...
fe63de3ed7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe63de3ed7 | ||
|
|
602e51aa5b | ||
|
|
926ca301b3 | ||
|
|
6d5d6083ff | ||
|
|
e78b6d23b8 | ||
|
|
7db5b4d1e8 | ||
|
|
80a542252d | ||
| 79fcfea8df | |||
| 29e25d5905 | |||
| 3a3513c3bc |
26 changed files with 4028 additions and 174 deletions
|
|
@ -96,83 +96,117 @@ Invoke-Step -Name "Load config.json" -Action {
|
||||||
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Build step enable/disable map from config + CLI overrides
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$stepsEnabled = @{
|
||||||
|
adminAccount = $true
|
||||||
|
bloatware = $true
|
||||||
|
software = $true
|
||||||
|
systemRegistry = $true
|
||||||
|
defaultProfile = $true
|
||||||
|
personalization = $true
|
||||||
|
scheduledTasks = $true
|
||||||
|
desktopInfo = $true
|
||||||
|
activation = $true
|
||||||
|
}
|
||||||
|
if ($Config -and $Config.steps) {
|
||||||
|
foreach ($key in @($stepsEnabled.Keys)) {
|
||||||
|
$val = $Config.steps.$key
|
||||||
|
if ($null -ne $val) { $stepsEnabled[$key] = [bool]$val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# CLI switches override config.steps
|
||||||
|
if ($SkipBloatware) { $stepsEnabled['bloatware'] = $false }
|
||||||
|
if ($SkipSoftware) { $stepsEnabled['software'] = $false }
|
||||||
|
if ($SkipDefaultProfile) { $stepsEnabled['defaultProfile'] = $false }
|
||||||
|
|
||||||
|
function Skip-Step {
|
||||||
|
param([string]$Name)
|
||||||
|
Write-Log "$Name - SKIPPED (disabled in config)" -Level WARN
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "SKIPPED" })
|
||||||
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 0a - Admin account
|
# Step 0a - Admin account
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 0a - Admin account" -Action {
|
if ($stepsEnabled['adminAccount']) {
|
||||||
|
Invoke-Step -Name "Step 0a - Admin account" -Action {
|
||||||
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 0a - Admin account" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 0b - Windows activation
|
# Step 0b - Windows activation
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 0b - Windows activation" -Action {
|
if ($stepsEnabled['activation']) {
|
||||||
|
Invoke-Step -Name "Step 0b - Windows activation" -Action {
|
||||||
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 0b - Windows activation" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 1 - Bloatware removal
|
# Step 1 - Bloatware removal
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
if ($SkipBloatware) {
|
if ($stepsEnabled['bloatware']) {
|
||||||
Write-Log "Step 1 - Bloatware removal: SKIPPED (-SkipBloatware)" -Level WARN
|
|
||||||
$StepResults.Add(@{ Name = "Step 1 - Bloatware removal"; Status = "SKIPPED" })
|
|
||||||
} else {
|
|
||||||
Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
|
Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
|
||||||
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
}
|
} else { Skip-Step "Step 1 - Bloatware removal" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 2 - Software installation
|
# Step 2 - Software installation
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
if ($SkipSoftware) {
|
if ($stepsEnabled['software']) {
|
||||||
Write-Log "Step 2 - Software installation: SKIPPED (-SkipSoftware)" -Level WARN
|
|
||||||
$StepResults.Add(@{ Name = "Step 2 - Software installation"; Status = "SKIPPED" })
|
|
||||||
} else {
|
|
||||||
Invoke-Step -Name "Step 2 - Software installation" -Action {
|
Invoke-Step -Name "Step 2 - Software installation" -Action {
|
||||||
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
}
|
} else { Skip-Step "Step 2 - Software installation" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 3 - System registry (HKLM)
|
# Step 3 - System registry (HKLM)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 3 - System registry" -Action {
|
if ($stepsEnabled['systemRegistry']) {
|
||||||
|
Invoke-Step -Name "Step 3 - System registry" -Action {
|
||||||
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 3 - System registry" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 4 - Default profile (NTUSER.DAT)
|
# Step 4 - Default profile (NTUSER.DAT)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
if ($SkipDefaultProfile) {
|
if ($stepsEnabled['defaultProfile']) {
|
||||||
Write-Log "Step 4 - Default profile: SKIPPED (-SkipDefaultProfile)" -Level WARN
|
|
||||||
$StepResults.Add(@{ Name = "Step 4 - Default profile"; Status = "SKIPPED" })
|
|
||||||
} else {
|
|
||||||
Invoke-Step -Name "Step 4 - Default profile" -Action {
|
Invoke-Step -Name "Step 4 - Default profile" -Action {
|
||||||
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
}
|
} else { Skip-Step "Step 4 - Default profile" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 5 - Personalization
|
# Step 5 - Personalization
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 5 - Personalization" -Action {
|
if ($stepsEnabled['personalization']) {
|
||||||
|
Invoke-Step -Name "Step 5 - Personalization" -Action {
|
||||||
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 5 - Personalization" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 6 - Scheduled tasks
|
# Step 6 - Scheduled tasks
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
|
if ($stepsEnabled['scheduledTasks']) {
|
||||||
|
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
|
||||||
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 6 - Scheduled tasks" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 7 - DesktopInfo
|
# Step 7 - DesktopInfo
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
|
if ($stepsEnabled['desktopInfo']) {
|
||||||
|
Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
|
||||||
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile
|
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile
|
||||||
}
|
}
|
||||||
|
} else { Skip-Step "Step 7 - DesktopInfo" }
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Summary
|
# Summary
|
||||||
|
|
|
||||||
125
Run.cmd
Normal file
125
Run.cmd
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Auto-elevate to Administrator if not already elevated
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo Requesting administrator privileges...
|
||||||
|
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Paths
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
set "DEPLOY_PS1=%SCRIPT_DIR%Deploy-Windows.ps1"
|
||||||
|
set "CONFIG_JSON=%SCRIPT_DIR%config\config.json"
|
||||||
|
set "CONFIG_EDITOR=%SCRIPT_DIR%config-editor.hta"
|
||||||
|
set "LOG_FILE=C:\Windows\Setup\Scripts\Deploy.log"
|
||||||
|
|
||||||
|
:MENU
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo X9 - Windows Deployment
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
echo Config : %CONFIG_JSON%
|
||||||
|
echo Log : %LOG_FILE%
|
||||||
|
echo.
|
||||||
|
echo [1] Full deployment (uses config.json)
|
||||||
|
echo [2] Dry run (no changes, log only)
|
||||||
|
echo [3] Skip bloatware removal
|
||||||
|
echo [4] Skip software install
|
||||||
|
echo [5] Open config editor (config-editor.hta)
|
||||||
|
echo [0] Exit
|
||||||
|
echo.
|
||||||
|
set /p CHOICE=" Select [0-5]: "
|
||||||
|
|
||||||
|
if "%CHOICE%"=="0" goto EXIT
|
||||||
|
if "%CHOICE%"=="1" goto FULL
|
||||||
|
if "%CHOICE%"=="2" goto DRYRUN
|
||||||
|
if "%CHOICE%"=="3" goto SKIP_BLOATWARE
|
||||||
|
if "%CHOICE%"=="4" goto SKIP_SOFTWARE
|
||||||
|
if "%CHOICE%"=="5" goto OPEN_EDITOR
|
||||||
|
|
||||||
|
echo Invalid choice. Try again.
|
||||||
|
timeout /t 2 >nul
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [1] Full deployment
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:FULL
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting full deployment...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%"
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [2] Dry run
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DRYRUN
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting dry run (no changes will be made)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -DryRun
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [3] Skip bloatware
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_BLOATWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (bloatware removal skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipBloatware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [4] Skip software
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_SOFTWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (software install skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipSoftware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [5] Config editor
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:OPEN_EDITOR
|
||||||
|
if not exist "%CONFIG_EDITOR%" (
|
||||||
|
echo ERROR: config-editor.hta not found: %CONFIG_EDITOR%
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
start "" mshta.exe "%CONFIG_EDITOR%"
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Done
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DONE
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo Deployment finished.
|
||||||
|
echo Log: %LOG_FILE%
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:EXIT
|
||||||
|
endlocal
|
||||||
|
exit /b 0
|
||||||
632
config-editor.hta
Normal file
632
config-editor.hta
Normal file
|
|
@ -0,0 +1,632 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>X9 - Deployment Config Editor</title>
|
||||||
|
<HTA:APPLICATION
|
||||||
|
ID="ConfigEditor"
|
||||||
|
APPLICATIONNAME="X9 Config Editor"
|
||||||
|
SCROLL="no"
|
||||||
|
SINGLEINSTANCE="yes"
|
||||||
|
WINDOWSTATE="normal"
|
||||||
|
INNERBORDER="no"
|
||||||
|
SELECTION="no"
|
||||||
|
CONTEXTMENU="no"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=11">
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: Segoe UI, Arial, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #1a2a33;
|
||||||
|
color: #d0dde3;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
background: #223B47;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-bottom: 2px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#header h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e8f4f8;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
#header .config-path {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #7aabbd;
|
||||||
|
margin-top: 3px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
#tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #1a2a33;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
padding: 8px 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #7aabbd;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.tab:hover { color: #b0d4e0; }
|
||||||
|
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
|
||||||
|
#content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tab-panel { display: none; padding: 16px 18px; }
|
||||||
|
.tab-panel.active { display: block; }
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th {
|
||||||
|
background: #223B47;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid #1e3340;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tr:hover td { background: #1e3340; }
|
||||||
|
.step-num {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
.step-name { color: #d0dde3; }
|
||||||
|
.info-btn {
|
||||||
|
background: #2e5568;
|
||||||
|
border: none;
|
||||||
|
color: #7aabbd;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #4a9aba;
|
||||||
|
}
|
||||||
|
input[type="text"], select {
|
||||||
|
background: #1a2a33;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="text"]:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9aba;
|
||||||
|
}
|
||||||
|
.label-cell {
|
||||||
|
width: 160px;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-top: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
.section-title:first-child { margin-top: 0; }
|
||||||
|
.btn {
|
||||||
|
background: #223B47;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.btn:hover { background: #2e5568; color: #e8f4f8; }
|
||||||
|
.btn-danger {
|
||||||
|
background: #3d1f1f;
|
||||||
|
border-color: #6b2c2c;
|
||||||
|
color: #d08080;
|
||||||
|
}
|
||||||
|
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
|
||||||
|
.btn-add {
|
||||||
|
background: #1a3a2a;
|
||||||
|
border-color: #2e6a4a;
|
||||||
|
color: #80c8a0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.btn-add:hover { background: #235a38; color: #a0e0b8; }
|
||||||
|
#footer {
|
||||||
|
background: #223B47;
|
||||||
|
border-top: 2px solid #2e5568;
|
||||||
|
padding: 10px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#btn-save {
|
||||||
|
background: #1a5c3a;
|
||||||
|
border: 1px solid #2a8a58;
|
||||||
|
color: #80e0b0;
|
||||||
|
padding: 7px 22px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
#btn-save:hover { background: #206e46; color: #a0f0c8; }
|
||||||
|
#status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7aabbd;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#tooltip {
|
||||||
|
position: fixed;
|
||||||
|
background: #0f1e26;
|
||||||
|
border: 1px solid #3a7a9a;
|
||||||
|
color: #c0dde8;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 340px;
|
||||||
|
line-height: 1.5;
|
||||||
|
z-index: 9999;
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
|
||||||
|
.settings-grid .span2 { grid-column: 1 / -1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<h1>X9 - Deployment Config</h1>
|
||||||
|
<div class="config-path" id="config-path-display">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tabs">
|
||||||
|
<div class="tab active" onclick="showTab('steps')">Steps</div>
|
||||||
|
<div class="tab" onclick="showTab('software')">Software</div>
|
||||||
|
<div class="tab" onclick="showTab('settings')">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- STEPS TAB -->
|
||||||
|
<div class="tab-panel active" id="tab-steps">
|
||||||
|
<table id="steps-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:40px;">On</th>
|
||||||
|
<th style="width:36px;">Step</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th style="width:32px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="steps-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SOFTWARE TAB -->
|
||||||
|
<div class="tab-panel" id="tab-software">
|
||||||
|
<table id="software-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Package Name</th>
|
||||||
|
<th>Winget ID</th>
|
||||||
|
<th style="width:70px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="software-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SETTINGS TAB -->
|
||||||
|
<div class="tab-panel" id="tab-settings">
|
||||||
|
<div class="section-title">Deployment</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Timezone</div>
|
||||||
|
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Locale</div>
|
||||||
|
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Admin Account</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Username</div>
|
||||||
|
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Password</div>
|
||||||
|
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Description</div>
|
||||||
|
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">PDF Default</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Force Adobe Reader</div>
|
||||||
|
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Scheduled Task</div>
|
||||||
|
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Desktop Info</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Enabled</div>
|
||||||
|
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Position</div>
|
||||||
|
<div>
|
||||||
|
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
|
||||||
|
<option value="bottomRight">Bottom Right</option>
|
||||||
|
<option value="bottomLeft">Bottom Left</option>
|
||||||
|
<option value="topRight">Top Right</option>
|
||||||
|
<option value="topLeft">Top Left</option>
|
||||||
|
<option value="center">Center</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Size</div>
|
||||||
|
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Color</div>
|
||||||
|
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Activation</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Product Key</div>
|
||||||
|
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">KMS Server</div>
|
||||||
|
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
|
||||||
|
<div id="status">Ready.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tooltip"></div>
|
||||||
|
|
||||||
|
<script language="JScript">
|
||||||
|
|
||||||
|
var STEP_DEFS = [
|
||||||
|
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
|
||||||
|
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
|
||||||
|
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
|
||||||
|
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
|
||||||
|
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
|
||||||
|
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
|
||||||
|
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
|
||||||
|
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
|
||||||
|
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
|
||||||
|
];
|
||||||
|
|
||||||
|
var configPath = "";
|
||||||
|
var config = null;
|
||||||
|
var fso = null;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
try {
|
||||||
|
fso = new ActiveXObject("Scripting.FileSystemObject");
|
||||||
|
|
||||||
|
// Derive config path from HTA location
|
||||||
|
var htaPath = location.pathname.replace(/\//g, "\\");
|
||||||
|
// Remove leading backslash from pathname
|
||||||
|
if (htaPath.charAt(0) === "\\") {
|
||||||
|
htaPath = htaPath.substring(1);
|
||||||
|
}
|
||||||
|
var dir = fso.GetParentFolderName(htaPath);
|
||||||
|
configPath = dir + "\\config\\config.json";
|
||||||
|
|
||||||
|
document.getElementById("config-path-display").innerText = configPath;
|
||||||
|
|
||||||
|
loadConfig();
|
||||||
|
buildStepsTable();
|
||||||
|
resizeWindow();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Init error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeWindow() {
|
||||||
|
window.resizeTo(800, 720);
|
||||||
|
window.moveTo(
|
||||||
|
(screen.width - 800) / 2,
|
||||||
|
(screen.height - 720) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
if (!fso.FileExists(configPath)) {
|
||||||
|
setStatus("config.json not found: " + configPath, true);
|
||||||
|
config = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var f = fso.OpenTextFile(configPath, 1, false, -1);
|
||||||
|
var raw = f.ReadAll();
|
||||||
|
f.Close();
|
||||||
|
config = JSON.parse(raw);
|
||||||
|
setStatus("Loaded: " + configPath);
|
||||||
|
populateSettings();
|
||||||
|
buildSoftwareTable();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Load error: " + e.message, true);
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStepsTable() {
|
||||||
|
var tbody = document.getElementById("steps-tbody");
|
||||||
|
var html = "";
|
||||||
|
for (var i = 0; i < STEP_DEFS.length; i++) {
|
||||||
|
var s = STEP_DEFS[i];
|
||||||
|
var checked = "";
|
||||||
|
// Check config.steps if loaded, default true
|
||||||
|
if (config && config.steps && config.steps[s.key] === false) {
|
||||||
|
checked = "";
|
||||||
|
} else {
|
||||||
|
checked = "checked";
|
||||||
|
}
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
|
||||||
|
html += "<td class='step-num'>" + s.num + "</td>";
|
||||||
|
html += "<td class='step-name'>" + s.label + "</td>";
|
||||||
|
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
|
||||||
|
html += "</tr>";
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSoftwareTable() {
|
||||||
|
var tbody = document.getElementById("software-tbody");
|
||||||
|
var html = "";
|
||||||
|
if (config && config.software && config.software.install) {
|
||||||
|
var list = config.software.install;
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSoftwareRow(idx, name, wingetId) {
|
||||||
|
return "<tr id='sw-row-" + idx + "'>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSoftwareRow() {
|
||||||
|
if (!config.software) { config.software = {}; }
|
||||||
|
if (!config.software.install) { config.software.install = []; }
|
||||||
|
config.software.install.push({ name: "", wingetId: "" });
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package added. Fill in name and Winget ID, then save.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSoftwareRow(idx) {
|
||||||
|
if (!config || !config.software || !config.software.install) { return; }
|
||||||
|
config.software.install.splice(idx, 1);
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package removed. Click Save to persist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareName(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].name = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareId(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].wingetId = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateSettings() {
|
||||||
|
if (!config) { return; }
|
||||||
|
|
||||||
|
if (config.deployment) {
|
||||||
|
setVal("s-timezone", config.deployment.timezone || "");
|
||||||
|
setVal("s-locale", config.deployment.locale || "");
|
||||||
|
}
|
||||||
|
if (config.adminAccount) {
|
||||||
|
setVal("s-admin-user", config.adminAccount.username || "");
|
||||||
|
setVal("s-admin-pass", config.adminAccount.password || "");
|
||||||
|
setVal("s-admin-desc", config.adminAccount.description || "");
|
||||||
|
}
|
||||||
|
if (config.pdfDefault) {
|
||||||
|
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
|
||||||
|
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
|
||||||
|
}
|
||||||
|
if (config.desktopInfo) {
|
||||||
|
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
|
||||||
|
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
|
||||||
|
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
|
||||||
|
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
|
||||||
|
}
|
||||||
|
if (config.activation) {
|
||||||
|
setVal("s-act-key", config.activation.productKey || "");
|
||||||
|
setVal("s-act-kms", config.activation.kmsServer || "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.value = val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChk(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.checked = !!val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (!el) { return; }
|
||||||
|
for (var i = 0; i < el.options.length; i++) {
|
||||||
|
if (el.options[i].value === val) {
|
||||||
|
el.selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStep(key, enabled) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
config.steps[key] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSetting(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingBool(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = !!val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingInt(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
var n = parseInt(val, 10);
|
||||||
|
config[section][key] = isNaN(n) ? val : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
if (!config) { setStatus("No config loaded.", true); return; }
|
||||||
|
try {
|
||||||
|
// Sync steps checkboxes before save (in case table was rebuilt)
|
||||||
|
syncStepsFromTable();
|
||||||
|
|
||||||
|
var json = JSON.stringify(config, null, 2);
|
||||||
|
var f = fso.CreateTextFile(configPath, true, true);
|
||||||
|
f.Write(json);
|
||||||
|
f.Close();
|
||||||
|
setStatus("Saved: " + configPath + " [" + now() + "]");
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Save error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncStepsFromTable() {
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var chk = rows[i].getElementsByTagName("input")[0];
|
||||||
|
if (chk && STEP_DEFS[i]) {
|
||||||
|
config.steps[STEP_DEFS[i].key] = chk.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(name) {
|
||||||
|
var panels = document.getElementsByClassName("tab-panel");
|
||||||
|
for (var i = 0; i < panels.length; i++) {
|
||||||
|
panels[i].className = "tab-panel";
|
||||||
|
}
|
||||||
|
var tabs = document.getElementsByClassName("tab");
|
||||||
|
for (var i = 0; i < tabs.length; i++) {
|
||||||
|
tabs[i].className = "tab";
|
||||||
|
}
|
||||||
|
document.getElementById("tab-" + name).className = "tab-panel active";
|
||||||
|
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
|
||||||
|
var nameMap = { steps: 0, software: 1, settings: 2 };
|
||||||
|
if (nameMap[name] !== undefined) {
|
||||||
|
allTabs[nameMap[name]].className = "tab active";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(evt, text) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
t.innerText = text;
|
||||||
|
t.style.display = "block";
|
||||||
|
positionTooltip(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionTooltip(evt) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
var x = evt.clientX + 12;
|
||||||
|
var y = evt.clientY + 12;
|
||||||
|
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
|
||||||
|
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
|
||||||
|
t.style.left = x + "px";
|
||||||
|
t.style.top = y + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTooltip() {
|
||||||
|
document.getElementById("tooltip").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(msg, isError) {
|
||||||
|
var el = document.getElementById("status");
|
||||||
|
el.innerText = msg;
|
||||||
|
el.style.color = isError ? "#d08080" : "#7aabbd";
|
||||||
|
}
|
||||||
|
|
||||||
|
function now() {
|
||||||
|
var d = new Date();
|
||||||
|
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
||||||
|
|
||||||
|
function escapeQ(s) {
|
||||||
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() { init(); };
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,4 +1,15 @@
|
||||||
{
|
{
|
||||||
|
"steps": {
|
||||||
|
"adminAccount": true,
|
||||||
|
"bloatware": true,
|
||||||
|
"software": true,
|
||||||
|
"systemRegistry": true,
|
||||||
|
"defaultProfile": true,
|
||||||
|
"personalization": true,
|
||||||
|
"scheduledTasks": true,
|
||||||
|
"desktopInfo": true,
|
||||||
|
"activation": true
|
||||||
|
},
|
||||||
"deployment": {
|
"deployment": {
|
||||||
"timezone": "Central Europe Standard Time",
|
"timezone": "Central Europe Standard Time",
|
||||||
"locale": "cs-CZ"
|
"locale": "cs-CZ"
|
||||||
|
|
|
||||||
244
flash/Deploy-Windows.ps1
Normal file
244
flash/Deploy-Windows.ps1
Normal file
|
|
@ -0,0 +1,244 @@
|
||||||
|
#Requires -RunAsAdministrator
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[switch]$SkipBloatware,
|
||||||
|
[switch]$SkipSoftware,
|
||||||
|
[switch]$SkipDefaultProfile,
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Paths
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$ScriptRoot = $PSScriptRoot
|
||||||
|
$LogDir = "C:\Windows\Setup\Scripts"
|
||||||
|
$LogFile = "$LogDir\Deploy.log"
|
||||||
|
$ConfigFile = "$ScriptRoot\config\config.json"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
function Write-Log {
|
||||||
|
param(
|
||||||
|
[string]$Message,
|
||||||
|
[ValidateSet("INFO","OK","ERROR","WARN","STEP")]
|
||||||
|
[string]$Level = "INFO"
|
||||||
|
)
|
||||||
|
$timestamp = Get-Date -Format "HH:mm:ss"
|
||||||
|
$line = "[$timestamp] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
switch ($Level) {
|
||||||
|
"OK" { Write-Host $line -ForegroundColor Green }
|
||||||
|
"ERROR" { Write-Host $line -ForegroundColor Red }
|
||||||
|
"WARN" { Write-Host $line -ForegroundColor Yellow }
|
||||||
|
"STEP" { Write-Host $line -ForegroundColor Cyan }
|
||||||
|
default { Write-Host $line }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step runner - catches errors, logs, always continues
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$StepResults = [System.Collections.Generic.List[hashtable]]::new()
|
||||||
|
|
||||||
|
function Invoke-Step {
|
||||||
|
param(
|
||||||
|
[string]$Name,
|
||||||
|
[scriptblock]$Action
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "---- $Name ----" -Level STEP
|
||||||
|
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Log "DryRun - skipping execution" -Level WARN
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "DRYRUN" })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
& $Action
|
||||||
|
Write-Log "$Name - OK" -Level OK
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "OK" })
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "$Name - ERROR: $_" -Level ERROR
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "ERROR" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Init
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (-not (Test-Path $LogDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
Write-Log "Deploy-Windows.ps1 started" -Level INFO
|
||||||
|
Write-Log "Computer: $env:COMPUTERNAME" -Level INFO
|
||||||
|
Write-Log "User: $env:USERNAME" -Level INFO
|
||||||
|
Write-Log "Date: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -Level INFO
|
||||||
|
if ($DryRun) { Write-Log "Mode: DRY RUN" -Level WARN }
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load config
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$Config = $null
|
||||||
|
Invoke-Step -Name "Load config.json" -Action {
|
||||||
|
if (-not (Test-Path $ConfigFile)) {
|
||||||
|
throw "config.json not found: $ConfigFile"
|
||||||
|
}
|
||||||
|
$script:Config = Get-Content $ConfigFile -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||||
|
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Build step enable/disable map from config + CLI overrides
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$stepsEnabled = @{
|
||||||
|
adminAccount = $true
|
||||||
|
bloatware = $true
|
||||||
|
software = $true
|
||||||
|
systemRegistry = $true
|
||||||
|
defaultProfile = $true
|
||||||
|
personalization = $true
|
||||||
|
scheduledTasks = $true
|
||||||
|
desktopInfo = $true
|
||||||
|
activation = $true
|
||||||
|
}
|
||||||
|
if ($Config -and $Config.steps) {
|
||||||
|
foreach ($key in @($stepsEnabled.Keys)) {
|
||||||
|
$val = $Config.steps.$key
|
||||||
|
if ($null -ne $val) { $stepsEnabled[$key] = [bool]$val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# CLI switches override config.steps
|
||||||
|
if ($SkipBloatware) { $stepsEnabled['bloatware'] = $false }
|
||||||
|
if ($SkipSoftware) { $stepsEnabled['software'] = $false }
|
||||||
|
if ($SkipDefaultProfile) { $stepsEnabled['defaultProfile'] = $false }
|
||||||
|
|
||||||
|
function Skip-Step {
|
||||||
|
param([string]$Name)
|
||||||
|
Write-Log "$Name - SKIPPED (disabled in config)" -Level WARN
|
||||||
|
$StepResults.Add(@{ Name = $Name; Status = "SKIPPED" })
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0a - Admin account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['adminAccount']) {
|
||||||
|
Invoke-Step -Name "Step 0a - Admin account" -Action {
|
||||||
|
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 0a - Admin account" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0b - Windows activation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['activation']) {
|
||||||
|
Invoke-Step -Name "Step 0b - Windows activation" -Action {
|
||||||
|
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 0b - Windows activation" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 1 - Bloatware removal
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['bloatware']) {
|
||||||
|
Invoke-Step -Name "Step 1 - Bloatware removal" -Action {
|
||||||
|
& "$ScriptRoot\scripts\01-bloatware.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 1 - Bloatware removal" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 2 - Software installation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['software']) {
|
||||||
|
Invoke-Step -Name "Step 2 - Software installation" -Action {
|
||||||
|
& "$ScriptRoot\scripts\02-software.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 2 - Software installation" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 3 - System registry (HKLM)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['systemRegistry']) {
|
||||||
|
Invoke-Step -Name "Step 3 - System registry" -Action {
|
||||||
|
& "$ScriptRoot\scripts\03-system-registry.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 3 - System registry" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 4 - Default profile (NTUSER.DAT)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['defaultProfile']) {
|
||||||
|
Invoke-Step -Name "Step 4 - Default profile" -Action {
|
||||||
|
& "$ScriptRoot\scripts\04-default-profile.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 4 - Default profile" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 5 - Personalization
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['personalization']) {
|
||||||
|
Invoke-Step -Name "Step 5 - Personalization" -Action {
|
||||||
|
& "$ScriptRoot\scripts\05-personalization.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 5 - Personalization" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 6 - Scheduled tasks
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['scheduledTasks']) {
|
||||||
|
Invoke-Step -Name "Step 6 - Scheduled tasks" -Action {
|
||||||
|
& "$ScriptRoot\scripts\06-scheduled-tasks.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 6 - Scheduled tasks" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 7 - DesktopInfo
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if ($stepsEnabled['desktopInfo']) {
|
||||||
|
Invoke-Step -Name "Step 7 - DesktopInfo" -Action {
|
||||||
|
& "$ScriptRoot\scripts\07-desktop-info.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
} else { Skip-Step "Step 7 - DesktopInfo" }
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
Write-Log "SUMMARY" -Level INFO
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
$countOK = ($StepResults | Where-Object { $_.Status -eq "OK" }).Count
|
||||||
|
$countError = ($StepResults | Where-Object { $_.Status -eq "ERROR" }).Count
|
||||||
|
$countSkipped = ($StepResults | Where-Object { $_.Status -eq "SKIPPED" }).Count
|
||||||
|
$countDryRun = ($StepResults | Where-Object { $_.Status -eq "DRYRUN" }).Count
|
||||||
|
|
||||||
|
foreach ($r in $StepResults) {
|
||||||
|
$lvl = switch ($r.Status) {
|
||||||
|
"OK" { "OK" }
|
||||||
|
"ERROR" { "ERROR" }
|
||||||
|
"SKIPPED" { "WARN" }
|
||||||
|
"DRYRUN" { "WARN" }
|
||||||
|
}
|
||||||
|
Write-Log "$($r.Status.PadRight(8)) $($r.Name)" -Level $lvl
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "----------------------------------------" -Level INFO
|
||||||
|
Write-Log "OK: $countOK ERROR: $countError SKIPPED: $countSkipped DRYRUN: $countDryRun" -Level INFO
|
||||||
|
Write-Log "Log saved to: $LogFile" -Level INFO
|
||||||
|
Write-Log "========================================" -Level INFO
|
||||||
|
|
||||||
|
if ($countError -gt 0) {
|
||||||
|
Write-Log "Deployment finished with errors. Review log: $LogFile" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Log "Deployment finished successfully." -Level OK
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
125
flash/Run.cmd
Normal file
125
flash/Run.cmd
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Auto-elevate to Administrator if not already elevated
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
net session >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo Requesting administrator privileges...
|
||||||
|
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Paths
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
set "DEPLOY_PS1=%SCRIPT_DIR%Deploy-Windows.ps1"
|
||||||
|
set "CONFIG_JSON=%SCRIPT_DIR%config\config.json"
|
||||||
|
set "CONFIG_EDITOR=%SCRIPT_DIR%config-editor.hta"
|
||||||
|
set "LOG_FILE=C:\Windows\Setup\Scripts\Deploy.log"
|
||||||
|
|
||||||
|
:MENU
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo X9 - Windows Deployment
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
echo Config : %CONFIG_JSON%
|
||||||
|
echo Log : %LOG_FILE%
|
||||||
|
echo.
|
||||||
|
echo [1] Full deployment (uses config.json)
|
||||||
|
echo [2] Dry run (no changes, log only)
|
||||||
|
echo [3] Skip bloatware removal
|
||||||
|
echo [4] Skip software install
|
||||||
|
echo [5] Open config editor (config-editor.hta)
|
||||||
|
echo [0] Exit
|
||||||
|
echo.
|
||||||
|
set /p CHOICE=" Select [0-5]: "
|
||||||
|
|
||||||
|
if "%CHOICE%"=="0" goto EXIT
|
||||||
|
if "%CHOICE%"=="1" goto FULL
|
||||||
|
if "%CHOICE%"=="2" goto DRYRUN
|
||||||
|
if "%CHOICE%"=="3" goto SKIP_BLOATWARE
|
||||||
|
if "%CHOICE%"=="4" goto SKIP_SOFTWARE
|
||||||
|
if "%CHOICE%"=="5" goto OPEN_EDITOR
|
||||||
|
|
||||||
|
echo Invalid choice. Try again.
|
||||||
|
timeout /t 2 >nul
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [1] Full deployment
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:FULL
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting full deployment...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%"
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [2] Dry run
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DRYRUN
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting dry run (no changes will be made)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -DryRun
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [3] Skip bloatware
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_BLOATWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (bloatware removal skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipBloatware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [4] Skip software
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:SKIP_SOFTWARE
|
||||||
|
cls
|
||||||
|
echo.
|
||||||
|
echo Starting deployment (software install skipped)...
|
||||||
|
echo.
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass -File "%DEPLOY_PS1%" -SkipSoftware
|
||||||
|
goto DONE
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: [5] Config editor
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:OPEN_EDITOR
|
||||||
|
if not exist "%CONFIG_EDITOR%" (
|
||||||
|
echo ERROR: config-editor.hta not found: %CONFIG_EDITOR%
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
)
|
||||||
|
start "" mshta.exe "%CONFIG_EDITOR%"
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:: Done
|
||||||
|
:: -----------------------------------------------------------------------
|
||||||
|
:DONE
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo Deployment finished.
|
||||||
|
echo Log: %LOG_FILE%
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
goto MENU
|
||||||
|
|
||||||
|
:EXIT
|
||||||
|
endlocal
|
||||||
|
exit /b 0
|
||||||
632
flash/config-editor.hta
Normal file
632
flash/config-editor.hta
Normal file
|
|
@ -0,0 +1,632 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>X9 - Deployment Config Editor</title>
|
||||||
|
<HTA:APPLICATION
|
||||||
|
ID="ConfigEditor"
|
||||||
|
APPLICATIONNAME="X9 Config Editor"
|
||||||
|
SCROLL="no"
|
||||||
|
SINGLEINSTANCE="yes"
|
||||||
|
WINDOWSTATE="normal"
|
||||||
|
INNERBORDER="no"
|
||||||
|
SELECTION="no"
|
||||||
|
CONTEXTMENU="no"
|
||||||
|
/>
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=11">
|
||||||
|
<style>
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: Segoe UI, Arial, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #1a2a33;
|
||||||
|
color: #d0dde3;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
#header {
|
||||||
|
background: #223B47;
|
||||||
|
padding: 12px 18px;
|
||||||
|
border-bottom: 2px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#header h1 {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #e8f4f8;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
#header .config-path {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #7aabbd;
|
||||||
|
margin-top: 3px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
#tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #1a2a33;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.tab {
|
||||||
|
padding: 8px 18px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #7aabbd;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.tab:hover { color: #b0d4e0; }
|
||||||
|
.tab.active { color: #e8f4f8; border-bottom: 2px solid #4a9aba; background: #1e3340; }
|
||||||
|
#content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.tab-panel { display: none; padding: 16px 18px; }
|
||||||
|
.tab-panel.active { display: block; }
|
||||||
|
table { width: 100%; border-collapse: collapse; }
|
||||||
|
th {
|
||||||
|
background: #223B47;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid #1e3340;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
tr:hover td { background: #1e3340; }
|
||||||
|
.step-num {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
width: 36px;
|
||||||
|
}
|
||||||
|
.step-name { color: #d0dde3; }
|
||||||
|
.info-btn {
|
||||||
|
background: #2e5568;
|
||||||
|
border: none;
|
||||||
|
color: #7aabbd;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.info-btn:hover { background: #3a6f8a; color: #e8f4f8; }
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: #4a9aba;
|
||||||
|
}
|
||||||
|
input[type="text"], select {
|
||||||
|
background: #1a2a33;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
input[type="text"]:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #4a9aba;
|
||||||
|
}
|
||||||
|
.label-cell {
|
||||||
|
width: 160px;
|
||||||
|
color: #9ac8d8;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-top: 10px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.section-title {
|
||||||
|
color: #4a9aba;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin: 16px 0 8px 0;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
border-bottom: 1px solid #2e5568;
|
||||||
|
}
|
||||||
|
.section-title:first-child { margin-top: 0; }
|
||||||
|
.btn {
|
||||||
|
background: #223B47;
|
||||||
|
border: 1px solid #2e5568;
|
||||||
|
color: #d0dde3;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.btn:hover { background: #2e5568; color: #e8f4f8; }
|
||||||
|
.btn-danger {
|
||||||
|
background: #3d1f1f;
|
||||||
|
border-color: #6b2c2c;
|
||||||
|
color: #d08080;
|
||||||
|
}
|
||||||
|
.btn-danger:hover { background: #5a2020; color: #f0a0a0; }
|
||||||
|
.btn-add {
|
||||||
|
background: #1a3a2a;
|
||||||
|
border-color: #2e6a4a;
|
||||||
|
color: #80c8a0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.btn-add:hover { background: #235a38; color: #a0e0b8; }
|
||||||
|
#footer {
|
||||||
|
background: #223B47;
|
||||||
|
border-top: 2px solid #2e5568;
|
||||||
|
padding: 10px 18px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#btn-save {
|
||||||
|
background: #1a5c3a;
|
||||||
|
border: 1px solid #2a8a58;
|
||||||
|
color: #80e0b0;
|
||||||
|
padding: 7px 22px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
#btn-save:hover { background: #206e46; color: #a0f0c8; }
|
||||||
|
#status {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #7aabbd;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
#tooltip {
|
||||||
|
position: fixed;
|
||||||
|
background: #0f1e26;
|
||||||
|
border: 1px solid #3a7a9a;
|
||||||
|
color: #c0dde8;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: 340px;
|
||||||
|
line-height: 1.5;
|
||||||
|
z-index: 9999;
|
||||||
|
display: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.settings-grid { display: grid; grid-template-columns: 160px 1fr; gap: 8px 12px; align-items: center; }
|
||||||
|
.settings-grid .span2 { grid-column: 1 / -1; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div id="header">
|
||||||
|
<h1>X9 - Deployment Config</h1>
|
||||||
|
<div class="config-path" id="config-path-display">Loading...</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tabs">
|
||||||
|
<div class="tab active" onclick="showTab('steps')">Steps</div>
|
||||||
|
<div class="tab" onclick="showTab('software')">Software</div>
|
||||||
|
<div class="tab" onclick="showTab('settings')">Settings</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="content">
|
||||||
|
|
||||||
|
<!-- STEPS TAB -->
|
||||||
|
<div class="tab-panel active" id="tab-steps">
|
||||||
|
<table id="steps-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width:40px;">On</th>
|
||||||
|
<th style="width:36px;">Step</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th style="width:32px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="steps-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SOFTWARE TAB -->
|
||||||
|
<div class="tab-panel" id="tab-software">
|
||||||
|
<table id="software-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Package Name</th>
|
||||||
|
<th>Winget ID</th>
|
||||||
|
<th style="width:70px;"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="software-tbody">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button class="btn btn-add" onclick="addSoftwareRow()">+ Add package</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SETTINGS TAB -->
|
||||||
|
<div class="tab-panel" id="tab-settings">
|
||||||
|
<div class="section-title">Deployment</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Timezone</div>
|
||||||
|
<div><input type="text" id="s-timezone" onchange="updateSetting('deployment','timezone',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Locale</div>
|
||||||
|
<div><input type="text" id="s-locale" onchange="updateSetting('deployment','locale',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Admin Account</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Username</div>
|
||||||
|
<div><input type="text" id="s-admin-user" onchange="updateSetting('adminAccount','username',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Password</div>
|
||||||
|
<div><input type="text" id="s-admin-pass" onchange="updateSetting('adminAccount','password',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Description</div>
|
||||||
|
<div><input type="text" id="s-admin-desc" onchange="updateSetting('adminAccount','description',this.value)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">PDF Default</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Force Adobe Reader</div>
|
||||||
|
<div><input type="checkbox" id="s-force-adobe" onchange="updateSettingBool('pdfDefault','forceAdobeReader',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Scheduled Task</div>
|
||||||
|
<div><input type="checkbox" id="s-pdf-task" onchange="updateSettingBool('pdfDefault','scheduledTaskEnabled',this.checked)"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Desktop Info</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Enabled</div>
|
||||||
|
<div><input type="checkbox" id="s-di-enabled" onchange="updateSettingBool('desktopInfo','enabled',this.checked)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Position</div>
|
||||||
|
<div>
|
||||||
|
<select id="s-di-position" onchange="updateSetting('desktopInfo','position',this.value)">
|
||||||
|
<option value="bottomRight">Bottom Right</option>
|
||||||
|
<option value="bottomLeft">Bottom Left</option>
|
||||||
|
<option value="topRight">Top Right</option>
|
||||||
|
<option value="topLeft">Top Left</option>
|
||||||
|
<option value="center">Center</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Size</div>
|
||||||
|
<div><input type="text" id="s-di-fontsize" onchange="updateSettingInt('desktopInfo','fontSize',this.value)"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">Font Color</div>
|
||||||
|
<div><input type="text" id="s-di-fontcolor" onchange="updateSetting('desktopInfo','fontColor',this.value)" placeholder="#FFFFFF"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section-title">Activation</div>
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="label-cell">Product Key</div>
|
||||||
|
<div><input type="text" id="s-act-key" onchange="updateSetting('activation','productKey',this.value)" placeholder="XXXXX-XXXXX-XXXXX-XXXXX-XXXXX"></div>
|
||||||
|
|
||||||
|
<div class="label-cell">KMS Server</div>
|
||||||
|
<div><input type="text" id="s-act-kms" onchange="updateSetting('activation','kmsServer',this.value)" placeholder="kms.example.com"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<button id="btn-save" onclick="saveConfig()">Save config.json</button>
|
||||||
|
<div id="status">Ready.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tooltip"></div>
|
||||||
|
|
||||||
|
<script language="JScript">
|
||||||
|
|
||||||
|
var STEP_DEFS = [
|
||||||
|
{ key: "adminAccount", num: "00", label: "Admin account", info: "Creates local admin account for MSP remote access. Account name and generated password are saved to Deploy.log." },
|
||||||
|
{ key: "bloatware", num: "01", label: "Bloatware removal", info: "Removes 44 pre-installed UWP apps (Teams, Xbox, Cortana, Solitaire, Office Hub, Skype...) and unused Windows Capabilities and Features. Calculator is kept." },
|
||||||
|
{ key: "software", num: "02", label: "Software install", info: "Installs packages from the list below via winget. Also sets Adobe Reader as default PDF viewer via HKCR." },
|
||||||
|
{ key: "systemRegistry", num: "03", label: "System registry", info: "HKLM-wide tweaks: disables Widgets, Teams auto-install, Edge first run, OneDrive, Outlook auto-install, GameDVR, Recall. Sets timezone. Hides taskbar search box via policy (Win11)." },
|
||||||
|
{ key: "defaultProfile", num: "04", label: "Default profile", info: "Modifies C:\\Users\\Default\\NTUSER.DAT so ALL future users inherit: left-aligned taskbar, hidden search/Copilot/TaskView/Widgets buttons, file extensions visible, Explorer opens to This PC, Num Lock on." },
|
||||||
|
{ key: "personalization", num: "05", label: "Personalization", info: "Dark system theme, light app theme, accent color #223B47, transparency off, accent on title bars. Applied to Default profile and current user." },
|
||||||
|
{ key: "scheduledTasks", num: "06", label: "Scheduled tasks", info: "Registers 4 tasks: ShowAllTrayIcons (logon - clears systray cache + restarts Explorer), UnlockStartLayout (once), PDF-DefaultApp (logon as SYSTEM - restores HKCR association), DesktopInfo (logon - renders wallpaper)." },
|
||||||
|
{ key: "desktopInfo", num: "07", label: "Desktop info", info: "Custom desktop wallpaper showing: computer name, IP, OS version, username, deployment date. Rendered as BMP on every logon via scheduled task. Replaces BackInfo.exe." },
|
||||||
|
{ key: "activation", num: "08", label: "Windows activation", info: "Checks and applies Windows activation." }
|
||||||
|
];
|
||||||
|
|
||||||
|
var configPath = "";
|
||||||
|
var config = null;
|
||||||
|
var fso = null;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
try {
|
||||||
|
fso = new ActiveXObject("Scripting.FileSystemObject");
|
||||||
|
|
||||||
|
// Derive config path from HTA location
|
||||||
|
var htaPath = location.pathname.replace(/\//g, "\\");
|
||||||
|
// Remove leading backslash from pathname
|
||||||
|
if (htaPath.charAt(0) === "\\") {
|
||||||
|
htaPath = htaPath.substring(1);
|
||||||
|
}
|
||||||
|
var dir = fso.GetParentFolderName(htaPath);
|
||||||
|
configPath = dir + "\\config\\config.json";
|
||||||
|
|
||||||
|
document.getElementById("config-path-display").innerText = configPath;
|
||||||
|
|
||||||
|
loadConfig();
|
||||||
|
buildStepsTable();
|
||||||
|
resizeWindow();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Init error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resizeWindow() {
|
||||||
|
window.resizeTo(800, 720);
|
||||||
|
window.moveTo(
|
||||||
|
(screen.width - 800) / 2,
|
||||||
|
(screen.height - 720) / 2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadConfig() {
|
||||||
|
try {
|
||||||
|
if (!fso.FileExists(configPath)) {
|
||||||
|
setStatus("config.json not found: " + configPath, true);
|
||||||
|
config = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var f = fso.OpenTextFile(configPath, 1, false, -1);
|
||||||
|
var raw = f.ReadAll();
|
||||||
|
f.Close();
|
||||||
|
config = JSON.parse(raw);
|
||||||
|
setStatus("Loaded: " + configPath);
|
||||||
|
populateSettings();
|
||||||
|
buildSoftwareTable();
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Load error: " + e.message, true);
|
||||||
|
config = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildStepsTable() {
|
||||||
|
var tbody = document.getElementById("steps-tbody");
|
||||||
|
var html = "";
|
||||||
|
for (var i = 0; i < STEP_DEFS.length; i++) {
|
||||||
|
var s = STEP_DEFS[i];
|
||||||
|
var checked = "";
|
||||||
|
// Check config.steps if loaded, default true
|
||||||
|
if (config && config.steps && config.steps[s.key] === false) {
|
||||||
|
checked = "";
|
||||||
|
} else {
|
||||||
|
checked = "checked";
|
||||||
|
}
|
||||||
|
html += "<tr>";
|
||||||
|
html += "<td><input type='checkbox' " + checked + " onclick=\"toggleStep('" + s.key + "', this.checked)\"></td>";
|
||||||
|
html += "<td class='step-num'>" + s.num + "</td>";
|
||||||
|
html += "<td class='step-name'>" + s.label + "</td>";
|
||||||
|
html += "<td><button class='info-btn' onmouseover=\"showTooltip(event, '" + escapeQ(s.info) + "')\" onmouseout='hideTooltip()'>i</button></td>";
|
||||||
|
html += "</tr>";
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildSoftwareTable() {
|
||||||
|
var tbody = document.getElementById("software-tbody");
|
||||||
|
var html = "";
|
||||||
|
if (config && config.software && config.software.install) {
|
||||||
|
var list = config.software.install;
|
||||||
|
for (var i = 0; i < list.length; i++) {
|
||||||
|
html += makeSoftwareRow(i, list[i].name, list[i].wingetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeSoftwareRow(idx, name, wingetId) {
|
||||||
|
return "<tr id='sw-row-" + idx + "'>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(name) + "' onchange=\"updateSoftwareName(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><input type='text' value='" + escapeQ(wingetId) + "' onchange=\"updateSoftwareId(" + idx + ", this.value)\"></td>" +
|
||||||
|
"<td><button class='btn btn-danger' onclick='removeSoftwareRow(" + idx + ")'>Remove</button></td>" +
|
||||||
|
"</tr>";
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSoftwareRow() {
|
||||||
|
if (!config.software) { config.software = {}; }
|
||||||
|
if (!config.software.install) { config.software.install = []; }
|
||||||
|
config.software.install.push({ name: "", wingetId: "" });
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package added. Fill in name and Winget ID, then save.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeSoftwareRow(idx) {
|
||||||
|
if (!config || !config.software || !config.software.install) { return; }
|
||||||
|
config.software.install.splice(idx, 1);
|
||||||
|
buildSoftwareTable();
|
||||||
|
setStatus("Package removed. Click Save to persist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareName(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].name = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSoftwareId(idx, val) {
|
||||||
|
if (config && config.software && config.software.install && config.software.install[idx] !== undefined) {
|
||||||
|
config.software.install[idx].wingetId = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateSettings() {
|
||||||
|
if (!config) { return; }
|
||||||
|
|
||||||
|
if (config.deployment) {
|
||||||
|
setVal("s-timezone", config.deployment.timezone || "");
|
||||||
|
setVal("s-locale", config.deployment.locale || "");
|
||||||
|
}
|
||||||
|
if (config.adminAccount) {
|
||||||
|
setVal("s-admin-user", config.adminAccount.username || "");
|
||||||
|
setVal("s-admin-pass", config.adminAccount.password || "");
|
||||||
|
setVal("s-admin-desc", config.adminAccount.description || "");
|
||||||
|
}
|
||||||
|
if (config.pdfDefault) {
|
||||||
|
setChk("s-force-adobe", config.pdfDefault.forceAdobeReader !== false);
|
||||||
|
setChk("s-pdf-task", config.pdfDefault.scheduledTaskEnabled !== false);
|
||||||
|
}
|
||||||
|
if (config.desktopInfo) {
|
||||||
|
setChk("s-di-enabled", config.desktopInfo.enabled !== false);
|
||||||
|
setSelVal("s-di-position", config.desktopInfo.position || "bottomRight");
|
||||||
|
setVal("s-di-fontsize", config.desktopInfo.fontSize !== undefined ? String(config.desktopInfo.fontSize) : "12");
|
||||||
|
setVal("s-di-fontcolor", config.desktopInfo.fontColor || "#FFFFFF");
|
||||||
|
}
|
||||||
|
if (config.activation) {
|
||||||
|
setVal("s-act-key", config.activation.productKey || "");
|
||||||
|
setVal("s-act-kms", config.activation.kmsServer || "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.value = val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setChk(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (el) { el.checked = !!val; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSelVal(id, val) {
|
||||||
|
var el = document.getElementById(id);
|
||||||
|
if (!el) { return; }
|
||||||
|
for (var i = 0; i < el.options.length; i++) {
|
||||||
|
if (el.options[i].value === val) {
|
||||||
|
el.selectedIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleStep(key, enabled) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
config.steps[key] = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSetting(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingBool(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
config[section][key] = !!val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettingInt(section, key, val) {
|
||||||
|
if (!config) { return; }
|
||||||
|
if (!config[section]) { config[section] = {}; }
|
||||||
|
var n = parseInt(val, 10);
|
||||||
|
config[section][key] = isNaN(n) ? val : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveConfig() {
|
||||||
|
if (!config) { setStatus("No config loaded.", true); return; }
|
||||||
|
try {
|
||||||
|
// Sync steps checkboxes before save (in case table was rebuilt)
|
||||||
|
syncStepsFromTable();
|
||||||
|
|
||||||
|
var json = JSON.stringify(config, null, 2);
|
||||||
|
var f = fso.CreateTextFile(configPath, true, true);
|
||||||
|
f.Write(json);
|
||||||
|
f.Close();
|
||||||
|
setStatus("Saved: " + configPath + " [" + now() + "]");
|
||||||
|
} catch(e) {
|
||||||
|
setStatus("Save error: " + e.message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncStepsFromTable() {
|
||||||
|
if (!config.steps) { config.steps = {}; }
|
||||||
|
var rows = document.getElementById("steps-tbody").getElementsByTagName("tr");
|
||||||
|
for (var i = 0; i < rows.length; i++) {
|
||||||
|
var chk = rows[i].getElementsByTagName("input")[0];
|
||||||
|
if (chk && STEP_DEFS[i]) {
|
||||||
|
config.steps[STEP_DEFS[i].key] = chk.checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(name) {
|
||||||
|
var panels = document.getElementsByClassName("tab-panel");
|
||||||
|
for (var i = 0; i < panels.length; i++) {
|
||||||
|
panels[i].className = "tab-panel";
|
||||||
|
}
|
||||||
|
var tabs = document.getElementsByClassName("tab");
|
||||||
|
for (var i = 0; i < tabs.length; i++) {
|
||||||
|
tabs[i].className = "tab";
|
||||||
|
}
|
||||||
|
document.getElementById("tab-" + name).className = "tab-panel active";
|
||||||
|
var allTabs = document.getElementById("tabs").getElementsByClassName("tab");
|
||||||
|
var nameMap = { steps: 0, software: 1, settings: 2 };
|
||||||
|
if (nameMap[name] !== undefined) {
|
||||||
|
allTabs[nameMap[name]].className = "tab active";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTooltip(evt, text) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
t.innerText = text;
|
||||||
|
t.style.display = "block";
|
||||||
|
positionTooltip(evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionTooltip(evt) {
|
||||||
|
var t = document.getElementById("tooltip");
|
||||||
|
var x = evt.clientX + 12;
|
||||||
|
var y = evt.clientY + 12;
|
||||||
|
if (x + 350 > document.body.clientWidth) { x = evt.clientX - 350; }
|
||||||
|
if (y + 80 > document.body.clientHeight) { y = evt.clientY - 80; }
|
||||||
|
t.style.left = x + "px";
|
||||||
|
t.style.top = y + "px";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideTooltip() {
|
||||||
|
document.getElementById("tooltip").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(msg, isError) {
|
||||||
|
var el = document.getElementById("status");
|
||||||
|
el.innerText = msg;
|
||||||
|
el.style.color = isError ? "#d08080" : "#7aabbd";
|
||||||
|
}
|
||||||
|
|
||||||
|
function now() {
|
||||||
|
var d = new Date();
|
||||||
|
return d.getHours() + ":" + pad(d.getMinutes()) + ":" + pad(d.getSeconds());
|
||||||
|
}
|
||||||
|
|
||||||
|
function pad(n) { return n < 10 ? "0" + n : String(n); }
|
||||||
|
|
||||||
|
function escapeQ(s) {
|
||||||
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() { init(); };
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
49
flash/config/config.json
Normal file
49
flash/config/config.json
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"steps": {
|
||||||
|
"adminAccount": true,
|
||||||
|
"bloatware": true,
|
||||||
|
"software": true,
|
||||||
|
"systemRegistry": true,
|
||||||
|
"defaultProfile": true,
|
||||||
|
"personalization": true,
|
||||||
|
"scheduledTasks": true,
|
||||||
|
"desktopInfo": true,
|
||||||
|
"activation": true
|
||||||
|
},
|
||||||
|
"deployment": {
|
||||||
|
"timezone": "Central Europe Standard Time",
|
||||||
|
"locale": "cs-CZ"
|
||||||
|
},
|
||||||
|
"adminAccount": {
|
||||||
|
"username": "adminx9",
|
||||||
|
"password": "AdminX9.AdminX9",
|
||||||
|
"description": "X9 MSP admin account"
|
||||||
|
},
|
||||||
|
"activation": {
|
||||||
|
"productKey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
|
||||||
|
"kmsServer": ""
|
||||||
|
},
|
||||||
|
"software": {
|
||||||
|
"install": [
|
||||||
|
{ "name": "7-Zip", "wingetId": "7zip.7zip" },
|
||||||
|
{ "name": "Adobe Acrobat Reader","wingetId": "Adobe.Acrobat.Reader.64-bit" },
|
||||||
|
{ "name": "OpenVPN Connect", "wingetId": "OpenVPNTechnologies.OpenVPNConnect" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"bloatware": {
|
||||||
|
"keepPackages": [
|
||||||
|
"Microsoft.WindowsCalculator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"desktopInfo": {
|
||||||
|
"enabled": true,
|
||||||
|
"position": "bottomRight",
|
||||||
|
"fontSize": 12,
|
||||||
|
"fontColor": "#FFFFFF",
|
||||||
|
"backgroundColor": "transparent"
|
||||||
|
},
|
||||||
|
"pdfDefault": {
|
||||||
|
"forceAdobeReader": true,
|
||||||
|
"scheduledTaskEnabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
88
flash/jak_na_to.txt
Normal file
88
flash/jak_na_to.txt
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
X9 - Windows Deployment
|
||||||
|
========================
|
||||||
|
Automatizovana priprava noveho Windows 10/11 pocitace pro klienta.
|
||||||
|
Spustit jednou jako Administrator na ciste nainstalovanem Windows.
|
||||||
|
|
||||||
|
|
||||||
|
CO JE NA FLASHCE
|
||||||
|
----------------
|
||||||
|
Run.cmd - SPUSTIT TOTO. Hlavni spoustec s menu.
|
||||||
|
config-editor.hta - Graficky editor konfigurace (dvojklik).
|
||||||
|
Deploy-Windows.ps1 - Hlavni deployment skript (spousti Run.cmd).
|
||||||
|
config\config.json - Konfigurace deploymentu.
|
||||||
|
scripts\ - Jednotlive kroky deploymentu (00-08).
|
||||||
|
|
||||||
|
|
||||||
|
JAK NA TO
|
||||||
|
---------
|
||||||
|
1. Zapojit flashku do pocitace.
|
||||||
|
|
||||||
|
2. Spustit Run.cmd pravym tlacitkem > "Spustit jako spravce".
|
||||||
|
(nebo dvojklik - skript si sam vyziada adminova prava)
|
||||||
|
|
||||||
|
3. Zobrazit se menu:
|
||||||
|
[1] Full deployment - spusti vsechny kroky dle config.json
|
||||||
|
[2] Dry run - projde vsechny kroky BEZ zmen, jen loguje
|
||||||
|
[3] Skip bloatware - deployment bez odstranovani aplikaci
|
||||||
|
[4] Skip software - deployment bez instalace softwaru
|
||||||
|
[5] Open config editor - otevre graficky editor konfigurace
|
||||||
|
[0] Exit
|
||||||
|
|
||||||
|
4. Vybrat [1] pro standardni deployment.
|
||||||
|
|
||||||
|
5. Po dokonceni zkontrolovat log:
|
||||||
|
C:\Windows\Setup\Scripts\Deploy.log
|
||||||
|
|
||||||
|
|
||||||
|
PRED DEPLOYMENTEM (volitelne)
|
||||||
|
------------------------------
|
||||||
|
Otevrit config-editor.hta (dvojklik) a zkontrolovat / upravit:
|
||||||
|
|
||||||
|
Zalozka STEPS - zapnout / vypnout jednotlive kroky
|
||||||
|
(kliknout na "i" pro popis co krok dela)
|
||||||
|
|
||||||
|
Zalozka SOFTWARE - seznam aplikaci instalovanych pres winget
|
||||||
|
(pridat / odebrat balicky)
|
||||||
|
|
||||||
|
Zalozka SETTINGS - casova zona, nazev admin uctu,
|
||||||
|
nastaveni DesktopInfo wallpaperu
|
||||||
|
|
||||||
|
Po uprave kliknout "Save config.json". Zmeny se projeji pri
|
||||||
|
spusteni deploymentu.
|
||||||
|
|
||||||
|
|
||||||
|
CO DEPLOYMENT DELA (kroky v poradí)
|
||||||
|
-------------------------------------
|
||||||
|
00 Admin account - vytvori lokalni admin ucet pro MSP pristup
|
||||||
|
01 Bloatware - odebere nepotrebne aplikace (Teams, Xbox, ...)
|
||||||
|
02 Software - nainstaluje aplikace pres winget
|
||||||
|
03 System registry - HKLM tweaky (widgety, OneDrive, Edge, ...)
|
||||||
|
04 Default profile - nastaveni taskbaru a Exploreru pro vsechny uzivatele
|
||||||
|
05 Personalization - tmave tema, akcetni barva #223B47
|
||||||
|
06 Scheduled tasks - registruje 4 ulohy (systray, PDF, DesktopInfo, ...)
|
||||||
|
07 DesktopInfo - vlastni tapeta s info o pocitaci (jmeno, IP, OS, ...)
|
||||||
|
08 Activation - aktivace Windows
|
||||||
|
|
||||||
|
|
||||||
|
LOG
|
||||||
|
---
|
||||||
|
Kazdy krok zapisuje do:
|
||||||
|
C:\Windows\Setup\Scripts\Deploy.log
|
||||||
|
|
||||||
|
Format zaznamu:
|
||||||
|
[HH:mm:ss] [OK/ERROR/WARN] zprava
|
||||||
|
|
||||||
|
Na konci logu je souhrn: pocet OK / ERROR / SKIPPED.
|
||||||
|
|
||||||
|
|
||||||
|
POZNAMKY
|
||||||
|
--------
|
||||||
|
- Skript vyzaduje pripojeni k internetu (winget instalace).
|
||||||
|
- Restart pocitace neni potreba - spoustec na to upozorni pokud ano.
|
||||||
|
- Nastaveni z Default Profile se aplikuji na KAZDEHO noveho uzivatele.
|
||||||
|
- DesktopInfo tapeta se obnovi pri kazdem prihlaseni.
|
||||||
|
- PDF default (Adobe Reader) se obnovuje pri kazdem prihlaseni jako ochrana
|
||||||
|
proti tomu, ze Edge prepise asociaci.
|
||||||
|
|
||||||
|
|
||||||
|
X9.cz MSP deployment suite
|
||||||
94
flash/scripts/00-admin-account.ps1
Normal file
94
flash/scripts/00-admin-account.ps1
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Read account config
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$accountName = "adminx9"
|
||||||
|
$accountPass = "AdminX9.AdminX9"
|
||||||
|
$accountDesc = "X9 MSP admin account"
|
||||||
|
|
||||||
|
if ($Config -and $Config.adminAccount) {
|
||||||
|
if ($Config.adminAccount.username) { $accountName = $Config.adminAccount.username }
|
||||||
|
if ($Config.adminAccount.password) { $accountPass = $Config.adminAccount.password }
|
||||||
|
if ($Config.adminAccount.description) { $accountDesc = $Config.adminAccount.description }
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Creating admin account: $accountName" -Level INFO
|
||||||
|
|
||||||
|
$securePass = ConvertTo-SecureString $accountPass -AsPlainText -Force
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Create or update account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$existing = Get-LocalUser -Name $accountName -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
Write-Log " Account already exists - updating password" -Level INFO
|
||||||
|
try {
|
||||||
|
Set-LocalUser -Name $accountName -Password $securePass -PasswordNeverExpires $true
|
||||||
|
Enable-LocalUser -Name $accountName
|
||||||
|
Write-Log " Account updated: $accountName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to update account: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
New-LocalUser -Name $accountName `
|
||||||
|
-Password $securePass `
|
||||||
|
-Description $accountDesc `
|
||||||
|
-PasswordNeverExpires `
|
||||||
|
-UserMayNotChangePassword `
|
||||||
|
-ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Account created: $accountName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to create account: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Add to Administrators group
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$adminsGroup = (Get-LocalGroup | Where-Object { $_.SID -eq "S-1-5-32-544" }).Name
|
||||||
|
$members = Get-LocalGroupMember -Group $adminsGroup -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -like "*$accountName" }
|
||||||
|
if (-not $members) {
|
||||||
|
Add-LocalGroupMember -Group $adminsGroup -Member $accountName -ErrorAction Stop
|
||||||
|
Write-Log " Added to $adminsGroup" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Already in $adminsGroup" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to add to Administrators: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Hide account from login screen
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
try {
|
||||||
|
$specialPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
|
||||||
|
if (-not (Test-Path $specialPath)) {
|
||||||
|
New-Item -Path $specialPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $specialPath -Name $accountName -Value 0 -Type DWord -Force
|
||||||
|
Write-Log " Account hidden from login screen" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to hide account from login screen: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 0a - Admin account complete" -Level OK
|
||||||
184
flash/scripts/01-bloatware.ps1
Normal file
184
flash/scripts/01-bloatware.ps1
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1a - AppX packages
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$AppxToRemove = @(
|
||||||
|
"Microsoft.Microsoft3DViewer"
|
||||||
|
"Microsoft.BingSearch"
|
||||||
|
"Microsoft.WindowsCamera"
|
||||||
|
"Clipchamp.Clipchamp"
|
||||||
|
"Microsoft.WindowsAlarms"
|
||||||
|
"Microsoft.Copilot"
|
||||||
|
"Microsoft.549981C3F5F10"
|
||||||
|
"Microsoft.Windows.DevHome"
|
||||||
|
"MicrosoftCorporationII.MicrosoftFamily"
|
||||||
|
"Microsoft.WindowsFeedbackHub"
|
||||||
|
"Microsoft.Edge.GameAssist"
|
||||||
|
"Microsoft.GetHelp"
|
||||||
|
"Microsoft.Getstarted"
|
||||||
|
"microsoft.windowscommunicationsapps"
|
||||||
|
"Microsoft.WindowsMaps"
|
||||||
|
"Microsoft.MixedReality.Portal"
|
||||||
|
"Microsoft.BingNews"
|
||||||
|
"Microsoft.MicrosoftOfficeHub"
|
||||||
|
"Microsoft.Office.OneNote"
|
||||||
|
"Microsoft.OutlookForWindows"
|
||||||
|
"Microsoft.Paint"
|
||||||
|
"Microsoft.MSPaint"
|
||||||
|
"Microsoft.People"
|
||||||
|
"Microsoft.Windows.Photos"
|
||||||
|
"Microsoft.PowerAutomateDesktop"
|
||||||
|
"MicrosoftCorporationII.QuickAssist"
|
||||||
|
"Microsoft.SkypeApp"
|
||||||
|
"Microsoft.ScreenSketch"
|
||||||
|
"Microsoft.MicrosoftSolitaireCollection"
|
||||||
|
"Microsoft.MicrosoftStickyNotes"
|
||||||
|
"MicrosoftTeams"
|
||||||
|
"MSTeams"
|
||||||
|
"Microsoft.Todos"
|
||||||
|
"Microsoft.WindowsSoundRecorder"
|
||||||
|
"Microsoft.Wallet"
|
||||||
|
"Microsoft.BingWeather"
|
||||||
|
"Microsoft.WindowsTerminal"
|
||||||
|
"Microsoft.Xbox.TCUI"
|
||||||
|
"Microsoft.XboxApp"
|
||||||
|
"Microsoft.XboxGameOverlay"
|
||||||
|
"Microsoft.XboxGamingOverlay"
|
||||||
|
"Microsoft.XboxIdentityProvider"
|
||||||
|
"Microsoft.XboxSpeechToTextOverlay"
|
||||||
|
"Microsoft.GamingApp"
|
||||||
|
"Microsoft.YourPhone"
|
||||||
|
"Microsoft.ZuneMusic"
|
||||||
|
"Microsoft.ZuneVideo"
|
||||||
|
"7EE7776C.LinkedInforWindows"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Packages to always keep
|
||||||
|
$KeepPackages = @("Microsoft.WindowsCalculator")
|
||||||
|
if ($Config -and $Config.bloatware -and $Config.bloatware.keepPackages) {
|
||||||
|
$KeepPackages += $Config.bloatware.keepPackages
|
||||||
|
}
|
||||||
|
$KeepPackages = $KeepPackages | Select-Object -Unique
|
||||||
|
|
||||||
|
Write-Log "1a - Removing AppX packages" -Level STEP
|
||||||
|
|
||||||
|
foreach ($pkg in $AppxToRemove) {
|
||||||
|
if ($KeepPackages -contains $pkg) {
|
||||||
|
Write-Log " KEEP $pkg" -Level INFO
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Installed packages (current user + all users)
|
||||||
|
$installed = Get-AppxPackage -Name $pkg -AllUsers -ErrorAction SilentlyContinue
|
||||||
|
if ($installed) {
|
||||||
|
try {
|
||||||
|
$installed | Remove-AppxPackage -AllUsers -ErrorAction Stop
|
||||||
|
Write-Log " Removed AppxPackage: $pkg" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove AppxPackage $pkg - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Provisioned packages (for new users)
|
||||||
|
$provisioned = Get-AppxProvisionedPackage -Online -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.DisplayName -eq $pkg }
|
||||||
|
if ($provisioned) {
|
||||||
|
try {
|
||||||
|
$provisioned | Remove-AppxProvisionedPackage -Online -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Removed provisioned: $pkg" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove provisioned $pkg - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $installed -and -not $provisioned) {
|
||||||
|
Write-Log " Not found (already removed): $pkg" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1b - Windows Capabilities
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$CapabilitiesToRemove = @(
|
||||||
|
"Print.Fax.Scan"
|
||||||
|
"Language.Handwriting"
|
||||||
|
"Browser.InternetExplorer"
|
||||||
|
"MathRecognizer"
|
||||||
|
"OneCoreUAP.OneSync"
|
||||||
|
"OpenSSH.Client"
|
||||||
|
"Microsoft.Windows.MSPaint"
|
||||||
|
"Microsoft.Windows.PowerShell.ISE"
|
||||||
|
"App.Support.QuickAssist"
|
||||||
|
"Microsoft.Windows.SnippingTool"
|
||||||
|
"App.StepsRecorder"
|
||||||
|
"Hello.Face"
|
||||||
|
"Media.WindowsMediaPlayer"
|
||||||
|
"Microsoft.Windows.WordPad"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "1b - Removing Windows Capabilities" -Level STEP
|
||||||
|
|
||||||
|
$installedCaps = Get-WindowsCapability -Online -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($cap in $CapabilitiesToRemove) {
|
||||||
|
# Match by prefix (e.g. Hello.Face matches Hello.Face.20134.0.0.0)
|
||||||
|
$matches = $installedCaps | Where-Object {
|
||||||
|
$_.Name -like "$cap*" -and $_.State -eq "Installed"
|
||||||
|
}
|
||||||
|
if ($matches) {
|
||||||
|
foreach ($c in $matches) {
|
||||||
|
try {
|
||||||
|
Remove-WindowsCapability -Online -Name $c.Name -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Removed capability: $($c.Name)" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove capability $($c.Name) - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log " Not found or not installed: $cap" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1c - Windows Optional Features
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$FeaturesToDisable = @(
|
||||||
|
"MediaPlayback"
|
||||||
|
"MicrosoftWindowsPowerShellV2Root"
|
||||||
|
"Recall"
|
||||||
|
"Microsoft-SnippingTool"
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "1c - Disabling Windows Optional Features" -Level STEP
|
||||||
|
|
||||||
|
foreach ($feat in $FeaturesToDisable) {
|
||||||
|
$feature = Get-WindowsOptionalFeature -Online -FeatureName $feat -ErrorAction SilentlyContinue
|
||||||
|
if ($feature -and $feature.State -eq "Enabled") {
|
||||||
|
try {
|
||||||
|
Disable-WindowsOptionalFeature -Online -FeatureName $feat -NoRestart -ErrorAction Stop | Out-Null
|
||||||
|
Write-Log " Disabled feature: $feat" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to disable feature $feat - $_" -Level WARN
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Log " Not enabled or not found: $feat" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 1 complete" -Level OK
|
||||||
122
flash/scripts/02-software.ps1
Normal file
122
flash/scripts/02-software.ps1
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Check winget availability
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking winget availability" -Level INFO
|
||||||
|
|
||||||
|
$winget = Get-Command winget -ErrorAction SilentlyContinue
|
||||||
|
if (-not $winget) {
|
||||||
|
# Try to find winget in known locations
|
||||||
|
$wingetPaths = @(
|
||||||
|
"$env:LOCALAPPDATA\Microsoft\WindowsApps\winget.exe"
|
||||||
|
"$env:ProgramFiles\WindowsApps\Microsoft.DesktopAppInstaller*\winget.exe"
|
||||||
|
)
|
||||||
|
foreach ($p in $wingetPaths) {
|
||||||
|
$found = Get-Item $p -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
if ($found) { $winget = $found.FullName; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $winget) {
|
||||||
|
Write-Log "winget not found - software installation skipped" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "winget found: $($winget.Source -or $winget)" -Level OK
|
||||||
|
|
||||||
|
# Accept agreements upfront
|
||||||
|
& winget source update --accept-source-agreements 2>&1 | Out-Null
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Install packages from config
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
if (-not $Config -or -not $Config.software -or -not $Config.software.install) {
|
||||||
|
Write-Log "No software list in config - skipping installs" -Level WARN
|
||||||
|
} else {
|
||||||
|
foreach ($pkg in $Config.software.install) {
|
||||||
|
Write-Log "Installing $($pkg.name) ($($pkg.wingetId))" -Level INFO
|
||||||
|
$result = & winget install --id $pkg.wingetId `
|
||||||
|
--silent `
|
||||||
|
--accept-package-agreements `
|
||||||
|
--accept-source-agreements `
|
||||||
|
--disable-interactivity `
|
||||||
|
2>&1
|
||||||
|
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
if ($exitCode -eq 0) {
|
||||||
|
Write-Log " Installed OK: $($pkg.name)" -Level OK
|
||||||
|
} elseif ($exitCode -eq -1978335189) {
|
||||||
|
# 0x8A150011 = already installed
|
||||||
|
Write-Log " Already installed: $($pkg.name)" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Failed: $($pkg.name) (exit $exitCode)" -Level ERROR
|
||||||
|
Write-Log " Output: $($result -join ' ')" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Set Adobe Reader as default PDF app
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$forcePdf = $true
|
||||||
|
if ($Config -and $Config.pdfDefault) {
|
||||||
|
$forcePdf = [bool]$Config.pdfDefault.forceAdobeReader
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($forcePdf) {
|
||||||
|
Write-Log "Setting Adobe Reader as default PDF app" -Level INFO
|
||||||
|
|
||||||
|
# Find Adobe PDF viewer executable (Acrobat DC or Reader DC)
|
||||||
|
$acroPaths = @(
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
|
||||||
|
)
|
||||||
|
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
|
||||||
|
if (-not $acroExe) {
|
||||||
|
Write-Log " Adobe PDF viewer not found - PDF default not set" -Level WARN
|
||||||
|
} else {
|
||||||
|
Write-Log " Found: $acroExe" -Level INFO
|
||||||
|
|
||||||
|
# Set file type association via HKCR (system-wide, requires admin)
|
||||||
|
$progId = "AcroExch.Document.DC"
|
||||||
|
$openCmd = "`"$acroExe`" `"%1`""
|
||||||
|
|
||||||
|
# HKCR\.pdf -> progId
|
||||||
|
if (-not (Test-Path "HKCR:\.pdf")) {
|
||||||
|
New-Item -Path "HKCR:\.pdf" -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId
|
||||||
|
|
||||||
|
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||||
|
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||||
|
if (-not (Test-Path $cmdPath)) {
|
||||||
|
New-Item -Path $cmdPath -Force | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
|
||||||
|
|
||||||
|
# Note: HKCU UserChoice requires a Windows-computed Hash value to be valid.
|
||||||
|
# Direct ProgId write without Hash is silently reset by Windows on next access.
|
||||||
|
# HKCR write above provides the system-wide default; per-user default is
|
||||||
|
# handled by the PDF-DefaultApp scheduled task via HKCR on every logon.
|
||||||
|
|
||||||
|
Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 2 complete" -Level OK
|
||||||
322
flash/scripts/03-system-registry.ps1
Normal file
322
flash/scripts/03-system-registry.ps1
Normal file
|
|
@ -0,0 +1,322 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class RegPrivilege {
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen);
|
||||||
|
[DllImport("kernel32.dll", ExactSpelling=true)]
|
||||||
|
static extern IntPtr GetCurrentProcess();
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
|
||||||
|
[DllImport("advapi32.dll", SetLastError=true)]
|
||||||
|
static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
|
struct TokPriv1Luid { public int Count; public long Luid; public int Attr; }
|
||||||
|
const int TOKEN_QUERY = 0x8;
|
||||||
|
const int TOKEN_ADJUST = 0x20;
|
||||||
|
const int SE_PRIVILEGE_ENABLED = 2;
|
||||||
|
public static bool Enable(string privilege) {
|
||||||
|
IntPtr htok = IntPtr.Zero;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false;
|
||||||
|
TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED;
|
||||||
|
if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false;
|
||||||
|
return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
function Grant-RegWriteAccess {
|
||||||
|
param([string]$Path)
|
||||||
|
# Grants Administrators FullControl on a TrustedInstaller-owned registry key.
|
||||||
|
# Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
|
||||||
|
try {
|
||||||
|
[RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
|
||||||
|
[RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null
|
||||||
|
|
||||||
|
$hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
|
||||||
|
$subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
|
||||||
|
$rootKey = switch ($hive) {
|
||||||
|
"HKLM" { [Microsoft.Win32.Registry]::LocalMachine }
|
||||||
|
"HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
|
||||||
|
"HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Take ownership (requires SeTakeOwnershipPrivilege)
|
||||||
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
|
||||||
|
if ($key) {
|
||||||
|
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
|
||||||
|
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
|
||||||
|
$key.SetAccessControl($acl)
|
||||||
|
$key.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grant FullControl to Administrators (requires ChangePermissions)
|
||||||
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
|
||||||
|
if ($key) {
|
||||||
|
$acl = $key.GetAccessControl()
|
||||||
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
||||||
|
"BUILTIN\Administrators",
|
||||||
|
[System.Security.AccessControl.RegistryRights]::FullControl,
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow)
|
||||||
|
$acl.SetAccessRule($rule)
|
||||||
|
$key.SetAccessControl($acl)
|
||||||
|
$key.Close()
|
||||||
|
}
|
||||||
|
Write-Log " ACL fixed for $Path" -Level INFO
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-Reg {
|
||||||
|
param(
|
||||||
|
[string]$Path,
|
||||||
|
[string]$Name,
|
||||||
|
$Value,
|
||||||
|
[string]$Type = "DWord"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $Path)) {
|
||||||
|
New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $Path\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Retry 1: grant write access via ACL manipulation
|
||||||
|
try {
|
||||||
|
Grant-RegWriteAccess -Path $Path
|
||||||
|
if (-not (Test-Path $Path)) {
|
||||||
|
New-Item -Path $Path -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $Path\$Name = $Value (after ACL fix)" -Level OK
|
||||||
|
return
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
# Retry 2: write via scheduled task running as SYSTEM
|
||||||
|
# SYSTEM has full registry access regardless of key ACL
|
||||||
|
try {
|
||||||
|
$regType = switch ($Type) {
|
||||||
|
"DWord" { "REG_DWORD" }
|
||||||
|
"String" { "REG_SZ" }
|
||||||
|
"ExpandString"{ "REG_EXPAND_SZ" }
|
||||||
|
"MultiString" { "REG_MULTI_SZ" }
|
||||||
|
"QWord" { "REG_QWORD" }
|
||||||
|
default { "REG_DWORD" }
|
||||||
|
}
|
||||||
|
# Convert registry PS path to reg.exe path
|
||||||
|
$regPath = $Path -replace '^HKLM:\\', 'HKLM\' `
|
||||||
|
-replace '^HKCU:\\', 'HKCU\' `
|
||||||
|
-replace '^HKCR:\\', 'HKCR\'
|
||||||
|
$tempScript = "$env:TEMP\set-reg-system-$([System.IO.Path]::GetRandomFileName()).ps1"
|
||||||
|
"reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f" |
|
||||||
|
Set-Content -Path $tempScript -Encoding UTF8
|
||||||
|
|
||||||
|
$taskName = "TempRegFix-$([System.IO.Path]::GetRandomFileName())"
|
||||||
|
$action = New-ScheduledTaskAction -Execute "cmd.exe" `
|
||||||
|
-Argument "/c reg add `"$regPath`" /v `"$Name`" /t $regType /d $Value /f"
|
||||||
|
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Seconds 30)
|
||||||
|
$task = New-ScheduledTask -Action $action -Principal $principal -Settings $settings
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
|
||||||
|
Start-ScheduledTask -TaskName $taskName
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item $tempScript -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Verify it was written
|
||||||
|
$written = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name
|
||||||
|
if ($null -ne $written) {
|
||||||
|
Write-Log " SET $Path\$Name = $Value (via SYSTEM task)" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " FAILED $Path\$Name - SYSTEM task ran but value not found" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-Reg {
|
||||||
|
param([string]$Path, [string]$Name)
|
||||||
|
try {
|
||||||
|
if (Test-Path $Path) {
|
||||||
|
Remove-ItemProperty -Path $Path -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " REMOVED $Path\$Name" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED removing $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "3 - Applying HKLM system registry tweaks" -Level STEP
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Bypass Network Requirement on OOBE (BypassNRO)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" `
|
||||||
|
-Name "BypassNRO" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Disable auto-install of Teams (Chat)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Communications" `
|
||||||
|
-Name "ConfigureChatAutoInstall" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Disable Cloud Optimized Content (ads in Start menu etc.)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||||
|
-Name "DisableCloudOptimizedContent" -Value 1
|
||||||
|
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent" `
|
||||||
|
-Name "DisableWindowsConsumerFeatures" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Disable Widgets (News and Interests)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Dsh" `
|
||||||
|
-Name "AllowNewsAndInterests" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Microsoft Edge - hide First Run Experience
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Edge" `
|
||||||
|
-Name "HideFirstRunExperience" -Value 1
|
||||||
|
|
||||||
|
# Also disable Edge desktop shortcut creation after install
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\EdgeUpdate" `
|
||||||
|
-Name "CreateDesktopShortcutDefault" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Password - no expiration
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log " Setting password max age to UNLIMITED" -Level INFO
|
||||||
|
$pwResult = & net accounts /maxpwage:UNLIMITED 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " Password max age set to UNLIMITED" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Failed to set password max age: $pwResult" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Time zone
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$tz = "Central Europe Standard Time"
|
||||||
|
if ($Config -and $Config.deployment -and $Config.deployment.timezone) {
|
||||||
|
$tz = $Config.deployment.timezone
|
||||||
|
}
|
||||||
|
Write-Log " Setting time zone: $tz" -Level INFO
|
||||||
|
try {
|
||||||
|
Set-TimeZone -Id $tz -ErrorAction Stop
|
||||||
|
Write-Log " Time zone set: $tz" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set time zone: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# OneDrive - uninstall from clean Windows (no policy block)
|
||||||
|
# NOTE: No policy key is set intentionally - M365 installation can reinstall
|
||||||
|
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log " Uninstalling OneDrive" -Level INFO
|
||||||
|
|
||||||
|
# Remove OneDriveSetup.exe if present
|
||||||
|
$oneDrivePaths = @(
|
||||||
|
"$env:SystemRoot\System32\OneDriveSetup.exe"
|
||||||
|
"$env:SystemRoot\SysWOW64\OneDriveSetup.exe"
|
||||||
|
)
|
||||||
|
foreach ($odPath in $oneDrivePaths) {
|
||||||
|
if (Test-Path $odPath) {
|
||||||
|
try {
|
||||||
|
# Uninstall first
|
||||||
|
& $odPath /uninstall 2>&1 | Out-Null
|
||||||
|
Write-Log " OneDrive uninstalled via $odPath" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " OneDrive uninstall failed: $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove OneDrive Start Menu shortcut
|
||||||
|
$odLnk = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\OneDrive.lnk"
|
||||||
|
if (Test-Path $odLnk) {
|
||||||
|
Remove-Item $odLnk -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Removed OneDrive Start Menu shortcut" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Outlook (new) - disable auto-install via UScheduler
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log " Disabling Outlook (new) auto-install" -Level INFO
|
||||||
|
|
||||||
|
$uschedulerPaths = @(
|
||||||
|
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler_Oobe\OutlookUpdate"
|
||||||
|
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Orchestrator\UScheduler\OutlookUpdate"
|
||||||
|
)
|
||||||
|
foreach ($uPath in $uschedulerPaths) {
|
||||||
|
if (Test-Path $uPath) {
|
||||||
|
try {
|
||||||
|
Remove-Item -Path $uPath -Recurse -Force
|
||||||
|
Write-Log " Removed UScheduler key: $uPath" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to remove UScheduler key: $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Disable GameDVR
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
|
||||||
|
-Name "AllowGameDVR" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Disable Recall (Windows AI feature)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||||
|
-Name "DisableAIDataAnalysis" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
|
||||||
|
# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
|
||||||
|
# this policy key ensures the setting survives Windows Updates.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
|
||||||
|
-Name "SearchOnTaskbarMode" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Start menu - hide Recommended section (Win11)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
|
||||||
|
-Name "HideRecommendedSection" -Value 1
|
||||||
|
|
||||||
|
Write-Log "Step 3 complete" -Level OK
|
||||||
324
flash/scripts/04-default-profile.ps1
Normal file
324
flash/scripts/04-default-profile.ps1
Normal file
|
|
@ -0,0 +1,324 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Helper - apply a registry setting to both Default hive and current HKCU
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
function Grant-HiveWriteAccess {
|
||||||
|
param([string]$HivePath) # full path e.g. "Registry::HKU\DefaultProfile\Software\..."
|
||||||
|
# Grants Administrators FullControl on a loaded hive key with restricted ACL.
|
||||||
|
try {
|
||||||
|
$acl = Get-Acl -Path $HivePath -ErrorAction Stop
|
||||||
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
||||||
|
"BUILTIN\Administrators",
|
||||||
|
[System.Security.AccessControl.RegistryRights]::FullControl,
|
||||||
|
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||||
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
|
[System.Security.AccessControl.AccessControlType]::Allow
|
||||||
|
)
|
||||||
|
$acl.SetAccessRule($rule)
|
||||||
|
Set-Acl -Path $HivePath -AclObject $acl -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Grant-HiveWriteAccess failed for $HivePath - $_" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-ProfileReg {
|
||||||
|
param(
|
||||||
|
[string]$SubKey, # relative to HKCU (e.g. "Software\Microsoft\...")
|
||||||
|
[string]$Name,
|
||||||
|
$Value,
|
||||||
|
[string]$Type = "DWord"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Apply to loaded Default hive
|
||||||
|
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $defPath)) {
|
||||||
|
New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Retry after granting write access to parent key
|
||||||
|
try {
|
||||||
|
$parentPath = $defPath -replace '\\[^\\]+$', ''
|
||||||
|
if (Test-Path $parentPath) { Grant-HiveWriteAccess -HivePath $parentPath }
|
||||||
|
if (-not (Test-Path $defPath)) {
|
||||||
|
New-Item -Path $defPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $defPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " DEFAULT HIVE failed $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply to current user as well
|
||||||
|
$hkcuPath = "HKCU:\$SubKey"
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $hkcuPath)) {
|
||||||
|
New-Item -Path $hkcuPath -Force -ErrorAction Stop | Out-Null
|
||||||
|
}
|
||||||
|
Set-ItemProperty -Path $hkcuPath -Name $Name -Value $Value -Type $Type -Force -ErrorAction Stop
|
||||||
|
Write-Log " SET $SubKey\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " HKCU failed $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Remove-ProfileReg {
|
||||||
|
param([string]$SubKey, [string]$Name)
|
||||||
|
|
||||||
|
$defPath = "Registry::HKU\DefaultProfile\$SubKey"
|
||||||
|
try {
|
||||||
|
if (Test-Path $defPath) {
|
||||||
|
Remove-ItemProperty -Path $defPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
$hkcuPath = "HKCU:\$SubKey"
|
||||||
|
try {
|
||||||
|
if (Test-Path $hkcuPath) {
|
||||||
|
Remove-ItemProperty -Path $hkcuPath -Name $Name -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " REMOVED $SubKey\$Name" -Level OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED removing $SubKey\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load Default profile hive
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$hivePath = "C:\Users\Default\NTUSER.DAT"
|
||||||
|
$hiveKey = "DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Loading Default hive: $hivePath" -Level INFO
|
||||||
|
|
||||||
|
# Unload first in case previous run left it mounted
|
||||||
|
& reg unload "HKU\$hiveKey" 2>&1 | Out-Null
|
||||||
|
|
||||||
|
$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to load Default hive: $loadResult" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
Write-Log "Default hive loaded" -Level OK
|
||||||
|
|
||||||
|
try {
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Taskbar settings (Win10 + Win11)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Applying taskbar settings" -Level STEP
|
||||||
|
|
||||||
|
$tbPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
||||||
|
|
||||||
|
# Win11: align taskbar to left (0 = left, 1 = center)
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
|
||||||
|
|
||||||
|
# Hide Search box / button - Win10/11 (0 = hidden, 1 = icon, 2 = full box)
|
||||||
|
# Note: Win11 uses Search subkey, Win10 uses Explorer\Advanced - set both
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Search" `
|
||||||
|
-Name "SearchboxTaskbarMode" -Value 0
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
|
||||||
|
|
||||||
|
# Hide Task View button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "ShowTaskViewButton" -Value 0
|
||||||
|
|
||||||
|
# Hide Widgets button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarDa" -Value 0
|
||||||
|
|
||||||
|
# Hide Chat / Teams button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarMn" -Value 0
|
||||||
|
|
||||||
|
# Hide Copilot button
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "ShowCopilotButton" -Value 0
|
||||||
|
|
||||||
|
# Show file extensions in Explorer
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "HideFileExt" -Value 0
|
||||||
|
|
||||||
|
# Open Explorer to This PC instead of Quick Access
|
||||||
|
Set-ProfileReg -SubKey $tbPath -Name "LaunchTo" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# System tray - show all icons
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# EnableAutoTray = 0 works on Win10; Win11 ignores it but set anyway
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
|
-Name "EnableAutoTray" -Value 0
|
||||||
|
|
||||||
|
# Win11 workaround: clear cached tray icon streams so all icons appear on next login
|
||||||
|
# Windows rebuilds the streams with all icons visible when no cache exists
|
||||||
|
$trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayNotifyKey) {
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams (Win11 systray workaround)" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also clear in Default hive so new users start with clean state
|
||||||
|
$defTrayKey = "Registry::HKU\DefaultProfile\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $defTrayKey) {
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams in Default hive" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} = This PC / Computer
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Start menu settings
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Applying Start menu settings" -Level STEP
|
||||||
|
|
||||||
|
# Disable Bing search suggestions in Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\Explorer" `
|
||||||
|
-Name "DisableSearchBoxSuggestions" -Value 1
|
||||||
|
|
||||||
|
# Win11: empty Start menu pins
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
|
||||||
|
-Name "ConfigureStartPins" `
|
||||||
|
-Value '{"pinnedList":[]}' `
|
||||||
|
-Type "String"
|
||||||
|
|
||||||
|
# Hide "Recently added" apps in Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackProgs" -Value 0
|
||||||
|
|
||||||
|
# Hide recently opened files/docs from Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackDocs" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Copilot - disable
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-ProfileReg -SubKey "Software\Policies\Microsoft\Windows\WindowsCopilot" `
|
||||||
|
-Name "TurnOffWindowsCopilot" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# GameDVR - disable
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-ProfileReg -SubKey "System\GameConfigStore" `
|
||||||
|
-Name "GameDVR_Enabled" -Value 0
|
||||||
|
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\GameDVR" `
|
||||||
|
-Name "AppCaptureEnabled" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Num Lock on startup
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-ProfileReg -SubKey "Control Panel\Keyboard" `
|
||||||
|
-Name "InitialKeyboardIndicators" -Value 2 -Type "String"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Accent color on title bars
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# OneDrive - remove RunOnce key from Default profile
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Removing OneDrive from Default profile RunOnce" -Level INFO
|
||||||
|
Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Run" `
|
||||||
|
-Name "OneDriveSetup"
|
||||||
|
|
||||||
|
Remove-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\RunOnce" `
|
||||||
|
-Name "Delete Cached Standalone Update Binary"
|
||||||
|
|
||||||
|
# Remove OneDrive from Explorer namespace (left panel)
|
||||||
|
$oneDriveClsid = "{018D5C66-4533-4307-9B53-224DE2ED1FE6}"
|
||||||
|
$nsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\$oneDriveClsid"
|
||||||
|
$defNsPath = "Registry::HKU\DefaultProfile\$nsPath"
|
||||||
|
if (Test-Path $defNsPath) {
|
||||||
|
Remove-Item -Path $defNsPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Removed OneDrive from Explorer namespace (Default)" -Level OK
|
||||||
|
}
|
||||||
|
$hkcuNsPath = "HKCU:\$nsPath"
|
||||||
|
if (Test-Path $hkcuNsPath) {
|
||||||
|
Remove-Item -Path $hkcuNsPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Removed OneDrive from Explorer namespace (HKCU)" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Empty taskbar pinned apps (Win10/11)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Clearing taskbar pinned apps layout" -Level INFO
|
||||||
|
|
||||||
|
$taskbarLayoutDir = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell"
|
||||||
|
if (-not (Test-Path $taskbarLayoutDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $taskbarLayoutDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
$taskbarLayoutXml = @"
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LayoutModificationTemplate
|
||||||
|
xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification"
|
||||||
|
xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout"
|
||||||
|
xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout"
|
||||||
|
xmlns:taskbar="http://schemas.microsoft.com/Start/2014/TaskbarLayout"
|
||||||
|
Version="1">
|
||||||
|
<CustomTaskbarLayoutCollection PinListPlacement="Replace">
|
||||||
|
<defaultlayout:TaskbarLayout>
|
||||||
|
<taskbar:TaskbarPinList>
|
||||||
|
</taskbar:TaskbarPinList>
|
||||||
|
</defaultlayout:TaskbarLayout>
|
||||||
|
</CustomTaskbarLayoutCollection>
|
||||||
|
</LayoutModificationTemplate>
|
||||||
|
"@
|
||||||
|
$taskbarLayoutXml | Set-Content -Path "$taskbarLayoutDir\LayoutModification.xml" -Encoding UTF8 -Force
|
||||||
|
Write-Log " Taskbar LayoutModification.xml written" -Level OK
|
||||||
|
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Unload Default hive - always, even on error
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Unloading Default hive" -Level INFO
|
||||||
|
[GC]::Collect()
|
||||||
|
[GC]::WaitForPendingFinalizers()
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
|
||||||
|
$unloadResult = & reg unload "HKU\$hiveKey" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log "Default hive unloaded" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Restart Explorer to apply taskbar/tray changes to current session
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Restarting Explorer to apply taskbar changes" -Level INFO
|
||||||
|
try {
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Start-Process explorer
|
||||||
|
Write-Log "Explorer restarted" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Explorer restart failed (non-fatal): $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 4 complete" -Level OK
|
||||||
172
flash/scripts/05-personalization.ps1
Normal file
172
flash/scripts/05-personalization.ps1
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# Accent color #223B47 stored as ABGR DWORD: 0xFF473B22
|
||||||
|
# A=FF B=47 G=3B R=22 -> 0xFF473B22 = 4283612962
|
||||||
|
$AccentColorABGR = 0xFF473B22
|
||||||
|
|
||||||
|
# Gradient colors (Windows generates these automatically but we set them explicitly)
|
||||||
|
# AccentPalette is 32 bytes - 8 shades of the accent color (BGRA each)
|
||||||
|
# We use the same color for all shades as a safe default
|
||||||
|
$AccentColorHex = "#223B47"
|
||||||
|
|
||||||
|
function Set-Reg {
|
||||||
|
param([string]$Path, [string]$Name, $Value, [string]$Type = "DWord")
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $Path)) { New-Item -Path $Path -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type $Type -Force
|
||||||
|
Write-Log " SET $Path\$Name = $Value" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " FAILED $Path\$Name - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Apply-ThemeSettings {
|
||||||
|
param([string]$HiveRoot) # "HKCU:" or "Registry::HKU\DefaultProfile"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# System theme - Dark (taskbar, Start, action center)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "SystemUsesLightTheme" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# App theme - Light
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "AppsUseLightTheme" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Accent color on Start and taskbar
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Transparency effects - disabled
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize" `
|
||||||
|
-Name "EnableTransparency" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Accent color
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "AccentColor" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorizationColor" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorizationAfterglow" -Value $AccentColorABGR -Type "DWord"
|
||||||
|
|
||||||
|
# Accent color on title bars and borders
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\DWM" `
|
||||||
|
-Name "ColorPrevalence" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Wallpaper - solid color #223B47 (fallback before DesktopInfo runs)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Background color as decimal RGB
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Colors" `
|
||||||
|
-Name "Background" -Value "34 59 71" -Type "String"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||||
|
-Name "WallpaperStyle" -Value "0" -Type "String"
|
||||||
|
|
||||||
|
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||||
|
-Name "TileWallpaper" -Value "0" -Type "String"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Load Default hive
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$hivePath = "C:\Users\Default\NTUSER.DAT"
|
||||||
|
$hiveKey = "DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Loading Default hive for personalization" -Level INFO
|
||||||
|
|
||||||
|
& reg unload "HKU\$hiveKey" 2>&1 | Out-Null
|
||||||
|
$loadResult = & reg load "HKU\$hiveKey" $hivePath 2>&1
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to load Default hive: $loadResult" -Level ERROR
|
||||||
|
Write-Log "Applying personalization to current user only" -Level WARN
|
||||||
|
|
||||||
|
Write-Log "Applying theme to current user (HKCU)" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "HKCU:"
|
||||||
|
|
||||||
|
# Set wallpaper via SystemParametersInfo for current user
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperHelper {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
[WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Write-Log "Applying theme to Default hive" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "Registry::HKU\DefaultProfile"
|
||||||
|
|
||||||
|
Write-Log "Applying theme to current user (HKCU)" -Level STEP
|
||||||
|
Apply-ThemeSettings -HiveRoot "HKCU:"
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
[GC]::Collect()
|
||||||
|
[GC]::WaitForPendingFinalizers()
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
|
||||||
|
$unloadResult = & reg unload "HKU\$hiveKey" 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log "Default hive unloaded" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log "Failed to unload Default hive: $unloadResult" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Apply wallpaper (solid color) to current desktop session
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Setting desktop wallpaper to solid color" -Level INFO
|
||||||
|
|
||||||
|
try {
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperHelper {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||||
|
# Empty string = solid color defined in Control Panel\Colors\Background
|
||||||
|
[WallpaperHelper]::SystemParametersInfo(20, 0, "", 3) | Out-Null
|
||||||
|
Write-Log " Desktop wallpaper updated" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to update wallpaper: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 5 complete" -Level OK
|
||||||
194
flash/scripts/06-scheduled-tasks.ps1
Normal file
194
flash/scripts/06-scheduled-tasks.ps1
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||||
|
if (-not (Test-Path $ScriptDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
function Register-Task {
|
||||||
|
param(
|
||||||
|
[string]$TaskName,
|
||||||
|
[string]$Description,
|
||||||
|
[object]$Action,
|
||||||
|
[object[]]$Triggers,
|
||||||
|
[string]$RunLevel = "Highest"
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
# Remove existing task with same name
|
||||||
|
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 5) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
|
||||||
|
$principal = New-ScheduledTaskPrincipal -GroupId "Users" `
|
||||||
|
-RunLevel $RunLevel
|
||||||
|
|
||||||
|
$task = New-ScheduledTask -Action $Action `
|
||||||
|
-Trigger $Triggers `
|
||||||
|
-Settings $settings `
|
||||||
|
-Principal $principal `
|
||||||
|
-Description $Description
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force | Out-Null
|
||||||
|
Write-Log " Registered task: $TaskName" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task $TaskName - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Task: ShowAllTrayIcons
|
||||||
|
# Runs on logon: clears TrayNotify icon cache and restarts Explorer so all
|
||||||
|
# tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
|
||||||
|
|
||||||
|
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
|
||||||
|
@'
|
||||||
|
# Win10: disable auto-hiding of tray icons
|
||||||
|
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
|
||||||
|
Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Win11: clear icon stream cache so all icons become visible after Explorer restart
|
||||||
|
$trayPath = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayPath) {
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart Explorer to apply changes
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Milliseconds 1500
|
||||||
|
if (-not (Get-Process explorer -ErrorAction SilentlyContinue)) {
|
||||||
|
Start-Process explorer
|
||||||
|
}
|
||||||
|
'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
|
||||||
|
$showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
|
||||||
|
Register-Task -TaskName "ShowAllTrayIcons" `
|
||||||
|
-Description "Show all system tray icons for current user" `
|
||||||
|
-Action $showTrayAction `
|
||||||
|
-Triggers $showTrayTrigger
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Task: PDF-DefaultApp
|
||||||
|
# Runs on every logon, restores .pdf -> Adobe Reader association
|
||||||
|
# Guards against Edge overwriting it
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: PDF-DefaultApp" -Level STEP
|
||||||
|
|
||||||
|
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1"
|
||||||
|
@'
|
||||||
|
# Restore .pdf -> Adobe Reader HKCR association (system-wide).
|
||||||
|
# Runs as SYSTEM so it can write to HKCR regardless of Edge updates.
|
||||||
|
# Note: HKCU UserChoice requires Windows Hash validation and cannot be
|
||||||
|
# set reliably via registry; HKCR provides the system-wide fallback.
|
||||||
|
$acroPaths = @(
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"$env:ProgramFiles\Adobe\Acrobat Reader DC\Reader\AcroRd32.exe"
|
||||||
|
"${env:ProgramFiles(x86)}\Adobe\Reader\Reader\AcroRd32.exe"
|
||||||
|
)
|
||||||
|
$acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
|
if (-not $acroExe) { exit 0 }
|
||||||
|
|
||||||
|
$progId = "AcroExch.Document.DC"
|
||||||
|
$openCmd = "`"$acroExe`" `"%1`""
|
||||||
|
|
||||||
|
# HKCR\.pdf
|
||||||
|
if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null }
|
||||||
|
$current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)"
|
||||||
|
if ($current -ne $progId) {
|
||||||
|
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||||
|
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||||
|
if (-not (Test-Path $cmdPath)) { New-Item -Path $cmdPath -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd -Force
|
||||||
|
'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
|
||||||
|
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
|
||||||
|
# Runs as SYSTEM to allow HKCR writes (system-wide file association)
|
||||||
|
$pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$pdfTask = New-ScheduledTask -Action $pdfAction `
|
||||||
|
-Trigger $pdfTrigger `
|
||||||
|
-Settings $pdfSettings `
|
||||||
|
-Principal $pdfPrincipal `
|
||||||
|
-Description "Restore Adobe Reader as default PDF app on logon"
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "PDF-DefaultApp" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Register-ScheduledTask -TaskName "PDF-DefaultApp" -InputObject $pdfTask -Force | Out-Null
|
||||||
|
Write-Log " Registered task: PDF-DefaultApp" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task PDF-DefaultApp - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Task: UnlockStartLayout
|
||||||
|
# Runs once after deployment to unlock the Start menu layout
|
||||||
|
# so users can still customize it later
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: UnlockStartLayout" -Level STEP
|
||||||
|
|
||||||
|
$unlockScript = "$ScriptDir\UnlockStartLayout.ps1"
|
||||||
|
@'
|
||||||
|
# Remove Start layout lock so users can modify it
|
||||||
|
$layoutXml = "C:\Users\Default\AppData\Local\Microsoft\Windows\Shell\LayoutModification.xml"
|
||||||
|
if (Test-Path $layoutXml) {
|
||||||
|
Remove-Item $layoutXml -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Unregister self after running once
|
||||||
|
Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
'@ | Set-Content -Path $unlockScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
|
$unlockAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$unlockScript`""
|
||||||
|
# Trigger: 5 minutes after system startup, once
|
||||||
|
$unlockTrigger = New-ScheduledTaskTrigger -AtStartup
|
||||||
|
$unlockTrigger.Delay = "PT5M"
|
||||||
|
|
||||||
|
$unlockPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
$unlockSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 10) `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$unlockTask = New-ScheduledTask -Action $unlockAction `
|
||||||
|
-Trigger $unlockTrigger `
|
||||||
|
-Settings $unlockSettings `
|
||||||
|
-Principal $unlockPrincipal `
|
||||||
|
-Description "Unlock Start menu layout 5 min after first boot"
|
||||||
|
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "UnlockStartLayout" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Register-ScheduledTask -TaskName "UnlockStartLayout" -InputObject $unlockTask -Force | Out-Null
|
||||||
|
Write-Log " Registered task: UnlockStartLayout" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task UnlockStartLayout - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 6 complete" -Level OK
|
||||||
235
flash/scripts/07-desktop-info.ps1
Normal file
235
flash/scripts/07-desktop-info.ps1
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
$ScriptDir = "C:\Windows\Setup\Scripts"
|
||||||
|
$RenderScript = "$ScriptDir\DesktopInfo-Render.ps1"
|
||||||
|
$BmpPath = "$ScriptDir\desktopinfo.bmp"
|
||||||
|
|
||||||
|
if (-not (Test-Path $ScriptDir)) {
|
||||||
|
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Write the rendering script (runs on every logon as the user)
|
||||||
|
# Layout: hostname (large bold, centered), then detail lines centered
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
|
||||||
|
|
||||||
|
$renderContent = @'
|
||||||
|
# DesktopInfo-Render.ps1
|
||||||
|
# Collects system info and renders it centered on the desktop wallpaper.
|
||||||
|
# Runs on every user logon via Scheduled Task.
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
$LogFile = "C:\Windows\Setup\Scripts\desktopinfo.log"
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" -Encoding UTF8
|
||||||
|
}
|
||||||
|
Write-Log "DesktopInfo render started" -Level INFO
|
||||||
|
|
||||||
|
Add-Type -AssemblyName System.Drawing
|
||||||
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class WallpaperApi {
|
||||||
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Collect system info
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Collecting system info"
|
||||||
|
$hostname = $env:COMPUTERNAME
|
||||||
|
$userDomain = $env:USERDOMAIN
|
||||||
|
$userName = $env:USERNAME
|
||||||
|
$loggedUser = if ($userDomain -and $userDomain -ne $hostname) { "$userDomain\$userName" } else { "$hostname\$userName" }
|
||||||
|
|
||||||
|
$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
||||||
|
$osName = if ($osInfo) { $osInfo.Caption -replace "^Microsoft\s*", "" } else { "Windows" }
|
||||||
|
$ramGB = if ($osInfo) { [math]::Round($osInfo.TotalVisibleMemorySize / 1024 / 1024, 1) } else { "?" }
|
||||||
|
|
||||||
|
$cpuInfo = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
|
$cpuCount = if ($cpuInfo) { $cpuInfo.NumberOfLogicalProcessors } else { "?" }
|
||||||
|
$cpuSpeed = if ($cpuInfo) { $cpuInfo.MaxClockSpeed } else { "?" }
|
||||||
|
|
||||||
|
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } |
|
||||||
|
Select-Object -ExpandProperty IPAddress) -join ", "
|
||||||
|
if (-not $ips) { $ips = "N/A" }
|
||||||
|
|
||||||
|
$csInfo = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
|
||||||
|
$domain = if ($csInfo -and $csInfo.PartOfDomain) { $csInfo.Domain } `
|
||||||
|
elseif ($csInfo -and $csInfo.Workgroup) { $csInfo.Workgroup.ToLower() } `
|
||||||
|
else { "N/A" }
|
||||||
|
|
||||||
|
Write-Log "hostname=$hostname user=$loggedUser os=$osName ram=$($ramGB)GB cpu=${cpuCount}x${cpuSpeed}MHz ips=$ips domain=$domain"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Screen dimensions
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
||||||
|
$width = if ($screen) { $screen.Bounds.Width } else { 1920 }
|
||||||
|
$height = if ($screen) { $screen.Bounds.Height } else { 1080 }
|
||||||
|
Write-Log "screen=${width}x${height}"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Create bitmap and graphics context
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap($width, $height)
|
||||||
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||||
|
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
|
||||||
|
$g.Clear([System.Drawing.ColorTranslator]::FromHtml("#556364"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Fonts and brushes
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$fontName = "Segoe UI"
|
||||||
|
$fontTitle = New-Object System.Drawing.Font($fontName, 36, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontBold = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontReg = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Regular)
|
||||||
|
$brushWhite = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
||||||
|
$brushGray = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("#C8D2D2"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Lines: text, font, brush
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$texts = @(
|
||||||
|
$hostname
|
||||||
|
"Logged on user: $loggedUser"
|
||||||
|
"OS: $osName"
|
||||||
|
"CPU: $cpuCount at $cpuSpeed MHz RAM: $($ramGB)GB"
|
||||||
|
"IPv4 address: $ips Machine domain: $domain"
|
||||||
|
)
|
||||||
|
$fonts = @( $fontTitle, $fontReg, $fontBold, $fontReg, $fontReg )
|
||||||
|
$brushes = @( $brushWhite, $brushGray, $brushGray, $brushGray, $brushGray )
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Measure total block height, then center vertically
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$lineSpacing = 8
|
||||||
|
$heights = @()
|
||||||
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
|
$heights += [int]($g.MeasureString($texts[$i], $fonts[$i]).Height)
|
||||||
|
}
|
||||||
|
$totalH = ($heights | Measure-Object -Sum).Sum + $lineSpacing * ($texts.Count - 1)
|
||||||
|
$currentY = [int](($height - $totalH) / 2)
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Draw each line centered horizontally
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
|
$sz = $g.MeasureString($texts[$i], $fonts[$i])
|
||||||
|
$x = [int](($width - $sz.Width) / 2)
|
||||||
|
$g.DrawString($texts[$i], $fonts[$i], $brushes[$i], [float]$x, [float]$currentY)
|
||||||
|
$currentY += $heights[$i] + $lineSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
$g.Dispose()
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Save and set as wallpaper
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$bmpPath = "C:\Windows\Setup\Scripts\desktopinfo.bmp"
|
||||||
|
Write-Log "Saving BMP: $bmpPath"
|
||||||
|
$bmp.Save($bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
|
||||||
|
$bmp.Dispose()
|
||||||
|
|
||||||
|
# Clear Windows wallpaper cache so it reloads from our BMP
|
||||||
|
# Without this, Windows reuses TranscodedWallpaper and ignores the updated file
|
||||||
|
$transcodedPath = "$env:APPDATA\Microsoft\Windows\Themes\TranscodedWallpaper"
|
||||||
|
if (Test-Path $transcodedPath) {
|
||||||
|
Remove-Item $transcodedPath -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log "Cleared TranscodedWallpaper cache"
|
||||||
|
}
|
||||||
|
|
||||||
|
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||||
|
$result = [WallpaperApi]::SystemParametersInfo(20, 0, $bmpPath, 3)
|
||||||
|
Write-Log "SystemParametersInfo result: $result"
|
||||||
|
Write-Log "DesktopInfo render complete" -Level INFO
|
||||||
|
'@
|
||||||
|
|
||||||
|
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
|
||||||
|
Write-Log "Render script written" -Level OK
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Store deployment date in registry (used for reference)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Storing deployment date in registry" -Level INFO
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path "HKLM:\SOFTWARE\X9\Deployment")) {
|
||||||
|
New-Item -Path "HKLM:\SOFTWARE\X9\Deployment" -Force | Out-Null
|
||||||
|
}
|
||||||
|
$existingDate = (Get-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||||
|
-Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate
|
||||||
|
if (-not $existingDate) {
|
||||||
|
Set-ItemProperty -Path "HKLM:\SOFTWARE\X9\Deployment" `
|
||||||
|
-Name "DeployDate" `
|
||||||
|
-Value (Get-Date -Format "yyyy-MM-dd") `
|
||||||
|
-Force
|
||||||
|
Write-Log " DeployDate set: $(Get-Date -Format 'yyyy-MM-dd')" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " DeployDate already set: $existingDate" -Level INFO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to set DeployDate: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Register scheduled task: DesktopInfo
|
||||||
|
# Runs the render script on every user logon
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering task: DesktopInfo" -Level STEP
|
||||||
|
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "DesktopInfo" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
|
||||||
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
$trigger.Delay = "PT20S" # wait for network to be available
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$principal = New-ScheduledTaskPrincipal -GroupId "Users" -RunLevel Limited
|
||||||
|
|
||||||
|
$task = New-ScheduledTask -Action $action `
|
||||||
|
-Trigger $trigger `
|
||||||
|
-Settings $settings `
|
||||||
|
-Principal $principal `
|
||||||
|
-Description "Render system info onto desktop wallpaper on logon"
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName "DesktopInfo" -InputObject $task -Force | Out-Null
|
||||||
|
Write-Log "Task DesktopInfo registered" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to register DesktopInfo task: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Run once immediately for current user
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Running DesktopInfo render now for current user" -Level INFO
|
||||||
|
try {
|
||||||
|
& powershell.exe -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File $RenderScript
|
||||||
|
Write-Log "DesktopInfo rendered" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "DesktopInfo render failed: $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 7 complete" -Level OK
|
||||||
109
flash/scripts/08-activation.ps1
Normal file
109
flash/scripts/08-activation.ps1
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# KMS Generic Volume License Keys (GVLK)
|
||||||
|
# Source: https://docs.microsoft.com/en-us/windows-server/get-started/kms-client-activation-keys
|
||||||
|
# These are official Microsoft-published keys for use with KMS infrastructure.
|
||||||
|
# Replace with your MAK/retail key for standalone activation.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$KmsKeys = @{
|
||||||
|
# Windows 11
|
||||||
|
"Windows 11 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX"
|
||||||
|
"Windows 11 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9"
|
||||||
|
"Windows 11 Pro Education" = "6TP4R-GNPTD-KYYHQ-7B7DP-J447Y"
|
||||||
|
"Windows 11 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
|
||||||
|
"Windows 11 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
|
||||||
|
# Windows 10
|
||||||
|
"Windows 10 Pro" = "W269N-WFGWX-YVC9B-4J6C9-T83GX"
|
||||||
|
"Windows 10 Pro N" = "MH37W-N47XK-V7XM9-C7227-GCQG9"
|
||||||
|
"Windows 10 Education" = "NW6C2-QMPVW-D7KKK-3GKT6-VCFB2"
|
||||||
|
"Windows 10 Enterprise" = "NPPR9-FWDCX-D2C8J-H872K-2YT43"
|
||||||
|
"Windows 10 Home" = "TX9XD-98N7V-6WMQ6-BX7FG-H8Q99"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Check current activation status
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Checking Windows activation status" -Level INFO
|
||||||
|
|
||||||
|
$licenseStatus = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1).LicenseStatus
|
||||||
|
# LicenseStatus: 0=Unlicensed, 1=Licensed, 2=OOBGrace, 3=OOTGrace, 4=NonGenuineGrace, 5=Notification, 6=ExtendedGrace
|
||||||
|
|
||||||
|
if ($licenseStatus -eq 1) {
|
||||||
|
Write-Log " Windows is already activated - skipping" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Activation status: $licenseStatus (not activated)" -Level WARN
|
||||||
|
|
||||||
|
# Detect Windows edition
|
||||||
|
$osCaption = (Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue).Caption
|
||||||
|
Write-Log " Detected OS: $osCaption" -Level INFO
|
||||||
|
|
||||||
|
# Check if a key is configured in config
|
||||||
|
$customKey = $null
|
||||||
|
if ($Config -and $Config.activation -and $Config.activation.productKey) {
|
||||||
|
$customKey = $Config.activation.productKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($customKey -and $customKey -ne "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX") {
|
||||||
|
# Use key from config
|
||||||
|
$keyToUse = $customKey
|
||||||
|
Write-Log " Using product key from config" -Level INFO
|
||||||
|
} else {
|
||||||
|
# Find matching GVLK key by OS name
|
||||||
|
$keyToUse = $null
|
||||||
|
foreach ($entry in $KmsKeys.GetEnumerator()) {
|
||||||
|
if ($osCaption -like "*$($entry.Key)*") {
|
||||||
|
$keyToUse = $entry.Value
|
||||||
|
Write-Log " Matched GVLK key for: $($entry.Key)" -Level INFO
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $keyToUse) {
|
||||||
|
Write-Log " No matching key found for: $osCaption" -Level WARN
|
||||||
|
Write-Log " Skipping activation - set activation.productKey in config.json" -Level WARN
|
||||||
|
} else {
|
||||||
|
# Install key
|
||||||
|
Write-Log " Installing product key..." -Level INFO
|
||||||
|
$ipkResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ipk $keyToUse 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Log " Key installed" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Key install result: $ipkResult" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set KMS server if configured
|
||||||
|
if ($Config -and $Config.activation -and $Config.activation.kmsServer) {
|
||||||
|
$kmsServer = $Config.activation.kmsServer
|
||||||
|
Write-Log " Setting KMS server: $kmsServer" -Level INFO
|
||||||
|
& cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /skms $kmsServer 2>&1 | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Attempt activation
|
||||||
|
Write-Log " Attempting activation..." -Level INFO
|
||||||
|
$atoResult = & cscript //nologo "$env:SystemRoot\System32\slmgr.vbs" /ato 2>&1
|
||||||
|
$atoOutput = $atoResult -join " "
|
||||||
|
|
||||||
|
if ($atoOutput -match "successfully" -or $atoOutput -match "uspesn") {
|
||||||
|
Write-Log " Activation successful" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " Activation result: $atoOutput" -Level WARN
|
||||||
|
Write-Log " Activation may require a KMS server or valid MAK key" -Level WARN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 8 - Activation complete" -Level OK
|
||||||
|
|
@ -62,6 +62,7 @@ $AppxToRemove = @(
|
||||||
"Microsoft.YourPhone"
|
"Microsoft.YourPhone"
|
||||||
"Microsoft.ZuneMusic"
|
"Microsoft.ZuneMusic"
|
||||||
"Microsoft.ZuneVideo"
|
"Microsoft.ZuneVideo"
|
||||||
|
"7EE7776C.LinkedInforWindows"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Packages to always keep
|
# Packages to always keep
|
||||||
|
|
@ -159,7 +160,6 @@ foreach ($cap in $CapabilitiesToRemove) {
|
||||||
$FeaturesToDisable = @(
|
$FeaturesToDisable = @(
|
||||||
"MediaPlayback"
|
"MediaPlayback"
|
||||||
"MicrosoftWindowsPowerShellV2Root"
|
"MicrosoftWindowsPowerShellV2Root"
|
||||||
"Microsoft-RemoteDesktopConnection"
|
|
||||||
"Recall"
|
"Recall"
|
||||||
"Microsoft-SnippingTool"
|
"Microsoft-SnippingTool"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -110,14 +110,12 @@ if ($forcePdf) {
|
||||||
}
|
}
|
||||||
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd
|
||||||
|
|
||||||
# Also set in HKCU for current user (UserChoice)
|
# Note: HKCU UserChoice requires a Windows-computed Hash value to be valid.
|
||||||
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
|
# Direct ProgId write without Hash is silently reset by Windows on next access.
|
||||||
if (-not (Test-Path $ucPath)) {
|
# HKCR write above provides the system-wide default; per-user default is
|
||||||
New-Item -Path $ucPath -Force | Out-Null
|
# handled by the PDF-DefaultApp scheduled task via HKCR on every logon.
|
||||||
}
|
|
||||||
Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId
|
|
||||||
|
|
||||||
Write-Log " PDF default set to AcroRd32" -Level OK
|
Write-Log " PDF default set to AcroRd32 (HKCR)" -Level OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,41 @@ function Write-Log {
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
public class RegPrivilege {
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disAll, ref TokPriv1Luid newState, int len, IntPtr prev, IntPtr relen);
|
||||||
|
[DllImport("kernel32.dll", ExactSpelling=true)]
|
||||||
|
static extern IntPtr GetCurrentProcess();
|
||||||
|
[DllImport("advapi32.dll", ExactSpelling=true, SetLastError=true)]
|
||||||
|
static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok);
|
||||||
|
[DllImport("advapi32.dll", SetLastError=true)]
|
||||||
|
static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack=1)]
|
||||||
|
struct TokPriv1Luid { public int Count; public long Luid; public int Attr; }
|
||||||
|
const int TOKEN_QUERY = 0x8;
|
||||||
|
const int TOKEN_ADJUST = 0x20;
|
||||||
|
const int SE_PRIVILEGE_ENABLED = 2;
|
||||||
|
public static bool Enable(string privilege) {
|
||||||
|
IntPtr htok = IntPtr.Zero;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST | TOKEN_QUERY, ref htok)) return false;
|
||||||
|
TokPriv1Luid tp; tp.Count = 1; tp.Luid = 0; tp.Attr = SE_PRIVILEGE_ENABLED;
|
||||||
|
if (!LookupPrivilegeValue(null, privilege, ref tp.Luid)) return false;
|
||||||
|
return AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
function Grant-RegWriteAccess {
|
function Grant-RegWriteAccess {
|
||||||
param([string]$Path)
|
param([string]$Path)
|
||||||
# Grants Administrators FullControl on a registry key that has restricted ACL.
|
# Grants Administrators FullControl on a TrustedInstaller-owned registry key.
|
||||||
# Required for keys owned by TrustedInstaller or with locked-down ACL.
|
# Enables SeTakeOwnershipPrivilege + SeRestorePrivilege to override ACL.
|
||||||
try {
|
try {
|
||||||
|
[RegPrivilege]::Enable("SeTakeOwnershipPrivilege") | Out-Null
|
||||||
|
[RegPrivilege]::Enable("SeRestorePrivilege") | Out-Null
|
||||||
|
|
||||||
$hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
|
$hive = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\.*', '$1'
|
||||||
$subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
|
$subkey = $Path -replace '^(HKLM|HKCU|HKU|HKCR|HKCC):\\', ''
|
||||||
$rootKey = switch ($hive) {
|
$rootKey = switch ($hive) {
|
||||||
|
|
@ -23,17 +53,22 @@ function Grant-RegWriteAccess {
|
||||||
"HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
|
"HKCU" { [Microsoft.Win32.Registry]::CurrentUser }
|
||||||
"HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
|
"HKCR" { [Microsoft.Win32.Registry]::ClassesRoot }
|
||||||
}
|
}
|
||||||
$rights = [Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree
|
|
||||||
$regRights = [System.Security.AccessControl.RegistryRights]::TakeOwnership
|
# Take ownership (requires SeTakeOwnershipPrivilege)
|
||||||
$key = $rootKey.OpenSubKey($subkey, $rights, $regRights)
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::TakeOwnership)
|
||||||
if ($key) {
|
if ($key) {
|
||||||
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
|
$acl = $key.GetAccessControl([System.Security.AccessControl.AccessControlSections]::None)
|
||||||
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
|
$acl.SetOwner([System.Security.Principal.NTAccount]"BUILTIN\Administrators")
|
||||||
$key.SetAccessControl($acl)
|
$key.SetAccessControl($acl)
|
||||||
$key.Close()
|
$key.Close()
|
||||||
}
|
}
|
||||||
# Re-open with ChangePermissions to grant full control
|
|
||||||
$key = $rootKey.OpenSubKey($subkey, $rights, [System.Security.AccessControl.RegistryRights]::ChangePermissions)
|
# Grant FullControl to Administrators (requires ChangePermissions)
|
||||||
|
$key = $rootKey.OpenSubKey($subkey,
|
||||||
|
[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,
|
||||||
|
[System.Security.AccessControl.RegistryRights]::ChangePermissions)
|
||||||
if ($key) {
|
if ($key) {
|
||||||
$acl = $key.GetAccessControl()
|
$acl = $key.GetAccessControl()
|
||||||
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
$rule = New-Object System.Security.AccessControl.RegistryAccessRule(
|
||||||
|
|
@ -41,12 +76,12 @@ function Grant-RegWriteAccess {
|
||||||
[System.Security.AccessControl.RegistryRights]::FullControl,
|
[System.Security.AccessControl.RegistryRights]::FullControl,
|
||||||
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
[System.Security.AccessControl.InheritanceFlags]"ContainerInherit,ObjectInherit",
|
||||||
[System.Security.AccessControl.PropagationFlags]::None,
|
[System.Security.AccessControl.PropagationFlags]::None,
|
||||||
[System.Security.AccessControl.AccessControlType]::Allow
|
[System.Security.AccessControl.AccessControlType]::Allow)
|
||||||
)
|
|
||||||
$acl.SetAccessRule($rule)
|
$acl.SetAccessRule($rule)
|
||||||
$key.SetAccessControl($acl)
|
$key.SetAccessControl($acl)
|
||||||
$key.Close()
|
$key.Close()
|
||||||
}
|
}
|
||||||
|
Write-Log " ACL fixed for $Path" -Level INFO
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN
|
Write-Log " Grant-RegWriteAccess failed for $Path - $_" -Level WARN
|
||||||
|
|
@ -206,13 +241,11 @@ catch {
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# OneDrive - prevent setup and remove shortcuts
|
# OneDrive - uninstall from clean Windows (no policy block)
|
||||||
|
# NOTE: No policy key is set intentionally - M365 installation can reinstall
|
||||||
|
# and run OneDrive normally. Policy DisableFileSyncNGSC would prevent that.
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Write-Log " Disabling OneDrive" -Level INFO
|
Write-Log " Uninstalling OneDrive" -Level INFO
|
||||||
|
|
||||||
# Disable OneDrive via policy
|
|
||||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\OneDrive" `
|
|
||||||
-Name "DisableFileSyncNGSC" -Value 1
|
|
||||||
|
|
||||||
# Remove OneDriveSetup.exe if present
|
# Remove OneDriveSetup.exe if present
|
||||||
$oneDrivePaths = @(
|
$oneDrivePaths = @(
|
||||||
|
|
@ -272,4 +305,18 @@ Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\GameDVR" `
|
||||||
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsAI" `
|
||||||
-Name "DisableAIDataAnalysis" -Value 1
|
-Name "DisableAIDataAnalysis" -Value 1
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Search on taskbar - hide via HKLM policy (Win11 22H2+ enforcement)
|
||||||
|
# User-level SearchboxTaskbarMode alone is insufficient on newer Win11 builds;
|
||||||
|
# this policy key ensures the setting survives Windows Updates.
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Windows Search" `
|
||||||
|
-Name "SearchOnTaskbarMode" -Value 0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Start menu - hide Recommended section (Win11)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" `
|
||||||
|
-Name "HideRecommendedSection" -Value 1
|
||||||
|
|
||||||
Write-Log "Step 3 complete" -Level OK
|
Write-Log "Step 3 complete" -Level OK
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,10 @@ try {
|
||||||
# Win11: align taskbar to left (0 = left, 1 = center)
|
# Win11: align taskbar to left (0 = left, 1 = center)
|
||||||
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
|
Set-ProfileReg -SubKey $tbPath -Name "TaskbarAl" -Value 0
|
||||||
|
|
||||||
# Hide Search box / button (0 = hidden, 1 = icon, 2 = full box)
|
# Hide Search box / button - Win10/11 (0 = hidden, 1 = icon, 2 = full box)
|
||||||
|
# Note: Win11 uses Search subkey, Win10 uses Explorer\Advanced - set both
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Search" `
|
||||||
|
-Name "SearchboxTaskbarMode" -Value 0
|
||||||
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
|
Set-ProfileReg -SubKey $tbPath -Name "SearchboxTaskbarMode" -Value 0
|
||||||
|
|
||||||
# Hide Task View button
|
# Hide Task View button
|
||||||
|
|
@ -155,9 +158,34 @@ try {
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# System tray - show all icons
|
# System tray - show all icons
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
# EnableAutoTray = 0 works on Win10; Win11 ignores it but set anyway
|
||||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer" `
|
||||||
-Name "EnableAutoTray" -Value 0
|
-Name "EnableAutoTray" -Value 0
|
||||||
|
|
||||||
|
# Win11 workaround: clear cached tray icon streams so all icons appear on next login
|
||||||
|
# Windows rebuilds the streams with all icons visible when no cache exists
|
||||||
|
$trayNotifyKey = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayNotifyKey) {
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayNotifyKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams (Win11 systray workaround)" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# Also clear in Default hive so new users start with clean state
|
||||||
|
$defTrayKey = "Registry::HKU\DefaultProfile\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $defTrayKey) {
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $defTrayKey -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log " Cleared TrayNotify icon streams in Default hive" -Level OK
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# CLSID {20D04FE0-3AEA-1069-A2D8-08002B30309D} = This PC / Computer
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Start menu settings
|
# Start menu settings
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
@ -168,12 +196,19 @@ try {
|
||||||
-Name "DisableSearchBoxSuggestions" -Value 1
|
-Name "DisableSearchBoxSuggestions" -Value 1
|
||||||
|
|
||||||
# Win11: empty Start menu pins
|
# Win11: empty Start menu pins
|
||||||
$startPinsPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced"
|
|
||||||
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Start" `
|
||||||
-Name "ConfigureStartPins" `
|
-Name "ConfigureStartPins" `
|
||||||
-Value '{"pinnedList":[]}' `
|
-Value '{"pinnedList":[]}' `
|
||||||
-Type "String"
|
-Type "String"
|
||||||
|
|
||||||
|
# Hide "Recently added" apps in Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackProgs" -Value 0
|
||||||
|
|
||||||
|
# Hide recently opened files/docs from Start menu
|
||||||
|
Set-ProfileReg -SubKey "Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" `
|
||||||
|
-Name "Start_TrackDocs" -Value 0
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Copilot - disable
|
# Copilot - disable
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
@ -272,4 +307,18 @@ finally {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Restart Explorer to apply taskbar/tray changes to current session
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Restarting Explorer to apply taskbar changes" -Level INFO
|
||||||
|
try {
|
||||||
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Seconds 2
|
||||||
|
Start-Process explorer
|
||||||
|
Write-Log "Explorer restarted" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Explorer restart failed (non-fatal): $_" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
Write-Log "Step 4 complete" -Level OK
|
Write-Log "Step 4 complete" -Level OK
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,12 @@ function Apply-ThemeSettings {
|
||||||
|
|
||||||
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
Set-Reg -Path "$HiveRoot\Control Panel\Desktop" `
|
||||||
-Name "TileWallpaper" -Value "0" -Type "String"
|
-Name "TileWallpaper" -Value "0" -Type "String"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Desktop icons - show This PC
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Set-Reg -Path "$HiveRoot\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" `
|
||||||
|
-Name "{20D04FE0-3AEA-1069-A2D8-08002B30309D}" -Value 0
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ function Register-Task {
|
||||||
[string]$TaskName,
|
[string]$TaskName,
|
||||||
[string]$Description,
|
[string]$Description,
|
||||||
[object]$Action,
|
[object]$Action,
|
||||||
[object]$Trigger,
|
[object[]]$Triggers,
|
||||||
[string]$RunLevel = "Highest"
|
[string]$RunLevel = "Highest"
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
|
@ -36,7 +36,7 @@ function Register-Task {
|
||||||
-RunLevel $RunLevel
|
-RunLevel $RunLevel
|
||||||
|
|
||||||
$task = New-ScheduledTask -Action $Action `
|
$task = New-ScheduledTask -Action $Action `
|
||||||
-Trigger $Trigger `
|
-Trigger $Triggers `
|
||||||
-Settings $settings `
|
-Settings $settings `
|
||||||
-Principal $principal `
|
-Principal $principal `
|
||||||
-Description $Description
|
-Description $Description
|
||||||
|
|
@ -51,29 +51,40 @@ function Register-Task {
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Task: ShowAllTrayIcons
|
# Task: ShowAllTrayIcons
|
||||||
# Runs on logon + every 1 minute, sets EnableAutoTray=0 so all tray icons
|
# Runs on logon: clears TrayNotify icon cache and restarts Explorer so all
|
||||||
# are always visible (Win11 hides them by default)
|
# tray icons are visible on first login (Win10: EnableAutoTray=0, Win11: cache clear)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
|
Write-Log "Registering task: ShowAllTrayIcons" -Level STEP
|
||||||
|
|
||||||
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
|
$showTrayScript = "$ScriptDir\ShowAllTrayIcons.ps1"
|
||||||
@'
|
@'
|
||||||
|
# Win10: disable auto-hiding of tray icons
|
||||||
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
|
$regPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer"
|
||||||
Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force
|
Set-ItemProperty -Path $regPath -Name "EnableAutoTray" -Value 0 -Force -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Win11: clear icon stream cache so all icons become visible after Explorer restart
|
||||||
|
$trayPath = "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify"
|
||||||
|
if (Test-Path $trayPath) {
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "IconStreams" -Force -ErrorAction SilentlyContinue
|
||||||
|
Remove-ItemProperty -Path $trayPath -Name "PastIconsStream" -Force -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restart Explorer to apply changes
|
||||||
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
Stop-Process -Name explorer -Force -ErrorAction SilentlyContinue
|
||||||
|
Start-Sleep -Milliseconds 1500
|
||||||
|
if (-not (Get-Process explorer -ErrorAction SilentlyContinue)) {
|
||||||
|
Start-Process explorer
|
||||||
|
}
|
||||||
'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
|
'@ | Set-Content -Path $showTrayScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
$showTrayAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$showTrayScript`""
|
||||||
$showTrayTrigger = @(
|
$showTrayTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
$(New-ScheduledTaskTrigger -AtLogOn),
|
|
||||||
$(New-ScheduledTaskTrigger -RepetitionInterval (New-TimeSpan -Minutes 1) -Once -At (Get-Date))
|
|
||||||
)
|
|
||||||
|
|
||||||
Register-Task -TaskName "ShowAllTrayIcons" `
|
Register-Task -TaskName "ShowAllTrayIcons" `
|
||||||
-Description "Show all system tray icons for current user" `
|
-Description "Show all system tray icons for current user" `
|
||||||
-Action $showTrayAction `
|
-Action $showTrayAction `
|
||||||
-Trigger $showTrayTrigger[0]
|
-Triggers $showTrayTrigger
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Task: PDF-DefaultApp
|
# Task: PDF-DefaultApp
|
||||||
|
|
@ -84,7 +95,10 @@ Write-Log "Registering task: PDF-DefaultApp" -Level STEP
|
||||||
|
|
||||||
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1"
|
$pdfScript = "$ScriptDir\PDF-DefaultApp.ps1"
|
||||||
@'
|
@'
|
||||||
# Restore .pdf -> Adobe Reader association
|
# Restore .pdf -> Adobe Reader HKCR association (system-wide).
|
||||||
|
# Runs as SYSTEM so it can write to HKCR regardless of Edge updates.
|
||||||
|
# Note: HKCU UserChoice requires Windows Hash validation and cannot be
|
||||||
|
# set reliably via registry; HKCR provides the system-wide fallback.
|
||||||
$acroPaths = @(
|
$acroPaths = @(
|
||||||
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
"$env:ProgramFiles\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
"${env:ProgramFiles(x86)}\Adobe\Acrobat DC\Acrobat\Acrobat.exe"
|
||||||
|
|
@ -96,26 +110,43 @@ $acroExe = $acroPaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||||
if (-not $acroExe) { exit 0 }
|
if (-not $acroExe) { exit 0 }
|
||||||
|
|
||||||
$progId = "AcroExch.Document.DC"
|
$progId = "AcroExch.Document.DC"
|
||||||
|
$openCmd = "`"$acroExe`" `"%1`""
|
||||||
|
|
||||||
# Check current association
|
# HKCR\.pdf
|
||||||
$current = (Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice" `
|
if (-not (Test-Path "HKCR:\.pdf")) { New-Item -Path "HKCR:\.pdf" -Force | Out-Null }
|
||||||
-Name "ProgId" -ErrorAction SilentlyContinue).ProgId
|
$current = (Get-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -ErrorAction SilentlyContinue)."(Default)"
|
||||||
|
|
||||||
if ($current -ne $progId) {
|
if ($current -ne $progId) {
|
||||||
$ucPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.pdf\UserChoice"
|
Set-ItemProperty -Path "HKCR:\.pdf" -Name "(Default)" -Value $progId -Force
|
||||||
if (-not (Test-Path $ucPath)) { New-Item -Path $ucPath -Force | Out-Null }
|
|
||||||
Set-ItemProperty -Path $ucPath -Name "ProgId" -Value $progId -Force
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# HKCR\AcroExch.Document.DC\shell\open\command
|
||||||
|
$cmdPath = "HKCR:\$progId\shell\open\command"
|
||||||
|
if (-not (Test-Path $cmdPath)) { New-Item -Path $cmdPath -Force | Out-Null }
|
||||||
|
Set-ItemProperty -Path $cmdPath -Name "(Default)" -Value $openCmd -Force
|
||||||
'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
|
'@ | Set-Content -Path $pdfScript -Encoding UTF8 -Force
|
||||||
|
|
||||||
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
$pdfAction = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$pdfScript`""
|
||||||
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
|
$pdfTrigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
|
||||||
Register-Task -TaskName "PDF-DefaultApp" `
|
# Runs as SYSTEM to allow HKCR writes (system-wide file association)
|
||||||
-Description "Restore Adobe Reader as default PDF app on logon" `
|
$pdfPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
-Action $pdfAction `
|
$pdfSettings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
-Trigger $pdfTrigger
|
-MultipleInstances IgnoreNew `
|
||||||
|
-StartWhenAvailable
|
||||||
|
$pdfTask = New-ScheduledTask -Action $pdfAction `
|
||||||
|
-Trigger $pdfTrigger `
|
||||||
|
-Settings $pdfSettings `
|
||||||
|
-Principal $pdfPrincipal `
|
||||||
|
-Description "Restore Adobe Reader as default PDF app on logon"
|
||||||
|
try {
|
||||||
|
Unregister-ScheduledTask -TaskName "PDF-DefaultApp" -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
Register-ScheduledTask -TaskName "PDF-DefaultApp" -InputObject $pdfTask -Force | Out-Null
|
||||||
|
Write-Log " Registered task: PDF-DefaultApp" -Level OK
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log " Failed to register task PDF-DefaultApp - $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Task: UnlockStartLayout
|
# Task: UnlockStartLayout
|
||||||
|
|
|
||||||
|
|
@ -19,137 +19,154 @@ if (-not (Test-Path $ScriptDir)) {
|
||||||
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
New-Item -ItemType Directory -Path $ScriptDir -Force | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
# Read display settings from config
|
|
||||||
# -----------------------------------------------------------------------
|
|
||||||
$fontSize = 13
|
|
||||||
$fontColor = "#FFFFFF"
|
|
||||||
$position = "bottomRight"
|
|
||||||
|
|
||||||
if ($Config -and $Config.desktopInfo) {
|
|
||||||
if ($Config.desktopInfo.fontSize) { $fontSize = [int]$Config.desktopInfo.fontSize }
|
|
||||||
if ($Config.desktopInfo.fontColor) { $fontColor = $Config.desktopInfo.fontColor }
|
|
||||||
if ($Config.desktopInfo.position) { $position = $Config.desktopInfo.position }
|
|
||||||
}
|
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Write the rendering script (runs on every logon as the user)
|
# Write the rendering script (runs on every logon as the user)
|
||||||
|
# Layout: hostname (large bold, centered), then detail lines centered
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
|
Write-Log "Writing DesktopInfo render script to $RenderScript" -Level INFO
|
||||||
|
|
||||||
$renderContent = @"
|
$renderContent = @'
|
||||||
# DesktopInfo-Render.ps1
|
# DesktopInfo-Render.ps1
|
||||||
# Collects system info and renders it onto the desktop wallpaper.
|
# Collects system info and renders it centered on the desktop wallpaper.
|
||||||
# Runs on every user logon via Scheduled Task.
|
# Runs on every user logon via Scheduled Task.
|
||||||
|
|
||||||
`$ErrorActionPreference = "Continue"
|
$ErrorActionPreference = "Continue"
|
||||||
|
$LogFile = "C:\Windows\Setup\Scripts\desktopinfo.log"
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
Add-Content -Path $LogFile -Value "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message" -Encoding UTF8
|
||||||
|
}
|
||||||
|
Write-Log "DesktopInfo render started" -Level INFO
|
||||||
|
|
||||||
Add-Type -AssemblyName System.Drawing
|
Add-Type -AssemblyName System.Drawing
|
||||||
Add-Type -TypeDefinition @'
|
Add-Type -AssemblyName System.Windows.Forms
|
||||||
|
Add-Type -TypeDefinition @"
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
public class WallpaperApi {
|
public class WallpaperApi {
|
||||||
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
[DllImport("user32.dll", CharSet=CharSet.Auto)]
|
||||||
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
|
||||||
}
|
}
|
||||||
'@ -ErrorAction SilentlyContinue
|
"@ -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Collect system info
|
# Collect system info
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
`$hostname = `$env:COMPUTERNAME
|
Write-Log "Collecting system info"
|
||||||
`$username = `$env:USERNAME
|
$hostname = $env:COMPUTERNAME
|
||||||
`$ipAddress = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
$userDomain = $env:USERDOMAIN
|
||||||
Where-Object { `$_.IPAddress -ne "127.0.0.1" -and `$_.PrefixOrigin -ne "WellKnown" } |
|
$userName = $env:USERNAME
|
||||||
Select-Object -First 1).IPAddress
|
$loggedUser = if ($userDomain -and $userDomain -ne $hostname) { "$userDomain\$userName" } else { "$hostname\$userName" }
|
||||||
if (-not `$ipAddress) { `$ipAddress = "N/A" }
|
|
||||||
|
|
||||||
`$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
$osInfo = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue
|
||||||
`$osName = if (`$osInfo) { `$osInfo.Caption -replace "Microsoft ", "" } else { "Windows" }
|
$osName = if ($osInfo) { $osInfo.Caption -replace "^Microsoft\s*", "" } else { "Windows" }
|
||||||
`$osBuild = if (`$osInfo) { `$osInfo.BuildNumber } else { "" }
|
$ramGB = if ($osInfo) { [math]::Round($osInfo.TotalVisibleMemorySize / 1024 / 1024, 1) } else { "?" }
|
||||||
|
|
||||||
# Deployment date = when script was first run, stored in registry
|
$cpuInfo = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||||
`$deployRegPath = "HKLM:\SOFTWARE\X9\Deployment"
|
$cpuCount = if ($cpuInfo) { $cpuInfo.NumberOfLogicalProcessors } else { "?" }
|
||||||
`$deployDate = (Get-ItemProperty -Path `$deployRegPath -Name "DeployDate" -ErrorAction SilentlyContinue).DeployDate
|
$cpuSpeed = if ($cpuInfo) { $cpuInfo.MaxClockSpeed } else { "?" }
|
||||||
if (-not `$deployDate) { `$deployDate = "N/A" }
|
|
||||||
|
$ips = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.IPAddress -ne "127.0.0.1" -and $_.PrefixOrigin -ne "WellKnown" } |
|
||||||
|
Select-Object -ExpandProperty IPAddress) -join ", "
|
||||||
|
if (-not $ips) { $ips = "N/A" }
|
||||||
|
|
||||||
|
$csInfo = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue
|
||||||
|
$domain = if ($csInfo -and $csInfo.PartOfDomain) { $csInfo.Domain } `
|
||||||
|
elseif ($csInfo -and $csInfo.Workgroup) { $csInfo.Workgroup.ToLower() } `
|
||||||
|
else { "N/A" }
|
||||||
|
|
||||||
|
Write-Log "hostname=$hostname user=$loggedUser os=$osName ram=$($ramGB)GB cpu=${cpuCount}x${cpuSpeed}MHz ips=$ips domain=$domain"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Build info lines
|
# Screen dimensions
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
`$lines = @(
|
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
||||||
"Computer : `$hostname"
|
$width = if ($screen) { $screen.Bounds.Width } else { 1920 }
|
||||||
"User : `$username"
|
$height = if ($screen) { $screen.Bounds.Height } else { 1080 }
|
||||||
"IP : `$ipAddress"
|
Write-Log "screen=${width}x${height}"
|
||||||
"OS : `$osName (build `$osBuild)"
|
|
||||||
"Deployed : `$deployDate"
|
# -----------------------------------------------------------------------
|
||||||
|
# Create bitmap and graphics context
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$bmp = New-Object System.Drawing.Bitmap($width, $height)
|
||||||
|
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
||||||
|
$g.TextRenderingHint = [System.Drawing.Text.TextRenderingHint]::AntiAlias
|
||||||
|
$g.Clear([System.Drawing.ColorTranslator]::FromHtml("#556364"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Fonts and brushes
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$fontName = "Segoe UI"
|
||||||
|
$fontTitle = New-Object System.Drawing.Font($fontName, 36, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontBold = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Bold)
|
||||||
|
$fontReg = New-Object System.Drawing.Font($fontName, 14, [System.Drawing.FontStyle]::Regular)
|
||||||
|
$brushWhite = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::White)
|
||||||
|
$brushGray = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("#C8D2D2"))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Lines: text, font, brush
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
$texts = @(
|
||||||
|
$hostname
|
||||||
|
"Logged on user: $loggedUser"
|
||||||
|
"OS: $osName"
|
||||||
|
"CPU: $cpuCount at $cpuSpeed MHz RAM: $($ramGB)GB"
|
||||||
|
"IPv4 address: $ips Machine domain: $domain"
|
||||||
)
|
)
|
||||||
|
$fonts = @( $fontTitle, $fontReg, $fontBold, $fontReg, $fontReg )
|
||||||
|
$brushes = @( $brushWhite, $brushGray, $brushGray, $brushGray, $brushGray )
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Render bitmap
|
# Measure total block height, then center vertically
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Add-Type -AssemblyName System.Windows.Forms -ErrorAction SilentlyContinue
|
$lineSpacing = 8
|
||||||
|
$heights = @()
|
||||||
`$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
`$width = if (`$screen) { `$screen.Bounds.Width } else { 1920 }
|
$heights += [int]($g.MeasureString($texts[$i], $fonts[$i]).Height)
|
||||||
`$height = if (`$screen) { `$screen.Bounds.Height } else { 1080 }
|
|
||||||
|
|
||||||
`$bmp = New-Object System.Drawing.Bitmap(`$width, `$height)
|
|
||||||
`$g = [System.Drawing.Graphics]::FromImage(`$bmp)
|
|
||||||
|
|
||||||
# Background: solid accent color #223B47
|
|
||||||
`$bgColor = [System.Drawing.ColorTranslator]::FromHtml("#223B47")
|
|
||||||
`$g.Clear(`$bgColor)
|
|
||||||
|
|
||||||
# Font and colors
|
|
||||||
`$fontFamily = "Consolas"
|
|
||||||
`$fontSize = $fontSize
|
|
||||||
`$font = New-Object System.Drawing.Font(`$fontFamily, `$fontSize, [System.Drawing.FontStyle]::Regular)
|
|
||||||
`$brush = New-Object System.Drawing.SolidBrush([System.Drawing.ColorTranslator]::FromHtml("$fontColor"))
|
|
||||||
`$shadowBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(180, 0, 0, 0))
|
|
||||||
|
|
||||||
# Measure text block
|
|
||||||
`$lineHeight = `$font.GetHeight(`$g) + 4
|
|
||||||
`$blockH = `$lines.Count * `$lineHeight
|
|
||||||
`$maxWidth = (`$lines | ForEach-Object { `$g.MeasureString(`$_, `$font).Width } | Measure-Object -Maximum).Maximum
|
|
||||||
|
|
||||||
# Position
|
|
||||||
`$margin = 24
|
|
||||||
`$pos = "$position"
|
|
||||||
`$x = switch -Wildcard (`$pos) {
|
|
||||||
"*Right" { `$width - `$maxWidth - `$margin }
|
|
||||||
"*Left" { `$margin }
|
|
||||||
default { `$margin }
|
|
||||||
}
|
}
|
||||||
`$y = switch -Wildcard (`$pos) {
|
$totalH = ($heights | Measure-Object -Sum).Sum + $lineSpacing * ($texts.Count - 1)
|
||||||
"bottom*" { `$height - `$blockH - `$margin }
|
$currentY = [int](($height - $totalH) / 2)
|
||||||
"top*" { `$margin }
|
|
||||||
default { `$height - `$blockH - `$margin }
|
# -----------------------------------------------------------------------
|
||||||
|
# Draw each line centered horizontally
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
for ($i = 0; $i -lt $texts.Count; $i++) {
|
||||||
|
$sz = $g.MeasureString($texts[$i], $fonts[$i])
|
||||||
|
$x = [int](($width - $sz.Width) / 2)
|
||||||
|
$g.DrawString($texts[$i], $fonts[$i], $brushes[$i], [float]$x, [float]$currentY)
|
||||||
|
$currentY += $heights[$i] + $lineSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
# Draw shadow then text
|
$g.Dispose()
|
||||||
foreach (`$line in `$lines) {
|
|
||||||
`$g.DrawString(`$line, `$font, `$shadowBrush, (`$x + 1), (`$y + 1))
|
# -----------------------------------------------------------------------
|
||||||
`$g.DrawString(`$line, `$font, `$brush, `$x, `$y)
|
# Save and set as wallpaper
|
||||||
`$y += `$lineHeight
|
# -----------------------------------------------------------------------
|
||||||
|
$bmpPath = "C:\Windows\Setup\Scripts\desktopinfo.bmp"
|
||||||
|
Write-Log "Saving BMP: $bmpPath"
|
||||||
|
$bmp.Save($bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
|
||||||
|
$bmp.Dispose()
|
||||||
|
|
||||||
|
# Clear Windows wallpaper cache so it reloads from our BMP
|
||||||
|
# Without this, Windows reuses TranscodedWallpaper and ignores the updated file
|
||||||
|
$transcodedPath = "$env:APPDATA\Microsoft\Windows\Themes\TranscodedWallpaper"
|
||||||
|
if (Test-Path $transcodedPath) {
|
||||||
|
Remove-Item $transcodedPath -Force -ErrorAction SilentlyContinue
|
||||||
|
Write-Log "Cleared TranscodedWallpaper cache"
|
||||||
}
|
}
|
||||||
|
|
||||||
`$g.Dispose()
|
|
||||||
|
|
||||||
# Save BMP
|
|
||||||
`$bmpPath = "$BmpPath"
|
|
||||||
`$bmp.Save(`$bmpPath, [System.Drawing.Imaging.ImageFormat]::Bmp)
|
|
||||||
`$bmp.Dispose()
|
|
||||||
|
|
||||||
# Set as wallpaper
|
|
||||||
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
# SPI_SETDESKTOPWALLPAPER=20, SPIF_UPDATEINIFILE|SPIF_SENDCHANGE=3
|
||||||
[WallpaperApi]::SystemParametersInfo(20, 0, `$bmpPath, 3) | Out-Null
|
$result = [WallpaperApi]::SystemParametersInfo(20, 0, $bmpPath, 3)
|
||||||
"@
|
Write-Log "SystemParametersInfo result: $result"
|
||||||
|
Write-Log "DesktopInfo render complete" -Level INFO
|
||||||
|
'@
|
||||||
|
|
||||||
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
|
$renderContent | Set-Content -Path $RenderScript -Encoding UTF8 -Force
|
||||||
Write-Log "Render script written" -Level OK
|
Write-Log "Render script written" -Level OK
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Store deployment date in registry (used by render script)
|
# Store deployment date in registry (used for reference)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
Write-Log "Storing deployment date in registry" -Level INFO
|
Write-Log "Storing deployment date in registry" -Level INFO
|
||||||
try {
|
try {
|
||||||
|
|
@ -184,6 +201,7 @@ try {
|
||||||
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$RenderScript`""
|
||||||
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
$trigger.Delay = "PT20S" # wait for network to be available
|
||||||
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 2) `
|
||||||
-MultipleInstances IgnoreNew `
|
-MultipleInstances IgnoreNew `
|
||||||
-StartWhenAvailable
|
-StartWhenAvailable
|
||||||
|
|
|
||||||
|
|
@ -211,6 +211,15 @@ Test-Check "File extensions visible" {
|
||||||
Test-Check "Explorer opens to This PC" {
|
Test-Check "Explorer opens to This PC" {
|
||||||
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "LaunchTo") -eq 1
|
||||||
}
|
}
|
||||||
|
Test-Check "This PC icon on desktop" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\HideDesktopIcons\NewStartPanel" "{20D04FE0-3AEA-1069-A2D8-08002B30309D}") -eq 0
|
||||||
|
}
|
||||||
|
Test-Check "Start menu Recommended section hidden" {
|
||||||
|
(Get-RegValue "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Explorer" "HideRecommendedSection") -eq 1
|
||||||
|
}
|
||||||
|
Test-Check "Start menu recently added hidden" {
|
||||||
|
(Get-RegValue "HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" "Start_TrackProgs") -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Scheduled tasks
|
# Scheduled tasks
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue