Skip to content

Commit aebc688

Browse files
committed
v1.22.2 — QuickScan CLI action, bootstrap installer, automation docs
QuickScan combines health + disk + debloat analysis in one pass. Bootstrap installer for one-liner remote deployment from GitHub releases. CLI exits with proper codes (0/1) for CI/CD integration. README updated with CLI headless mode docs and Ansible examples.
1 parent 1d27f57 commit aebc688

8 files changed

Lines changed: 289 additions & 10 deletions

File tree

Changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## v1.22.2
4+
5+
- **New Feature:** QuickScan CLI action — combined health check + disk analysis + debloat recommendations in a single pass (`-Action QuickScan`). Useful for first-time assessment of any machine (50-EntryPoint).
6+
- **New Feature:** Bootstrap installer (`Install-RackStack.ps1`) — one-liner remote deployment that downloads the latest release from GitHub and runs it with CLI parameters. Works with Ansible, RMM tools, PDQ, and any tool that can execute PowerShell. Includes version caching, TLS 1.2 enforcement, and proper exit codes.
7+
- **Improvement:** CLI headless mode now exits with proper exit codes (0 = success, 1 = error) for CI/CD integration.
8+
- 65 modules, 2554 tests
9+
310
## v1.22.1
411

512
- **New Feature:** Structured error code system — 46 error codes across 8 categories (RS-1xxx Core, RS-2xxx Network, RS-3xxx Security, RS-4xxx Roles, RS-5xxx VM, RS-6xxx Storage, RS-7xxx Config, RS-8xxx Agent) with wiki-linked troubleshooting. Errors display code, message, and clickable hyperlink to wiki documentation. OSC 8 hyperlinks in Windows Terminal, plain URL fallback elsewhere (02-Logging).

Header.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.22.1
33+
1.22.2
3434
3535
.LAST UPDATED
3636
03/12/2026
@@ -1390,7 +1390,7 @@
13901390
param(
13911391
# CLI headless mode: run a specific action without interactive menus
13921392
# Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent]
1393-
[ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch')]
1393+
[ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan')]
13941394
[string]$Action,
13951395

13961396
[ValidateSet('Light', 'Standard', 'Aggressive')]

Install-RackStack.ps1

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<#
2+
.SYNOPSIS
3+
RackStack Bootstrap Installer — download and run with one command.
4+
5+
.DESCRIPTION
6+
Downloads the latest RackStack.exe from GitHub Releases and optionally
7+
runs it with CLI parameters. Designed for remote deployment via
8+
Ansible, RMM tools, PDQ, or any tool that can execute PowerShell.
9+
10+
One-liner usage (run as Administrator):
11+
irm https://raw.githubusercontent.com/TheAbider/RackStack/master/Install-RackStack.ps1 | iex
12+
13+
With parameters (download + run):
14+
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/TheAbider/RackStack/master/Install-RackStack.ps1)))
15+
16+
Ansible example:
17+
ansible windows -m win_shell -a "irm https://raw.githubusercontent.com/TheAbider/RackStack/master/Install-RackStack.ps1 | iex"
18+
19+
.PARAMETER Action
20+
CLI action to run after download: Cleanup, Debloat, HealthCheck, QuickScan, Batch
21+
22+
.PARAMETER Tier
23+
Profile tier: Light, Standard, Aggressive (default: Standard)
24+
25+
.PARAMETER Silent
26+
Auto-confirm all prompts
27+
28+
.PARAMETER InstallPath
29+
Where to save RackStack.exe (default: C:\Temp\RackStack)
30+
31+
.PARAMETER NoRun
32+
Download only, do not execute
33+
34+
.NOTES
35+
Requires: PowerShell 5.1+, Administrator privileges, Internet access
36+
#>
37+
38+
param(
39+
[ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan')]
40+
[string]$Action = 'QuickScan',
41+
42+
[ValidateSet('Light', 'Standard', 'Aggressive')]
43+
[string]$Tier = 'Standard',
44+
45+
[switch]$Silent,
46+
47+
[string]$InstallPath = 'C:\Temp\RackStack',
48+
49+
[switch]$NoRun
50+
)
51+
52+
$ErrorActionPreference = 'Stop'
53+
54+
# Enforce TLS 1.2
55+
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
56+
57+
Write-Host ""
58+
Write-Host " RackStack Bootstrap Installer" -ForegroundColor Cyan
59+
Write-Host " =============================" -ForegroundColor Cyan
60+
Write-Host ""
61+
62+
# Check for admin
63+
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
64+
if (-not $isAdmin) {
65+
Write-Host " ERROR: This script requires Administrator privileges." -ForegroundColor Red
66+
Write-Host " Run PowerShell as Administrator and try again." -ForegroundColor Yellow
67+
exit 1
68+
}
69+
70+
# Create install directory
71+
if (-not (Test-Path -LiteralPath $InstallPath)) {
72+
Write-Host " Creating directory: $InstallPath" -ForegroundColor Gray
73+
New-Item -Path $InstallPath -ItemType Directory -Force | Out-Null
74+
}
75+
76+
$exePath = Join-Path $InstallPath "RackStack.exe"
77+
78+
# Get latest release URL from GitHub API
79+
Write-Host " Checking latest release..." -ForegroundColor Gray
80+
try {
81+
$releaseInfo = Invoke-RestMethod -Uri "https://api.github.com/repos/TheAbider/RackStack/releases/latest" -UseBasicParsing
82+
$version = $releaseInfo.tag_name
83+
$exeAsset = $releaseInfo.assets | Where-Object { $_.name -eq "RackStack.exe" } | Select-Object -First 1
84+
85+
if (-not $exeAsset) {
86+
Write-Host " ERROR: RackStack.exe not found in latest release." -ForegroundColor Red
87+
exit 1
88+
}
89+
90+
$downloadUrl = $exeAsset.browser_download_url
91+
Write-Host " Latest version: $version" -ForegroundColor Green
92+
}
93+
catch {
94+
Write-Host " ERROR: Failed to query GitHub releases: $_" -ForegroundColor Red
95+
exit 1
96+
}
97+
98+
# Check if we already have this version
99+
$needsDownload = $true
100+
if (Test-Path -LiteralPath $exePath) {
101+
try {
102+
$existingVersion = (Get-Item $exePath).VersionInfo.FileVersion
103+
if ($existingVersion -and $version -eq "v$existingVersion") {
104+
Write-Host " Already up to date ($version)" -ForegroundColor Green
105+
$needsDownload = $false
106+
}
107+
else {
108+
Write-Host " Updating from v$existingVersion to $version" -ForegroundColor Yellow
109+
}
110+
}
111+
catch {
112+
# Can't read version, re-download
113+
}
114+
}
115+
116+
# Download
117+
if ($needsDownload) {
118+
Write-Host " Downloading RackStack.exe ($([math]::Round($exeAsset.size / 1MB, 1)) MB)..." -ForegroundColor Gray
119+
try {
120+
Invoke-WebRequest -Uri $downloadUrl -OutFile $exePath -UseBasicParsing
121+
Write-Host " Downloaded to: $exePath" -ForegroundColor Green
122+
}
123+
catch {
124+
Write-Host " ERROR: Download failed: $_" -ForegroundColor Red
125+
exit 1
126+
}
127+
}
128+
129+
if ($NoRun) {
130+
Write-Host ""
131+
Write-Host " Download complete. Run manually:" -ForegroundColor Cyan
132+
Write-Host " $exePath -Action $Action -Tier $Tier -Silent" -ForegroundColor White
133+
Write-Host ""
134+
exit 0
135+
}
136+
137+
# Run with CLI parameters
138+
Write-Host ""
139+
Write-Host " Launching RackStack -Action $Action -Tier $Tier$(if ($Silent) { ' -Silent' })..." -ForegroundColor Cyan
140+
Write-Host ""
141+
142+
$args = @("-Action", $Action, "-Tier", $Tier)
143+
if ($Silent) { $args += "-Silent" }
144+
145+
try {
146+
$process = Start-Process -FilePath $exePath -ArgumentList $args -Wait -PassThru -NoNewWindow
147+
exit $process.ExitCode
148+
}
149+
catch {
150+
Write-Host " ERROR: Failed to launch RackStack: $_" -ForegroundColor Red
151+
exit 1
152+
}

Modules/00-Initialization.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
160160
if (-not $script:ModuleRoot -and $script:ScriptPath) {
161161
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
162162
}
163-
$script:ScriptVersion = "1.22.1"
163+
$script:ScriptVersion = "1.22.2"
164164
$script:ScriptStartTime = Get-Date
165165

166166
# CLI headless mode parameters (populated from param block in monolithic/exe)

Modules/50-EntryPoint.ps1

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,24 @@ function Invoke-CLIAction {
217217
[Environment]::Exit(1)
218218
}
219219
}
220+
'QuickScan' {
221+
Write-OutputColor " Running quick scan (health + disk + debloat analysis)..." -color "Info"
222+
Write-OutputColor "" -color "Info"
223+
224+
# Phase 1: System health
225+
Write-OutputColor " --- SYSTEM HEALTH ---" -color "Info"
226+
Show-SystemHealthCheck
227+
228+
# Phase 2: Disk space analysis
229+
Write-OutputColor "" -color "Info"
230+
Write-OutputColor " --- DISK SPACE ANALYSIS ---" -color "Info"
231+
Show-EnhancedCleanupAnalysis
232+
233+
# Phase 3: Debloat recommendations
234+
Write-OutputColor "" -color "Info"
235+
Write-OutputColor " --- DEBLOAT RECOMMENDATIONS ---" -color "Info"
236+
Show-DebloatAnalysis
237+
}
220238
default {
221239
Write-OutputColor " Unknown CLI action: $($script:CLIAction)" -color "Error"
222240
[Environment]::Exit(1)
@@ -225,6 +243,11 @@ function Invoke-CLIAction {
225243

226244
Write-OutputColor "" -color "Info"
227245
Write-OutputColor " CLI action completed successfully." -color "Success"
246+
247+
# Exit with proper code for automation consumers
248+
if ($script:HeadlessMode) {
249+
[Environment]::Exit(0)
250+
}
228251
}
229252

230253
# Dry-run mode flag (set per batch session)

README.md

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,52 @@ Place `batch_config.json` next to the script and it runs automatically on launch
269269

270270
New in v1.8.0: `InstallAgents` array for multi-agent installs, `ValidateCluster` for cluster readiness checks.
271271

272+
## CLI Headless Mode
273+
274+
Run RackStack non-interactively for automation (Ansible, RMM tools, PDQ, remote scripts):
275+
276+
```powershell
277+
# Quick health + disk + debloat assessment
278+
RackStack.exe -Action QuickScan -Silent
279+
280+
# Disk cleanup (Standard profile)
281+
RackStack.exe -Action Cleanup -Tier Standard -Silent
282+
283+
# System debloat (Aggressive, auto-detects Server vs Workstation)
284+
RackStack.exe -Action Debloat -Tier Aggressive -Silent
285+
286+
# Run a batch config JSON file
287+
RackStack.exe -Action Batch -Config "C:\path\to\config.json" -Silent
288+
```
289+
290+
### One-Liner Remote Deployment
291+
292+
Download and run the latest release in one command:
293+
294+
```powershell
295+
# Download + QuickScan (default)
296+
irm https://raw.githubusercontent.com/TheAbider/RackStack/master/Install-RackStack.ps1 | iex
297+
298+
# Download + specific action
299+
powershell -NoProfile -ExecutionPolicy Bypass -Command "& { $s = irm https://raw.githubusercontent.com/TheAbider/RackStack/master/Install-RackStack.ps1; $s | Set-Content rs.ps1; .\rs.ps1 -Action Cleanup -Tier Standard -Silent }"
300+
```
301+
302+
### Ansible Example
303+
304+
```yaml
305+
- name: Run RackStack cleanup on Windows hosts
306+
win_shell: |
307+
$url = 'https://github.com/TheAbider/RackStack/releases/latest/download/RackStack.exe'
308+
Invoke-WebRequest -Uri $url -OutFile C:\Temp\RackStack.exe -UseBasicParsing
309+
C:\Temp\RackStack.exe -Action Cleanup -Tier Standard -Silent
310+
```
311+
312+
**Exit codes:** `0` = success, `1` = error. All CLI actions return proper exit codes for CI/CD integration.
313+
314+
**Tiers:** `Light` (minimal, safe for prod), `Standard` (recommended), `Aggressive` (maximum cleanup/debloat).
315+
316+
**Actions:** `Cleanup` (disk cleanup), `Debloat` (remove bloatware + telemetry), `HealthCheck` (system health report), `QuickScan` (health + disk + debloat analysis), `Batch` (JSON-driven full config).
317+
272318
## Project Structure
273319

274320
```
@@ -283,9 +329,9 @@ RackStack/
283329
│ ├── 00-Initialization.ps1 # Constants, variables, config loading
284330
│ ├── 01-Console.ps1 # Console window management
285331
│ ├── ... # 61 more modules
286-
│ └── 62-HyperVReplica.ps1
332+
│ └── 64-SystemDebloat.ps1
287333
├── Tests/
288-
│ ├── Run-Tests.ps1 # 1854 automated tests
334+
│ ├── Run-Tests.ps1 # 2500+ automated tests
289335
│ ├── Validate-Release.ps1 # Pre-release validation suite
290336
│ └── ...
291337
└── docs/
@@ -306,12 +352,12 @@ RackStack/
306352
| 40-44 | **VM Pipeline** | Host storage, VHD management, ISO downloads, offline VHD, VM deployment |
307353
| 45-50 | **Session** | Config export, session summary, cleanup, menus, entry point |
308354
| 51-59 | **Extended** | Cluster dashboard, checkpoints, export/import, HTML reports, QoL, operations, remote, diagnostics, storage backends |
309-
| 60-62 | **Server Roles** | Role templates, AD DS promotion, Hyper-V Replica management |
355+
| 60-64 | **Server Roles** | Role templates, AD DS promotion, Hyper-V Replica, scheduled tasks, system debloat |
310356
311357
## Testing
312358
313359
```powershell
314-
# Full test suite (~1,854 tests, ~2 minutes)
360+
# Full test suite (~2,500+ tests, ~4 minutes)
315361
powershell -ExecutionPolicy Bypass -File Tests\Run-Tests.ps1
316362
317363
# PSScriptAnalyzer (0 errors on all 65 modules + monolithic)
@@ -331,7 +377,7 @@ Tests cover parsing, module loading, function existence (300+), version consiste
331377
4. Test: `.\Tests\Run-Tests.ps1`
332378
5. Compile: `Invoke-PS2EXE -InputFile 'RackStack v{ver}.ps1' -OutputFile 'RackStack.exe'`
333379

334-
The sync script matches `#region`/`#endregion` markers between modules and the monolithic file. All 62 region pairs are flat (non-nested). Use `-DryRun` to preview.
380+
The sync script matches `#region`/`#endregion` markers between modules and the monolithic file. All 64 region pairs are flat (non-nested). Use `-DryRun` to preview.
335381

336382
> **File summary:** `RackStack.ps1` = modular loader (for dev). `RackStack v{version}.ps1` = monolithic build (for deployment). `RackStack.exe` = compiled from monolithic (for end users).
337383

RackStack.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Environment-specific settings are configured via defaults.json.
1414
1515
.VERSION
16-
1.22.1
16+
1.22.2
1717
1818
.NOTES
1919
- Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing)

Tests/Run-Tests.ps1

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<#
22
.SYNOPSIS
3-
Automated Test Runner for RackStack v1.22.1
3+
Automated Test Runner for RackStack v1.22.2
44

55
.DESCRIPTION
66
Comprehensive non-interactive test suite covering:
@@ -9491,6 +9491,57 @@ try {
94919491
Write-TestResult "Error code system tests" $false $_.Exception.Message
94929492
}
94939493

9494+
# ============================================================================
9495+
# SECTION: CLI HEADLESS MODE & AUTOMATION
9496+
# ============================================================================
9497+
Write-SectionHeader "CLI HEADLESS MODE & AUTOMATION"
9498+
9499+
try {
9500+
$mod50 = Get-Content -LiteralPath (Join-Path $modulesPath "50-EntryPoint.ps1") -Raw -ErrorAction Stop
9501+
$headerContent = Get-Content -LiteralPath (Join-Path $script:ModuleRoot "Header.ps1") -Raw -ErrorAction Stop
9502+
9503+
# CLI action dispatch
9504+
Write-TestResult "50-EntryPoint: Invoke-CLIAction defined" ($mod50 -match 'function\s+Invoke-CLIAction\b')
9505+
Write-TestResult "50-EntryPoint: HeadlessMode routes to Invoke-CLIAction" ($mod50 -match 'HeadlessMode[\s\S]{0,100}Invoke-CLIAction')
9506+
Write-TestResult "50-EntryPoint: CLI handles Cleanup action" ($mod50 -match "Invoke-CLIAction[\s\S]*?'Cleanup'")
9507+
Write-TestResult "50-EntryPoint: CLI handles Debloat action" ($mod50 -match "Invoke-CLIAction[\s\S]*?'Debloat'")
9508+
Write-TestResult "50-EntryPoint: CLI handles HealthCheck action" ($mod50 -match "Invoke-CLIAction[\s\S]*?'HealthCheck'")
9509+
Write-TestResult "50-EntryPoint: CLI handles Batch action" ($mod50 -match "Invoke-CLIAction[\s\S]*?'Batch'")
9510+
Write-TestResult "50-EntryPoint: CLI handles QuickScan action" ($mod50 -match "Invoke-CLIAction[\s\S]*?'QuickScan'")
9511+
Write-TestResult "50-EntryPoint: CLI exits with code 0 on success" ($mod50 -match 'Invoke-CLIAction[\s\S]*?\[Environment\]::Exit\(0\)')
9512+
Write-TestResult "50-EntryPoint: CLI exits with code 1 on error" ($mod50 -match 'Invoke-CLIAction[\s\S]*?\[Environment\]::Exit\(1\)')
9513+
9514+
# Header param block
9515+
Write-TestResult "Header: has param block" ($headerContent -match 'param\s*\(')
9516+
Write-TestResult "Header: Action param with ValidateSet" ($headerContent -match "ValidateSet.*Cleanup.*Debloat.*HealthCheck.*Batch.*QuickScan")
9517+
Write-TestResult "Header: Tier param with ValidateSet" ($headerContent -match "ValidateSet.*Light.*Standard.*Aggressive")
9518+
Write-TestResult "Header: Silent switch param" ($headerContent -match '\[switch\]\$Silent')
9519+
9520+
# QuickScan runs health + disk + debloat
9521+
Write-TestResult "50-EntryPoint: QuickScan runs health check" ($mod50 -match "'QuickScan'[\s\S]{0,500}Show-SystemHealthCheck")
9522+
Write-TestResult "50-EntryPoint: QuickScan runs disk analysis" ($mod50 -match "'QuickScan'[\s\S]{0,500}Show-EnhancedCleanupAnalysis")
9523+
Write-TestResult "50-EntryPoint: QuickScan runs debloat analysis" ($mod50 -match "'QuickScan'[\s\S]{0,1000}Show-DebloatAnalysis")
9524+
9525+
# Bootstrap installer
9526+
$bootstrapPath = Join-Path $script:ModuleRoot "Install-RackStack.ps1"
9527+
if (Test-Path -LiteralPath $bootstrapPath) {
9528+
$bootstrapContent = Get-Content -LiteralPath $bootstrapPath -Raw
9529+
Write-TestResult "Install-RackStack: bootstrap installer exists" $true
9530+
Write-TestResult "Install-RackStack: checks for admin" ($bootstrapContent -match 'IsInRole.*Administrator')
9531+
Write-TestResult "Install-RackStack: queries GitHub releases API" ($bootstrapContent -match 'api\.github\.com/repos.*releases/latest')
9532+
Write-TestResult "Install-RackStack: downloads RackStack.exe" ($bootstrapContent -match 'Invoke-WebRequest.*RackStack\.exe|browser_download_url')
9533+
Write-TestResult "Install-RackStack: has Action param" ($bootstrapContent -match '\[string\]\$Action')
9534+
Write-TestResult "Install-RackStack: has Silent switch" ($bootstrapContent -match '\[switch\]\$Silent')
9535+
Write-TestResult "Install-RackStack: enforces TLS 1.2" ($bootstrapContent -match 'Tls12')
9536+
}
9537+
else {
9538+
Write-TestResult "Install-RackStack: bootstrap installer exists" $false "File not found"
9539+
}
9540+
9541+
} catch {
9542+
Write-TestResult "CLI headless mode tests" $false $_.Exception.Message
9543+
}
9544+
94949545
$elapsed = (Get-Date) - $script:StartTime
94959546

94969547
Write-Host ""

0 commit comments

Comments
 (0)