Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ body:
- type: textarea
id: defaults
attributes:
label: Relevant defaults.json snippet (REDACT SECRETS)
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.**
label: Relevant rackstack.config.json snippet (REDACT SECRETS)
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.**
render: json

- type: checkboxes
Expand Down
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ body:
- Hyper-V (VM deployment, replica, checkpoints)
- Clustering (failover, quorum)
- Active Directory / Identity
- Configuration (defaults.json, batch mode)
- Configuration (rackstack.config.json, batch mode)
- CLI / Headless surface
- Health / Monitoring / Reports
- Security hardening
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ jobs:
$assets = @(
@{ Name = 'RackStack.exe'; Path = 'builds\RackStack.exe' }
@{ Name = "RackStack v$ver.ps1"; Path = "builds\RackStack v$ver.ps1" }
@{ Name = 'defaults.example.json'; Path = 'defaults.example.json' }
@{ Name = 'rackstack.config.example.json'; Path = 'rackstack.config.example.json' }
)
$lines = foreach ($a in $assets) {
$h = (Get-FileHash -LiteralPath $a.Path -Algorithm SHA256).Hash.ToLower()
Expand Down Expand Up @@ -371,7 +371,7 @@ jobs:
$assetArgs = @(
"builds\RackStack.exe"
"builds\RackStack v$ver.ps1"
"defaults.example.json"
"rackstack.config.example.json"
'release-hashes.txt'
)
$sbomPath = "builds\RackStack-$ver-sbom.cyclonedx.json"
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Credentials (org-specific URLs, keys, passwords)
# Base + company config under both naming schemes; only the example is tracked
rackstack.config.json
*.rackstack.config.json
defaults.json
*.defaults.json
!rackstack.config.example.json
!defaults.example.json

# Build artifacts (monolithic, exe, archives)
Expand Down
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Thanks for your interest in contributing! Here's how to get started.
## Getting Started

1. Fork the repo and clone it locally
2. Copy `defaults.example.json` to `defaults.json` and fill in your environment details
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)
3. Make your changes in the `Modules/` directory
4. Run the test suite before submitting

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

## Pull Request Checklist

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

## Configuration Safety

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

- `$script:Domain` for domain operations
- `$script:SupportContact` for error messages
Expand Down
13 changes: 13 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## v1.122.0

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.

- **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.
- **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.
- **The example template is now `rackstack.config.example.json`** (renamed from `defaults.example.json`) and ships in releases under that name.
- **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.
- **Uninstall/self-destruct cleans up config files under both naming schemes**, including company override files, which it previously left behind.
- **Docs, issue templates, and the config guide updated** to the new naming with the fallback behavior documented.

No module or CLI action changes (81 modules, 201 actions).

## v1.121.15

Final audit sweep — remaining fixes across the firewall/RDP, iSCSI, network-diagnostics, and session modules.
Expand Down
4 changes: 2 additions & 2 deletions Header.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
7h3 4b1d3r

.VERSION
1.121.15
1.122.0
.LAST UPDATED
07/01/2026
07/16/2026

.CHANGELOG v1.21.1
ROBUSTNESS, UX, CACHE CONSISTENCY:
Expand Down
70 changes: 42 additions & 28 deletions Modules/00-Initialization.ps1
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#region ===== SCRIPT INITIALIZATION =====
# Tool identity (override via defaults.json)
# Tool identity (override via rackstack.config.json)
$script:ToolName = "RackStack" # Short name (used in filenames, scheduled tasks)
$script:ToolFullName = "RackStack" # Display name (used in UI banners, reports)
$script:SupportContact = "" # Support contact shown in agent installer messages (set via defaults.json)
$script:SupportContact = "" # Support contact shown in agent installer messages (set via rackstack.config.json)
$script:ConfigDirName = "rackstackconfig" # Derived: config directory under USERPROFILE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

# Auto-update state (populated by Test-StartupUpdateCheck on launch)
Expand Down Expand Up @@ -323,9 +323,23 @@ $script:AppConfigDir = "$env:USERPROFILE\.$($script:ConfigDirName)"
$script:FavoritesPath = "$script:AppConfigDir\favorites.json"
$script:HistoryPath = "$script:AppConfigDir\history.json"
$script:SessionStatePath = "$script:AppConfigDir\session.json"
$script:DefaultsPath = "$script:ModuleRoot\defaults.json"
$script:CompanyDefaultsName = $null # e.g., "acme" (loaded from acme.defaults.json)
$script:CompanyDefaultsPath = $null # e.g., "C:\...\acme.defaults.json"
# Base config resolution: the namespaced rackstack.config.json is preferred; the
# legacy defaults.json is still read when only it exists. New config files are
# always created under the new name, and saves target the resolved (loaded) file.
function Get-RackStackConfigPath {
param(
[Parameter(Mandatory=$true)]
[string]$Root
)
$preferredPath = Join-Path $Root "rackstack.config.json"
$legacyPath = Join-Path $Root "defaults.json"
if (Test-Path -LiteralPath $preferredPath) { return $preferredPath }
if (Test-Path -LiteralPath $legacyPath) { return $legacyPath }
return $preferredPath
}
$script:DefaultsPath = Get-RackStackConfigPath -Root $script:ModuleRoot
$script:CompanyDefaultsName = $null # e.g., "acme" (loaded from acme.rackstack.config.json, or legacy acme.defaults.json)
$script:CompanyDefaultsPath = $null # e.g., "C:\...\acme.rackstack.config.json"
$script:Favorites = @()
$script:CommandHistory = @()
$script:MaxHistoryItems = 100
Expand All @@ -338,7 +352,7 @@ $script:BuiltInVMTemplates = $null
$script:CustomRoleTemplates = @{}
$script:TimeZoneRegion = ""

# VM naming convention (override via defaults.json VMNaming)
# VM naming convention (override via rackstack.config.json VMNaming)
$script:VMNaming = @{
SiteId = ""
Pattern = "{Site}-{Prefix}{Seq}"
Expand Down
2 changes: 1 addition & 1 deletion Modules/02-Logging.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ function Write-CenteredOutput {
$script:ErrorCodes = @{
# Core / System (1000-1999)
"RS-1001" = @{ Message = "Script requires administrative privileges"; Category = "Core" }
"RS-1002" = @{ Message = "defaults.json parse error"; Category = "Core" }
"RS-1002" = @{ Message = "config file parse error"; Category = "Core" }
"RS-1003" = @{ Message = "Module load failure"; Category = "Core" }
"RS-1004" = @{ Message = "PowerShell version unsupported"; Category = "Core" }
"RS-1005" = @{ Message = "Transcript start failed"; Category = "Core" }
Expand Down
2 changes: 1 addition & 1 deletion Modules/05-SystemCheck.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ function Test-AllConnectivity {
}

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