Skip to content

Commit 271ce39

Browse files
committed
v1.122.0: namespaced config file naming with legacy fallback
The base config is now rackstack.config.json and company overrides are <company>.rackstack.config.json, so the files stay unambiguous when the EXE or monolithic script runs from a shared folder. Fully backward compatible: legacy defaults.json and <company>.defaults.json are still read whenever the new-named file is absent (per file), and the tool keeps saving to whichever file it loaded. - Get-RackStackConfigPath resolves the base config once at init (prefer new name, fall back to legacy, create new name when neither exists); the first-run wizard and defaults editor write through the resolved path - Get-CompanyDefaultsFiles discovers company files under both naming schemes, deduped per company key with the new name winning; also fixes a latent bug where the returned array nested inside callers' @() wrappers and collapsed multiple companies into one entry - Save picker, menus, and prompts show the actually-loaded filename - Exit cleanup removes config files under both schemes plus company files and crash-leftover .tmp siblings; the company-file wildcard sweeps are gated to directories identifiable as the tool's own home so shared folders are never glob-swept - Example template renamed to rackstack.config.example.json and the release asset list updated to match - .gitignore covers both schemes; the tracked example stays tracked - Docs, wiki-facing guides, issue templates, and the config guide updated; company config files now documented with the fallback rules - New test section covers resolution order, dual-name company discovery, cleanup gating, .gitignore, and release-asset pins
1 parent 36c733d commit 271ce39

55 files changed

Lines changed: 532 additions & 300 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ body:
6666
- type: textarea
6767
id: defaults
6868
attributes:
69-
label: Relevant defaults.json snippet (REDACT SECRETS)
70-
description: If the bug involves config (network, agents, file-server, etc.), paste the matching defaults.json fragment. **Remove any credentials, tokens, or client secrets first.**
69+
label: Relevant rackstack.config.json snippet (REDACT SECRETS)
70+
description: If the bug involves config (network, agents, file-server, etc.), paste the matching rackstack.config.json (or legacy defaults.json) fragment. **Remove any credentials, tokens, or client secrets first.**
7171
render: json
7272

7373
- type: checkboxes

.github/ISSUE_TEMPLATE/feature_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ body:
4242
- Hyper-V (VM deployment, replica, checkpoints)
4343
- Clustering (failover, quorum)
4444
- Active Directory / Identity
45-
- Configuration (defaults.json, batch mode)
45+
- Configuration (rackstack.config.json, batch mode)
4646
- CLI / Headless surface
4747
- Health / Monitoring / Reports
4848
- Security hardening

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ jobs:
184184
$assets = @(
185185
@{ Name = 'RackStack.exe'; Path = 'builds\RackStack.exe' }
186186
@{ Name = "RackStack v$ver.ps1"; Path = "builds\RackStack v$ver.ps1" }
187-
@{ Name = 'defaults.example.json'; Path = 'defaults.example.json' }
187+
@{ Name = 'rackstack.config.example.json'; Path = 'rackstack.config.example.json' }
188188
)
189189
$lines = foreach ($a in $assets) {
190190
$h = (Get-FileHash -LiteralPath $a.Path -Algorithm SHA256).Hash.ToLower()
@@ -371,7 +371,7 @@ jobs:
371371
$assetArgs = @(
372372
"builds\RackStack.exe"
373373
"builds\RackStack v$ver.ps1"
374-
"defaults.example.json"
374+
"rackstack.config.example.json"
375375
'release-hashes.txt'
376376
)
377377
$sbomPath = "builds\RackStack-$ver-sbom.cyclonedx.json"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Credentials (org-specific URLs, keys, passwords)
2+
# Base + company config under both naming schemes; only the example is tracked
3+
rackstack.config.json
4+
*.rackstack.config.json
25
defaults.json
36
*.defaults.json
7+
!rackstack.config.example.json
48
!defaults.example.json
59

610
# Build artifacts (monolithic, exe, archives)

CONTRIBUTING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Thanks for your interest in contributing! Here's how to get started.
55
## Getting Started
66

77
1. Fork the repo and clone it locally
8-
2. Copy `defaults.example.json` to `defaults.json` and fill in your environment details
8+
2. Copy `rackstack.config.example.json` to `rackstack.config.json` and fill in your environment details (a legacy `defaults.json` is still read when present)
99
3. Make your changes in the `Modules/` directory
1010
4. Run the test suite before submitting
1111

@@ -30,7 +30,7 @@ powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1
3030

3131
## Pull Request Checklist
3232

33-
- [ ] All 5,048 tests pass (`Run-Tests.ps1` exits with code 0)
33+
- [ ] The full structural test suite passes (`Run-Tests.ps1` exits with code 0)
3434
- [ ] PSScriptAnalyzer reports 0 errors (`pssa-check.ps1`)
3535
- [ ] Monolithic synced (`sync-to-monolithic.ps1` shows 0 parse errors)
3636
- [ ] New functions follow PowerShell verb-noun naming (`Get-`, `Set-`, `Test-`, `Show-`)
@@ -64,7 +64,7 @@ powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1
6464

6565
## Configuration Safety
6666

67-
Never hardcode organization names, domains, contact info, or credentials in module code. Use the `$script:` variables that are loaded from `defaults.json`:
67+
Never hardcode organization names, domains, contact info, or credentials in module code. Use the `$script:` variables that are loaded from `rackstack.config.json`:
6868

6969
- `$script:Domain` for domain operations
7070
- `$script:SupportContact` for error messages

Changelog.md

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

3+
## v1.122.0
4+
5+
Namespaced config-file naming. The base config is now `rackstack.config.json` and company overrides are `<company>.rackstack.config.json` -- names that stay unambiguous when the EXE runs from a shared or busy folder. Nothing breaks for existing setups: the legacy `defaults.json` and `<company>.defaults.json` names are still read whenever the new-named file is absent, and the tool keeps saving to whichever file it loaded.
6+
7+
- **New config names, per-file preference.** `rackstack.config.json` is used when it exists; otherwise a legacy `defaults.json` is read. The same rule applies per company file, and when both names exist for the same company the new name wins. Company selection via `_companyDefaults` is unchanged, and multiple company files are still supported.
8+
- **New installs get the new names.** The first-run wizard and the in-tool defaults editor create `rackstack.config.json`; existing legacy files keep working in place with no migration step.
9+
- **The example template is now `rackstack.config.example.json`** (renamed from `defaults.example.json`) and ships in releases under that name.
10+
- **Menus and prompts show the actual loaded filename**, so a machine still running on `defaults.json` sees that name in the save picker and settings screens.
11+
- **Uninstall/self-destruct cleans up config files under both naming schemes**, including company override files, which it previously left behind.
12+
- **Docs, issue templates, and the config guide updated** to the new naming with the fallback behavior documented.
13+
14+
No module or CLI action changes (81 modules, 201 actions).
15+
316
## v1.121.15
417

518
Final audit sweep — remaining fixes across the firewall/RDP, iSCSI, network-diagnostics, and session modules.

Header.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.121.15
33+
1.122.0
3434
.LAST UPDATED
35-
07/01/2026
35+
07/16/2026
3636
3737
.CHANGELOG v1.21.1
3838
ROBUSTNESS, UX, CACHE CONSISTENCY:

Modules/00-Initialization.ps1

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
#region ===== SCRIPT INITIALIZATION =====
2-
# Tool identity (override via defaults.json)
2+
# Tool identity (override via rackstack.config.json)
33
$script:ToolName = "RackStack" # Short name (used in filenames, scheduled tasks)
44
$script:ToolFullName = "RackStack" # Display name (used in UI banners, reports)
5-
$script:SupportContact = "" # Support contact shown in agent installer messages (set via defaults.json)
5+
$script:SupportContact = "" # Support contact shown in agent installer messages (set via rackstack.config.json)
66
$script:ConfigDirName = "rackstackconfig" # Derived: config directory under USERPROFILE
77

8-
# Company/Environment Variables (generic defaults - override via defaults.json)
8+
# Company/Environment Variables (generic defaults - override via rackstack.config.json)
99
$domain = "" # Default primary domain (empty = not configured)
1010
$localadminaccountname = 'localadmin' # Local administrator account name
1111
$FullName = "Local Administrator" # Full name for the new local admin account
@@ -15,7 +15,7 @@ $BackupName = "Backup" # Backup NIC name made by SET
1515
$script:logFilePath = $null # Path for log file (set to enable logging)
1616
$script:emailAddress = $null # Email address for log notifications
1717

18-
# DNS Presets for quick configuration (custom presets merged from defaults.json)
18+
# DNS Presets for quick configuration (custom presets merged from rackstack.config.json)
1919
$script:DNSPresets = [ordered]@{
2020
"Google DNS" = @("8.8.8.8", "8.8.4.4")
2121
"Cloudflare" = @("1.1.1.1", "1.0.0.1")
@@ -34,7 +34,7 @@ $script:DefaultDownloadTimeoutSeconds = 1800 # 30 minutes for standard downlo
3434
$script:MaxDownloadRetries = 3 # Max retry attempts for large file downloads (>500MB)
3535

3636
# Centralized timeout configuration (seconds)
37-
# These can be overridden via defaults.json "Timeouts" section
37+
# These can be overridden via rackstack.config.json "Timeouts" section
3838
$script:Timeouts = @{
3939
CIMQuery = 10 # Standard CIM/WMI queries
4040
CIMQuerySlow = 15 # CIM queries on older/slower systems
@@ -53,7 +53,7 @@ $script:Timeouts = @{
5353
HealthCheckCache = 30 # Health check result cache TTL
5454
}
5555

56-
# Configurable MSP agent installer (override via defaults.json AgentInstaller)
56+
# Configurable MSP agent installer (override via rackstack.config.json AgentInstaller)
5757
$script:AgentInstaller = @{
5858
ToolName = "MSP"
5959
FolderName = "Agents"
@@ -73,7 +73,7 @@ $script:AgentInstaller = @{
7373
# service registers (which left agents installed-but-unenrolled). Bounded by the install timeout.
7474
$script:AgentEarlyDetectGraceSeconds = 90
7575

76-
# Additional agent installers (v1.8.0, override via defaults.json AdditionalAgents array)
76+
# Additional agent installers (v1.8.0, override via rackstack.config.json AdditionalAgents array)
7777
$script:AdditionalAgents = @()
7878

7979
# Agent installer cache
@@ -90,10 +90,10 @@ $script:PowerPlanGUID = @{
9090
# Default connectivity test target (used by SystemCheck, SET internet detection)
9191
$script:DefaultConnectivityTarget = "8.8.8.8"
9292

93-
# Default temp directory for transcripts, reports, and exports (override via defaults.json TempPath)
93+
# Default temp directory for transcripts, reports, and exports (override via rackstack.config.json TempPath)
9494
$script:TempPath = "$env:SystemDrive\Temp"
9595

96-
# SAN target IP mappings - last octet suffixes paired with labels (override via defaults.json SANTargetMappings)
96+
# SAN target IP mappings - last octet suffixes paired with labels (override via rackstack.config.json SANTargetMappings)
9797
$script:SANTargetMappings = @(
9898
@{ Suffix = 10; Label = "A0" }
9999
@{ Suffix = 11; Label = "B1" }
@@ -105,13 +105,13 @@ $script:SANTargetMappings = @(
105105
@{ Suffix = 17; Label = "A3" }
106106
)
107107

108-
# Custom SAN target pairings - defines A/B pairs and host-to-pair assignments (override via defaults.json SANTargetPairings)
108+
# Custom SAN target pairings - defines A/B pairs and host-to-pair assignments (override via rackstack.config.json SANTargetPairings)
109109
# When set, this overrides the default Initialize-SANTargetPairs logic and Get-SANTargetsForHost retry order.
110110
# A side = even suffixes, B side = odd suffixes by convention.
111111
# CycleSize controls the modulo: host 5 maps the same as host 1, host 6 as host 2, etc.
112112
$script:SANTargetPairings = $null
113113

114-
# Defender exclusion paths for Hyper-V hosts (override via defaults.json)
114+
# Defender exclusion paths for Hyper-V hosts (override via rackstack.config.json)
115115
$script:DefenderExclusionPaths = @(
116116
"C:\ProgramData\Microsoft\Windows\Hyper-V"
117117
"C:\ProgramData\Microsoft\Windows\Hyper-V\Snapshots"
@@ -122,7 +122,7 @@ $script:DefenderCommonVMPaths = @() # Populated dynamically by Update-DefenderV
122122

123123
# Operator-configurable list of services to surface in service-audit views. Set to $null
124124
# at init so callers can detect "not configured" and fall back to the built-in list (vmms,
125-
# vmcompute, ClusSvc, MSiSCSI, etc.). Populated by Import-Defaults when defaults.json
125+
# vmcompute, ClusSvc, MSiSCSI, etc.). Populated by Import-Defaults when rackstack.config.json
126126
# contains a `MonitoredServices` array. Prior to v1.98.8 callers read `$script:Defaults.
127127
# MonitoredServices` but $script:Defaults was never assigned, so the documented field
128128
# was dead code.
@@ -132,7 +132,7 @@ $script:MonitoredServices = $null
132132
$script:WindowsLicensingAppId = "55c92734-d682-4d71-983e-d6ec3f16059f"
133133

134134
# FileServer - Cloudflare Access-protected file server
135-
# Override via defaults.json - empty BaseURL = cloud features disabled
135+
# Override via rackstack.config.json - empty BaseURL = cloud features disabled
136136
$script:FileServer = @{
137137
StorageType = "nginx" # nginx (default), azure, static
138138
BaseURL = ""
@@ -146,10 +146,10 @@ $script:FileServer = @{
146146
AgentFolder = "Agents"
147147
}
148148

149-
# Azure Arc server onboarding (override via defaults.json "AzureArc" section)
149+
# Azure Arc server onboarding (override via rackstack.config.json "AzureArc" section)
150150
# Empty TenantId/SubscriptionId/ResourceGroup = Arc onboarding not configured.
151-
# ServicePrincipalSecret should be left empty in defaults.json committed to
152-
# source control supply it at runtime, via an environment variable, or via
151+
# ServicePrincipalSecret should be left empty in any rackstack.config.json kept
152+
# under source control - supply it at runtime, via an environment variable, or via
153153
# a secrets manager. See 65-AzureArc.ps1 Resolve-AzureArcSecret.
154154
$script:AzureArc = @{
155155
TenantId = "" # Azure AD / Entra tenant ID (GUID)
@@ -163,15 +163,15 @@ $script:AzureArc = @{
163163
Tags = "" # Optional Arc resource tags, "key1=val1,key2=val2"
164164
}
165165

166-
# Microsoft Defender for Endpoint onboarding (override via defaults.json "DefenderEndpoint")
166+
# Microsoft Defender for Endpoint onboarding (override via rackstack.config.json "DefenderEndpoint")
167167
# Paths point to the per-tenant onboarding/offboarding .cmd scripts downloaded
168168
# from the Defender portal (security.microsoft.com). Empty = MDE not configured.
169169
$script:DefenderEndpoint = @{
170170
OnboardingScriptPath = "" # Path to WindowsDefenderATPLocalOnboardingScript.cmd
171171
OffboardingScriptPath = "" # Path to the offboarding .cmd (expires ~30 days after generation)
172172
}
173173

174-
# WSUS server setup (override via defaults.json "WSUS")
174+
# WSUS server setup (override via rackstack.config.json "WSUS")
175175
# Empty ContentPath = WSUS setup not configured. ContentPath must be an
176176
# absolute local path; it holds downloaded update binaries (can grow large).
177177
$script:WSUS = @{
@@ -181,7 +181,7 @@ $script:WSUS = @{
181181
SyncHour = 3 # Hour (0-23) for the daily automatic catalog sync
182182
}
183183

184-
# AD CS Certification Authority bootstrap (override via defaults.json "ADCS")
184+
# AD CS Certification Authority bootstrap (override via rackstack.config.json "ADCS")
185185
# Empty CACommonName = AD CS setup not configured. Configuring a CA is hard
186186
# to reverse — the CA install requires a typed confirmation regardless.
187187
$script:ADCS = @{
@@ -192,7 +192,7 @@ $script:ADCS = @{
192192
ValidityYears = 10 # Root CA certificate validity (1-25 years)
193193
}
194194

195-
# Storage Migration Service (override via defaults.json "StorageMigration")
195+
# Storage Migration Service (override via rackstack.config.json "StorageMigration")
196196
# InstallProxy controls whether the StorageMigrationSetup CLI action also
197197
# installs the SMS-Proxy feature (run the proxy on the destination server).
198198
$script:StorageMigration = @{
@@ -211,11 +211,11 @@ $script:VHDCachePath = "D:\Virtual Machines\_BaseImages" # Cached sysprepped
211211
$script:ClusterVHDCachePath = "C:\ClusterStorage\Volume1\_BaseImages" # Cached VHDs on clusters
212212
$script:StorageInitialized = $false # Whether host storage has been initialized
213213

214-
# Dashboard color thresholds for CPU/memory/disk usage (override via defaults.json DashboardWarningPercent / DashboardCriticalPercent)
214+
# Dashboard color thresholds for CPU/memory/disk usage (override via rackstack.config.json DashboardWarningPercent / DashboardCriticalPercent)
215215
$script:DashboardWarningPercent = 70
216216
$script:DashboardCriticalPercent = 90
217217

218-
# Storage backend type: iSCSI, FC, S2D, SMB3, NVMeoF, Local (override via defaults.json StorageBackendType)
218+
# Storage backend type: iSCSI, FC, S2D, SMB3, NVMeoF, Local (override via rackstack.config.json StorageBackendType)
219219
$script:StorageBackendType = "iSCSI"
220220

221221
# Store script path at startup (MUST be before functions for Exit-Script to work)
@@ -233,7 +233,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
233233
if (-not $script:ModuleRoot -and $script:ScriptPath) {
234234
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
235235
}
236-
$script:ScriptVersion = "1.121.15"
236+
$script:ScriptVersion = "1.122.0"
237237
$script:ScriptStartTime = Get-Date
238238

239239
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.
@@ -294,7 +294,7 @@ $script:RebootNeeded = $false
294294
$script:DisabledAdminReboot = $false
295295
$script:ReturnToMainMenu = $false # Flag to signal "go straight to main menu"
296296

297-
# Auto-update: if true, automatically download and install updates on startup (override via defaults.json)
297+
# Auto-update: if true, automatically download and install updates on startup (override via rackstack.config.json)
298298
$script:AutoUpdate = $false
299299

300300
# Auto-update state (populated by Test-StartupUpdateCheck on launch)
@@ -323,9 +323,23 @@ $script:AppConfigDir = "$env:USERPROFILE\.$($script:ConfigDirName)"
323323
$script:FavoritesPath = "$script:AppConfigDir\favorites.json"
324324
$script:HistoryPath = "$script:AppConfigDir\history.json"
325325
$script:SessionStatePath = "$script:AppConfigDir\session.json"
326-
$script:DefaultsPath = "$script:ModuleRoot\defaults.json"
327-
$script:CompanyDefaultsName = $null # e.g., "acme" (loaded from acme.defaults.json)
328-
$script:CompanyDefaultsPath = $null # e.g., "C:\...\acme.defaults.json"
326+
# Base config resolution: the namespaced rackstack.config.json is preferred; the
327+
# legacy defaults.json is still read when only it exists. New config files are
328+
# always created under the new name, and saves target the resolved (loaded) file.
329+
function Get-RackStackConfigPath {
330+
param(
331+
[Parameter(Mandatory=$true)]
332+
[string]$Root
333+
)
334+
$preferredPath = Join-Path $Root "rackstack.config.json"
335+
$legacyPath = Join-Path $Root "defaults.json"
336+
if (Test-Path -LiteralPath $preferredPath) { return $preferredPath }
337+
if (Test-Path -LiteralPath $legacyPath) { return $legacyPath }
338+
return $preferredPath
339+
}
340+
$script:DefaultsPath = Get-RackStackConfigPath -Root $script:ModuleRoot
341+
$script:CompanyDefaultsName = $null # e.g., "acme" (loaded from acme.rackstack.config.json, or legacy acme.defaults.json)
342+
$script:CompanyDefaultsPath = $null # e.g., "C:\...\acme.rackstack.config.json"
329343
$script:Favorites = @()
330344
$script:CommandHistory = @()
331345
$script:MaxHistoryItems = 100
@@ -338,7 +352,7 @@ $script:BuiltInVMTemplates = $null
338352
$script:CustomRoleTemplates = @{}
339353
$script:TimeZoneRegion = ""
340354

341-
# VM naming convention (override via defaults.json VMNaming)
355+
# VM naming convention (override via rackstack.config.json VMNaming)
342356
$script:VMNaming = @{
343357
SiteId = ""
344358
Pattern = "{Site}-{Prefix}{Seq}"

Modules/02-Logging.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ function Write-CenteredOutput {
164164
$script:ErrorCodes = @{
165165
# Core / System (1000-1999)
166166
"RS-1001" = @{ Message = "Script requires administrative privileges"; Category = "Core" }
167-
"RS-1002" = @{ Message = "defaults.json parse error"; Category = "Core" }
167+
"RS-1002" = @{ Message = "config file parse error"; Category = "Core" }
168168
"RS-1003" = @{ Message = "Module load failure"; Category = "Core" }
169169
"RS-1004" = @{ Message = "PowerShell version unsupported"; Category = "Core" }
170170
"RS-1005" = @{ Message = "Transcript start failed"; Category = "Core" }

Modules/05-SystemCheck.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ function Test-AllConnectivity {
434434
}
435435

436436
# DNS resolution test. The target is configurable via $script:NetworkTestDnsTarget
437-
# (defaults.json key NetworkTestDnsTarget) so air-gapped / restricted environments that
437+
# (rackstack.config.json key NetworkTestDnsTarget) so air-gapped / restricted environments that
438438
# block public hostnames but allow internal DNS aren't forced into a false-negative.
439439
# Default falls back to a public name for the common case.
440440
$dnsTarget = if ($script:NetworkTestDnsTarget) { $script:NetworkTestDnsTarget } else { "google.com" }

0 commit comments

Comments
 (0)