Add admin account creation and Windows activation steps
- 00-admin-account.ps1: create/update adminx9, add to Administrators, hide from login screen via SpecialAccounts\UserList - 08-activation.ps1: activate via config key or GVLK fallback matched by OS edition; supports optional KMS server; skips if already active - config.json: add adminAccount block (password), activation block (productKey placeholder, kmsServer) - Deploy-Windows.ps1: add Step 0a and Step 0b before bloatware removal - Test-Deployment.ps1: add checks for admin account and activation - SPEC.md: document new steps, close open question #4 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
30d930c667
commit
97bd9dfc76
6 changed files with 280 additions and 3 deletions
|
|
@ -96,6 +96,20 @@ Invoke-Step -Name "Load config.json" -Action {
|
||||||
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
Write-Log "Config loaded from $ConfigFile" -Level INFO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0a - Admin account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Invoke-Step -Name "Step 0a - Admin account" -Action {
|
||||||
|
& "$ScriptRoot\scripts\00-admin-account.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Step 0b - Windows activation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Invoke-Step -Name "Step 0b - Windows activation" -Action {
|
||||||
|
& "$ScriptRoot\scripts\08-activation.ps1" -Config $Config -LogFile $LogFile
|
||||||
|
}
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Step 1 - Bloatware removal
|
# Step 1 - Bloatware removal
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
|
||||||
22
SPEC.md
22
SPEC.md
|
|
@ -37,6 +37,26 @@ Script is divided into steps. Each step logs its result. Steps can be skipped wi
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## STEP 0a - Admin account
|
||||||
|
|
||||||
|
Creates local admin account `adminx9`:
|
||||||
|
- Password from `config.json` (`adminAccount.password`)
|
||||||
|
- Added to Administrators group
|
||||||
|
- Password never expires, user cannot change password
|
||||||
|
- Hidden from Windows login screen (SpecialAccounts\UserList = 0)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## STEP 0b - Windows activation
|
||||||
|
|
||||||
|
Activates Windows using product key from config:
|
||||||
|
- Key from `config.json` (`activation.productKey`) - set to real MAK/retail key for production
|
||||||
|
- Falls back to GVLK (KMS client key) matched by detected OS edition
|
||||||
|
- Optional KMS server via `activation.kmsServer`
|
||||||
|
- If already activated, skips silently
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## STEP 1 - Bloatware removal
|
## STEP 1 - Bloatware removal
|
||||||
|
|
||||||
### 1a - AppX packages (UWP apps)
|
### 1a - AppX packages (UWP apps)
|
||||||
|
|
@ -270,4 +290,4 @@ Custom PowerShell scheduled task. No external dependencies.
|
||||||
| 1 | BackInfo replacement | DONE - custom PS scheduled task DesktopInfo |
|
| 1 | BackInfo replacement | DONE - custom PS scheduled task DesktopInfo |
|
||||||
| 2 | Complete SW list for winget | TODO |
|
| 2 | Complete SW list for winget | TODO |
|
||||||
| 3 | Per-client variability via config.json | FUTURE |
|
| 3 | Per-client variability via config.json | FUTURE |
|
||||||
| 4 | Admin account adminx9 - script or manual? | OPEN |
|
| 4 | Admin account adminx9 - script or manual? | DONE - script (00-admin-account.ps1) |
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
{
|
{
|
||||||
"deployment": {
|
"deployment": {
|
||||||
"timezone": "Central Europe Standard Time",
|
"timezone": "Central Europe Standard Time",
|
||||||
"locale": "cs-CZ",
|
"locale": "cs-CZ"
|
||||||
"adminAccount": "adminx9"
|
},
|
||||||
|
"adminAccount": {
|
||||||
|
"username": "adminx9",
|
||||||
|
"password": "AdminX9.AdminX9",
|
||||||
|
"description": "X9 MSP admin account"
|
||||||
|
},
|
||||||
|
"activation": {
|
||||||
|
"productKey": "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX",
|
||||||
|
"kmsServer": ""
|
||||||
},
|
},
|
||||||
"software": {
|
"software": {
|
||||||
"install": [
|
"install": [
|
||||||
|
|
|
||||||
94
scripts/00-admin-account.ps1
Normal file
94
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
|
||||||
109
scripts/08-activation.ps1
Normal file
109
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
|
||||||
|
|
@ -61,6 +61,38 @@ Test-Check "Deploy.log exists" {
|
||||||
Test-Path "C:\Windows\Setup\Scripts\Deploy.log"
|
Test-Path "C:\Windows\Setup\Scripts\Deploy.log"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Admin account
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Admin account ---"
|
||||||
|
Test-Check "Account adminx9 exists" {
|
||||||
|
Get-LocalUser -Name "adminx9" -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 is enabled" {
|
||||||
|
(Get-LocalUser -Name "adminx9" -ErrorAction SilentlyContinue).Enabled -eq $true
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 in Administrators" {
|
||||||
|
$adminsGroup = (Get-LocalGroup | Where-Object { $_.SID -eq "S-1-5-32-544" }).Name
|
||||||
|
Get-LocalGroupMember -Group $adminsGroup -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -like "*adminx9" }
|
||||||
|
}
|
||||||
|
Test-Check "Account adminx9 hidden from login screen" {
|
||||||
|
$specialPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList"
|
||||||
|
(Get-ItemProperty -Path $specialPath -Name "adminx9" -ErrorAction SilentlyContinue).adminx9 -eq 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# Activation
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "--- Activation ---"
|
||||||
|
Test-Check "Windows activated" {
|
||||||
|
$status = (Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND Name LIKE 'Windows%'" -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object -First 1).LicenseStatus
|
||||||
|
$status -eq 1
|
||||||
|
} -WarnOnly
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
# Software
|
# Software
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue