diff --git a/Changelog.md b/Changelog.md index 67c0d2b..3722e88 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,16 @@ # Changelog +## v1.121.7 + +Active Directory — the new-forest wizard now catches two problems up front instead of failing partway through promotion. + +- **Invalid NetBIOS name is caught before you start.** If the domain name's first label is longer than 15 characters, the wizard now tells you immediately (before asking for the DSRM password) instead of failing several minutes into the promotion. +- **Wrong starting state is caught up front.** Creating a new forest requires a standalone (workgroup) server; if the machine is already joined to a domain, the wizard now says so and points you to "Add Domain Controller" instead of failing during promotion. + +(A deep audit of the AD module confirmed its security-critical parts — DSRM password handling, credentials, and promotion parameters — are already correct; these were the only two gaps.) + +No module or CLI action changes (81 modules, 201 actions). + ## v1.121.6 Failover Clustering safety — fixes found by a deep audit of the clustering module. diff --git a/Header.ps1 b/Header.ps1 index 0a1228e..9c20939 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.121.6 + 1.121.7 .LAST UPDATED 07/01/2026 diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index 8c6d3e2..6c59789 100644 --- a/Modules/00-Initialization.ps1 +++ b/Modules/00-Initialization.ps1 @@ -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.6" +$script:ScriptVersion = "1.121.7" $script:ScriptStartTime = Get-Date # Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe. diff --git a/Modules/61-ActiveDirectory.ps1 b/Modules/61-ActiveDirectory.ps1 index b674a95..ca8cd37 100644 --- a/Modules/61-ActiveDirectory.ps1 +++ b/Modules/61-ActiveDirectory.ps1 @@ -769,6 +769,23 @@ function Install-NewForest { return } + # New-forest-only guard: Install-ADDSForest requires a STANDALONE (workgroup) server. A + # domain-joined member (DomainRole 1 = member workstation, 3 = member server) cannot create a + # new forest — catch it now, before the domain prompt and DSRM password entry, instead of + # failing minutes into promotion. Scoped HERE, not in the shared Test-ADDSPrerequisites (which + # Add-DC / RODC reuse, where a member server is the valid starting state). + try { + $newForestCompSys = Get-CimInstance -ClassName Win32_ComputerSystem -OperationTimeoutSec 8 -ErrorAction Stop + if ($newForestCompSys.DomainRole -in @(1, 3)) { + Write-OutputColor "" -color "Error" + Write-OutputColor " This server is joined to an existing domain ('$($newForestCompSys.Domain)')." -color "Error" + Write-OutputColor " A NEW forest must be created on a standalone (workgroup) server." -color "Error" + Write-OutputColor " To add a DC to the existing domain, use 'Add Domain Controller' instead." -color "Warning" + Write-PressEnter + return + } + } catch { } + # Step 3: Domain name Write-OutputColor "" -color "Info" $domainName = Read-Host " Enter domain name (e.g., corp.contoso.com)" @@ -789,6 +806,18 @@ function Install-NewForest { $netbiosName = Get-NetBIOSNameFromFQDN -DomainName $domainName + # Install-ADDSForest enforces a 15-character NetBIOS limit and fails several minutes into + # promotion (after the DSRM password has already been entered). Validate the derived NetBIOS + # name now so the operator can fix the domain name before committing anything. + if ($netbiosName.Length -gt 15) { + Write-OutputColor "" -color "Error" + Write-OutputColor " The NetBIOS name derived from '$domainName' is '$netbiosName' ($($netbiosName.Length) chars)." -color "Error" + Write-OutputColor " NetBIOS names must be 15 characters or fewer — use a domain whose first label" -color "Error" + Write-OutputColor " is 15 characters or fewer (e.g. 'corp.$domainName'), then retry." -color "Warning" + Write-PressEnter + return + } + # Step 4: Functional level $level = Select-FunctionalLevel if ($null -eq $level) { return } diff --git a/README.md b/README.md index f2595db..126a6dc 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5279 structural tests + 5284 structural tests Pester 312 tests SLSA Level 3

diff --git a/RackStack.ps1 b/RackStack.ps1 index f54ad2e..62c34de 100644 --- a/RackStack.ps1 +++ b/RackStack.ps1 @@ -13,7 +13,7 @@ Environment-specific settings are configured via defaults.json. .VERSION - 1.121.6 + 1.121.7 .NOTES - Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing) - Must be run as Administrator diff --git a/RackStack.psd1 b/RackStack.psd1 index 69dcf31..75f0f4a 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'RackStack.psm1' - ModuleVersion = '1.121.6' + ModuleVersion = '1.121.7' GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91' Author = 'TheAbider' CompanyName = 'TheAbider' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 935fa66..5d127e7 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -1,6 +1,6 @@ <# .SYNOPSIS - Automated Test Runner for RackStack v1.121.6 + Automated Test Runner for RackStack v1.121.7 .DESCRIPTION Comprehensive non-interactive test suite covering: @@ -9779,6 +9779,31 @@ catch { Write-TestResult "Failover Clustering Safety Tests" $false $_.Exception.Message } +# ============================================================================ +# SECTION 194: NEW-FOREST PRE-PROMOTION VALIDATION (v1.121.7) +# ============================================================================ +# Guards the two new-forest input-validation fixes: fail fast on an invalid NetBIOS name and on a +# domain-joined member server, BEFORE the DSRM password is collected. +Write-SectionHeader "SECTION 194: NEW-FOREST PRE-PROMOTION VALIDATION" + +try { + $adR = Get-Content "$modulesPath\61-ActiveDirectory.ps1" -Raw + + # NetBIOS length validation happens in Install-NewForest, BEFORE Read-DSRMPassword. + Write-TestResult "61-AD: new forest validates NetBIOS length" ($adR -match '\$netbiosName\.Length -gt 15') + Write-TestResult "61-AD: NetBIOS check precedes DSRM password prompt" ($adR -match '\$netbiosName\.Length -gt 15[\s\S]{0,1200}Read-DSRMPassword') + + # New-forest-only member-server guard (scoped locally, not in the shared prereq check). + Write-TestResult "61-AD: new forest rejects domain-joined member server" ($adR -match 'DomainRole -in @\(1, 3\)[\s\S]{0,300}standalone \(workgroup\)') + Write-TestResult "61-AD: member-server guard is new-forest-scoped (shared prereq unchanged)" ($adR -match 'function Test-ADDSPrerequisites[\s\S]{0,4000}DomainRole[\s\S]{0,60}-ge 4') + + # Regression guard: the shared prereq still uses -ge 4 (so Add-DC / RODC keep working on members). + Write-TestResult "61-AD: shared prereq still uses -ge 4 (not -ge 3)" (-not ($adR -match 'function Test-ADDSPrerequisites[\s\S]{0,4000}DomainRole[\s\S]{0,40}-ge 3')) +} +catch { + Write-TestResult "New-Forest Validation Tests" $false $_.Exception.Message +} + # ============================================================================ # SECTION 174: DOCUMENTATION FRESHNESS (counts must match the codebase) # ============================================================================