diff --git a/Changelog.md b/Changelog.md index 0e8d701..55da33c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,16 @@ # Changelog +## v1.118.0 + +DFS Namespaces & Replication — a new module (**79-DFS**) surfaced under **Roles & Features → [14] DFS Namespaces & Replication**, plus a read-only CLI action. + +- **`DFSAudit`** (read-only) — reports whether the DFS Namespace (`FS-DFS-Namespace`) and Replication (`FS-DFS-Replication`) roles are installed, the namespaces this server knows, and the DFS-R replication groups and replicated-folder count. JSON-aware; makes no changes. Backlog measurement is left to the DFS Management console (it needs a specific member pair + folder and can be slow). +- **DFS role install** (reversible) — installs the missing DFS server role(s) plus management tools via the timeout-guarded feature installer, capturing which features were already present so the session undo removes only the ones it added. Dry-Run aware. Server-SKU gated. + +The Roles & Features menu shows a live DFS status indicator (Installed / Partial / Not Installed / Tools N/A). Namespace and replication-group creation remains in the DFS Management console (`dfsmgmt.msc`); this module covers the role lifecycle and an at-a-glance audit. + +New module 79-DFS. Modules: 79 → 80. CLI actions: 199 → 200. + ## v1.117.0 Service certificate binding audit — a new module (**78-CertificateAudit**) surfaced under **Security & Access → [12] Certificate Binding Audit**, plus a read-only CLI action. diff --git a/Header.ps1 b/Header.ps1 index b67475e..1abfe30 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.117.0 + 1.118.0 .LAST UPDATED 05/23/2026 @@ -1391,7 +1391,7 @@ param( # CLI headless mode: run a specific action without interactive menus # Usage: RackStack.exe -Action Cleanup [-Tier Standard] [-Silent] [-OutputFormat JSON] - [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin', 'ClusterValidationReport', 'SmbEnforce', 'SmbSecurityCheck', 'PrintServerAudit', 'NtpHardeningAudit', 'CertBindingAudit')] + [ValidateSet('Cleanup', 'Debloat', 'HealthCheck', 'Batch', 'QuickScan', 'Inventory', 'DriftCheck', 'Snapshot', 'Compliance', 'Harden', 'Remediate', 'Aggregate', 'Compare', 'Export', 'Trend', 'CertCheck', 'ReportHTML', 'ListeningPorts', 'SoftwareList', 'Uptime', 'ServiceAudit', 'EventAudit', 'NetInfo', 'ScheduledExport', 'ValidateConfig', 'Watch', 'Query', 'Diff', 'Baseline', 'Alert', 'FleetScan', 'PatchStatus', 'UserAudit', 'FirewallAudit', 'TaskAudit', 'DiskAudit', 'TLSAudit', 'SMBAudit', 'DriverAudit', 'TimeAudit', 'BootAudit', 'GPOAudit', 'MemoryAudit', 'ProcessAudit', 'BackupAudit', 'ShareAudit', 'DNSAudit', 'PowerAudit', 'RegistryAudit', 'ProfileAudit', 'HyperVAudit', 'NetworkAudit', 'StorageAudit', 'FeatureAudit', 'AutoStartAudit', 'BIOSAudit', 'ClusterAudit', 'AuditPolicyAudit', 'EnvAudit', 'CrashAudit', 'LocalGroupAudit', 'WMIAudit', 'TempAudit', 'UpdatePolicyAudit', 'IISAudit', 'SSHAudit', 'BitLockerAudit', 'PrintAudit', 'CredGuardAudit', 'PortAudit', 'AntivirusAudit', 'DotNetAudit', 'RDPAudit', 'VPNAudit', 'HostsFileAudit', 'NetStatAudit', 'LicenseAudit', 'USBDeviceAudit', 'AppLockerAudit', 'EventSubAudit', 'HotfixAudit', 'SysInfoAudit', 'LogonAudit', 'ACLAudit', 'RecoveryAudit', 'ServiceAccountAudit', 'ProxyAudit', 'PendingRebootAudit', 'PageFileAudit', 'CPUAudit', 'DefenderExclusionAudit', 'KerberosAudit', 'DHCPAudit', 'NUMAAudit', 'SymlinkAudit', 'StartupScriptAudit', 'SecureChannelAudit', 'ComObjectAudit', 'FirewallLogAudit', 'ScheduledRebootAudit', 'PowerShellAudit', 'RouteTableAudit', 'TokenPrivilegeAudit', 'WindowsCapabilityAudit', 'ARPTableAudit', 'LocaleAudit', 'TaskHistoryAudit', 'NTFSAudit', 'Win11Cleanup', 'DarkMode', 'LightMode', 'iSCSIAudit', 'NICTeamAudit', 'SMBSessionAudit', 'WindowsUpdateAudit', 'ClusterQuorumAudit', 'S2DAudit', 'VirtualSwitchAudit', 'MPIOPathAudit', 'ServiceRecoveryAudit', 'VMOvercommitAudit', 'DedupAudit', 'ClusterNetworkAudit', 'ReplicaLagAudit', 'HandleLeakAudit', 'ShadowCopyAudit', 'QoSPolicyAudit', 'LiveMigrationAudit', 'DomainTrustAudit', 'DiskLatencyAudit', 'NICOffloadAudit', 'StorageTimeoutAudit', 'EventLogCapacityAudit', 'TcpSettingsAudit', 'WinRMAudit', 'ClusterHealthScore', 'VMInventoryExport', 'VMSnapshotAudit', 'StorageHealthScore', 'CSVSpaceAudit', 'SMBConnectionAudit', 'VolumeLabelAudit', 'NICErrorAudit', 'VMResourceWaste', 'HealthDashboard', 'SCCMClientAudit', 'SCOMAgentAudit', 'WACConnectivityAudit', 'AzureADAudit', 'ServerScore', 'FleetReport', 'PasswordPolicy', 'FirewallRuleAudit', 'GPResultAudit', 'DNSCacheAudit', 'TPMAudit', 'SecureBootAudit', 'TimeSkewAudit', 'NetworkProfileAudit', 'InsecureServiceAudit', 'SelfTest', 'CheckForUpdate', 'ExportLogs', 'UpdateSelf', 'Rollback', 'ScheduleUpdateCheck', 'Dashboard', 'History', 'Replay', 'AzureArcEnroll', 'DefenderEndpointOnboard', 'WSUSSetup', 'ADCSSetup', 'StorageMigrationSetup', 'GPOBackup', 'GPODrift', 'JEAList', 'NPSSetup', 'AlwaysOnVPNSetup', 'CISScan', 'SIEMSetup', 'SIEMStatus', 'WACSetup', 'WACStatus', 'VHDXEncryptionAudit', 'ADRecycleBin', 'ClusterValidationReport', 'SmbEnforce', 'SmbSecurityCheck', 'PrintServerAudit', 'NtpHardeningAudit', 'CertBindingAudit', 'DFSAudit')] [string]$Action, [ValidateSet('Light', 'Standard', 'Aggressive')] diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index 73db9bd..0c85092 100644 --- a/Modules/00-Initialization.ps1 +++ b/Modules/00-Initialization.ps1 @@ -225,7 +225,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.117.0" +$script:ScriptVersion = "1.118.0" $script:ScriptStartTime = Get-Date # Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe. diff --git a/Modules/34-Help.ps1 b/Modules/34-Help.ps1 index 96a64b3..9ffd4da 100644 --- a/Modules/34-Help.ps1 +++ b/Modules/34-Help.ps1 @@ -253,7 +253,7 @@ function Search-HelpTopics { @{ Title = "Performance"; Keywords = @("performance", "cpu", "memory", "disk", "io", "bandwidth", "dashboard", "process"); Description = "Live performance dashboard with CPU, memory, disk I/O, and network bandwidth monitoring" } @{ Title = "Licensing & NTP"; Keywords = @("license", "activation", "kms", "avma", "ntp", "time", "timezone", "clock"); Description = "Windows licensing status (KMS/AVMA/Retail), NTP configuration, time sync, and timezone setup" } @{ Title = "VM Management"; Keywords = @("checkpoint", "snapshot", "export", "import", "migration", "vhd", "iso"); Description = "VM checkpoints, export/import, migration readiness, VHD health, and ISO inventory" } - @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "199 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." } + @{ Title = "CLI Actions"; Keywords = @("cli", "action", "headless", "automation", "fleet", "json", "audit", "scan", "score", "dashboard", "monitor", "policy", "sla", "netmap", "validate"); Description = "200 CLI actions for headless automation. Run -ListActions to see all. JSON output via -OutputFormat JSON. Key: ServerScore, HealthDashboard, FleetReport, CISScan, NPSSetup, AlwaysOnVPNSetup, SIEMStatus." } @{ Title = "SelfTest Action"; Keywords = @("selftest", "self-test", "diagnose", "diagnostic", "verify", "healthcheck", "sanity"); Description = "Internal diagnostic. -Action SelfTest checks PS version, elevation, module count, version consistency, defaults.json validity, temp path writability, FileServer reachability, and agent installer config. Exit 1 on any failure. Use -OutputFormat JSON for structured output." } @{ Title = "Security Audits"; Keywords = @("security", "audit", "hardening", "compliance", "tls", "smb", "kerberos", "credguard", "applocker", "bitlockeraudit", "defenderexclusionaudit", "audit-policy", "secureboot", "tpm"); Description = "Security-focused CLI audits: TLSAudit, SMBAudit, KerberosAudit, CredGuardAudit, AppLockerAudit, BitLockerAudit, DefenderExclusionAudit, AuditPolicyAudit, SecureBootAudit, TPMAudit, UserAudit, LogonAudit, InsecureServiceAudit, RegistryAudit. All support -OutputFormat JSON." } @{ Title = "Network Audits"; Keywords = @("netaudit", "dns", "firewall-audit", "firewalllog", "arp", "route", "tcp", "netstat", "dhcp", "netprofile", "winrm", "qos", "nicoffload"); Description = "Network audits: DNSAudit, DNSCacheAudit, FirewallAudit, FirewallRuleAudit, FirewallLogAudit, ARPTableAudit, RouteTableAudit, TcpSettingsAudit, NetStatAudit, DHCPAudit, NetworkProfileAudit, WinRMAudit, QoSPolicyAudit, NICOffloadAudit, NICErrorAudit, HostsFileAudit, VPNAudit, ProxyAudit." } diff --git a/Modules/48-MenuDisplay.ps1 b/Modules/48-MenuDisplay.ps1 index 073de2e..d31c632 100644 --- a/Modules/48-MenuDisplay.ps1 +++ b/Modules/48-MenuDisplay.ps1 @@ -444,6 +444,15 @@ function Show-RolesFeaturesMenu { } -CacheSeconds 120 $wacColor = if ($wacStatusText -eq "Running") { "Success" } else { "Warning" } + $dfsStatusText = Get-CachedValue -Key "DFSState" -FetchScript { + $d = Get-DFSStatus + if (-not $d.Available) { "Tools N/A" } + elseif ($d.NamespaceRoleInstalled -and $d.ReplicationRoleInstalled) { "Installed" } + elseif ($d.NamespaceRoleInstalled -or $d.ReplicationRoleInstalled) { "Partial" } + else { "Not Installed" } + } -CacheSeconds 120 + $dfsColor = if ($dfsStatusText -eq "Installed") { "Success" } else { "Warning" } + Write-OutputColor "" -color "Info" Write-OutputColor " ╔════════════════════════════════════════════════════════════════════════╗" -color "Info" Write-OutputColor " ║$((" ROLES & FEATURES").PadRight(72))║" -color "Info" @@ -464,6 +473,7 @@ function Show-RolesFeaturesMenu { Write-MenuItem "[11] Remote Access / Always-On VPN ►" -Status $aovpnStatusText -StatusColor $aovpnColor Write-MenuItem "[12] SIEM Log Forwarder ►" -Status $siemStatusText -StatusColor $siemColor Write-MenuItem "[13] Windows Admin Center (WAC) ►" -Status $wacStatusText -StatusColor $wacColor + Write-MenuItem "[14] DFS Namespaces & Replication ►" -Status $dfsStatusText -StatusColor $dfsColor Write-OutputColor " └────────────────────────────────────────────────────────────────────────┘" -color "Info" Write-OutputColor "" -color "Info" Write-OutputColor " [B] ◄ Back to Server Config" -color "Info" diff --git a/Modules/49-MenuRunner.ps1 b/Modules/49-MenuRunner.ps1 index 96ba879..ae384c8 100644 --- a/Modules/49-MenuRunner.ps1 +++ b/Modules/49-MenuRunner.ps1 @@ -256,9 +256,10 @@ function Start-Show-RolesFeaturesMenu { "11" { Show-RemoteAccessManagement } "12" { Show-SIEMForwarderManagement } "13" { Show-WindowsAdminCenterManagement } + "14" { Show-DFSManagement } "back" { return } default { - Write-OutputColor " Invalid choice. Enter 1-13 or B." -color "Error" + Write-OutputColor " Invalid choice. Enter 1-14 or B." -color "Error" Start-Sleep -Milliseconds 500 } } diff --git a/Modules/50-EntryPoint.ps1 b/Modules/50-EntryPoint.ps1 index 9556cb3..d8351ac 100644 --- a/Modules/50-EntryPoint.ps1 +++ b/Modules/50-EntryPoint.ps1 @@ -417,6 +417,7 @@ function Assert-Elevation { @{ Action = 'PrintServerAudit'; Description = 'Read-only: report print-spooler posture, queue depth, orphaned ports + unused drivers (JSON-aware)' } @{ Action = 'NtpHardeningAudit'; Description = 'Read-only: report W32Time clock-tamper posture (phase-correction limits, auth mode) (JSON-aware)' } @{ Action = 'CertBindingAudit'; Description = 'Read-only: report RDP/WinRM listener certificate bindings + expiry (JSON-aware)' } + @{ Action = 'DFSAudit'; Description = 'Read-only: report DFS namespace/replication role state, namespaces + replication groups (JSON-aware)' } @{ Action = 'Batch'; Description = 'JSON-driven full configuration' } ) if ($script:CLIOutputFormat -eq 'JSON') { @@ -2089,6 +2090,11 @@ footer{text-align:center;color:#999;font-size:12px;padding:16px} $certAuditOk = Start-CertBindingAudit [Environment]::Exit([int](-not $certAuditOk)) } + 'DFSAudit' { + # Read-only DFS namespace/replication posture (JSON-aware). + $dfsAuditOk = Start-DFSAudit + [Environment]::Exit([int](-not $dfsAuditOk)) + } 'Batch' { if (-not $script:CLIConfig) { Write-OutputColor " ERROR: -Action Batch requires -Config " -color "Error" diff --git a/Modules/79-DFS.ps1 b/Modules/79-DFS.ps1 new file mode 100644 index 0000000..18462d9 --- /dev/null +++ b/Modules/79-DFS.ps1 @@ -0,0 +1,178 @@ +#region ===== DFS (NAMESPACES & REPLICATION) ===== +# Read-only audit of DFS Namespaces (DFS-N) and DFS Replication (DFS-R), plus a +# reversible one-step install of the DFS server roles. The audit reports the +# installed role state, the namespaces this server knows, and the DFS-R +# replication groups and replicated-folder count. It makes no changes. +# +# Backlog measurement (Get-DfsrBacklog) is intentionally NOT run during the audit: +# it requires naming a specific sending/receiving member plus a replicated folder +# and can be slow, so it is left for targeted use in the DFS Management console. + +# Report whether the DFS role features are installed and whether the DFS +# PowerShell tooling is present at all. +function Test-DFSRoleInstalled { + $r = @{ Namespace = $false; Replication = $false; ToolsAvailable = $false } + $r.ToolsAvailable = [bool](Get-Command Get-DfsnRoot -ErrorAction SilentlyContinue) + try { + if (Get-Command Get-WindowsFeature -ErrorAction SilentlyContinue) { + $ns = Get-WindowsFeature -Name 'FS-DFS-Namespace' -ErrorAction SilentlyContinue + $rep = Get-WindowsFeature -Name 'FS-DFS-Replication' -ErrorAction SilentlyContinue + if ($ns) { $r.Namespace = ($ns.InstallState -eq 'Installed') } + if ($rep) { $r.Replication = ($rep.InstallState -eq 'Installed') } + } + } catch {} + return $r +} + +# Read-only DFS posture: role state, namespaces, replication groups + folder count. +function Get-DFSStatus { + $result = [PSCustomObject]@{ + Available = $false + NamespaceRoleInstalled = $false + ReplicationRoleInstalled = $false + Namespaces = @() + ReplicationGroups = @() + ReplicatedFolderCount = 0 + } + $roles = Test-DFSRoleInstalled + if (-not $roles.ToolsAvailable) { return $result } + $result.Available = $true + $result.NamespaceRoleInstalled = $roles.Namespace + $result.ReplicationRoleInstalled = $roles.Replication + try { $result.Namespaces = @(Get-DfsnRoot -ErrorAction SilentlyContinue | ForEach-Object { "$($_.Path)" } | Where-Object { $_ }) } catch {} + try { + $rgs = @(Get-DfsReplicationGroup -ErrorAction SilentlyContinue) + $result.ReplicationGroups = @($rgs | ForEach-Object { "$($_.GroupName)" } | Where-Object { $_ }) + $result.ReplicatedFolderCount = @(Get-DfsReplicatedFolder -ErrorAction SilentlyContinue).Count + } catch {} + return $result +} + +# Reversible: install the DFS Namespace + Replication server roles (and their +# management tools). Captures which features were already present so the undo +# removes only the ones this action added. +function Install-DFSRoles { + $roles = Test-DFSRoleInstalled + if ($roles.Namespace -and $roles.Replication) { + Write-OutputColor " Both DFS roles (Namespace + Replication) are already installed." -color "Info" + return $true + } + if (-not (Test-WindowsServer)) { + Write-OutputColor " DFS is a Windows Server role — this OS is not a server SKU." -color "Error" + return $false + } + $needNs = -not $roles.Namespace + $needRep = -not $roles.Replication + + if ($script:DryRunMode -and -not $script:ApplyingDryRunQueue) { + $addNs = $needNs; $addRep = $needRep + Push-DryRunStep -Label "Install DFS roles (Namespace + Replication)" -Category "Roles" -OneWay $false ` + -Preflight { if (-not (Test-WindowsServer)) { "Not a Windows Server SKU" } else { $true } }.GetNewClosure() ` + -Apply { Install-DFSRoles | Out-Null }.GetNewClosure() ` + -Undo { + if ($addRep) { Uninstall-WindowsFeature -Name 'FS-DFS-Replication' -ErrorAction SilentlyContinue | Out-Null } + if ($addNs) { Uninstall-WindowsFeature -Name 'FS-DFS-Namespace' -ErrorAction SilentlyContinue | Out-Null } + }.GetNewClosure() + Write-OutputColor " Queued (Dry-Run): install DFS roles." -color "Warning" + Add-SessionChange -Category "DryRun" -Description "Queued DFS role install" + return $true + } + + $ok = $true + if ($needNs) { + Write-OutputColor " Installing DFS Namespaces (FS-DFS-Namespace)..." -color "Info" + $r1 = Install-WindowsFeatureWithTimeout -FeatureName 'FS-DFS-Namespace' -DisplayName 'DFS Namespaces' -IncludeManagementTools + if (-not $r1.Success) { $ok = $false; Write-OutputColor " DFS Namespaces install failed." -color "Error"; if ($r1.Error) { Write-OutputColor " $($r1.Error.Trim())" -color "Error" } } + } + if ($needRep -and $ok) { + Write-OutputColor " Installing DFS Replication (FS-DFS-Replication)..." -color "Info" + $r2 = Install-WindowsFeatureWithTimeout -FeatureName 'FS-DFS-Replication' -DisplayName 'DFS Replication' -IncludeManagementTools + if (-not $r2.Success) { $ok = $false; Write-OutputColor " DFS Replication install failed." -color "Error"; if ($r2.Error) { Write-OutputColor " $($r2.Error.Trim())" -color "Error" } } + } + + if ($ok) { + Write-OutputColor " DFS roles installed." -color "Success" + Add-SessionChange -Category "Roles" -Description "Installed DFS roles (Namespace + Replication)" + Clear-MenuCache + # When invoked as part of a Dry-Run queue apply, the queue owns the undo — + # don't also register a session undo (it would double-remove on rollback). + if (-not $script:ApplyingDryRunQueue) { + $undoNs = $needNs; $undoRep = $needRep + Add-UndoAction -Category "Roles" -Description "Installed DFS roles" -UndoScript { + param($RemoveNs, $RemoveRep) + if ($RemoveRep) { Uninstall-WindowsFeature -Name 'FS-DFS-Replication' -ErrorAction SilentlyContinue | Out-Null } + if ($RemoveNs) { Uninstall-WindowsFeature -Name 'FS-DFS-Namespace' -ErrorAction SilentlyContinue | Out-Null } + } -UndoParams @{ RemoveNs = $undoNs; RemoveRep = $undoRep } + } + } + return $ok +} + +# Interactive: DFS audit + optional role install. +function Show-DFSManagement { + Clear-Host + Write-CenteredOutput "DFS Namespaces & Replication" -color "Info" + $s = Get-DFSStatus + if (-not $s.Available) { + Write-OutputColor " DFS management tools are not available on this host." -color "Error" + Write-OutputColor " Install the DFS roles / RSAT DFS management tools to use this feature." -color "Info" + Write-PressEnter; return + } + $nsColor = if ($s.NamespaceRoleInstalled) { "Success" } else { "Warning" } + $repColor = if ($s.ReplicationRoleInstalled) { "Success" } else { "Warning" } + Write-OutputColor "" -color "Info" + Write-OutputColor " ┌────────────────────────────────────────────────────────────────────────┐" -color "Info" + Write-OutputColor " │$(" DFS STATUS".PadRight(72))│" -color "Info" + Write-OutputColor " ├────────────────────────────────────────────────────────────────────────┤" -color "Info" + Write-OutputColor " │$(" Namespace role : $(if ($s.NamespaceRoleInstalled) { 'Installed' } else { 'Not installed' })".PadRight(72))│" -color $nsColor + Write-OutputColor " │$(" Replication role : $(if ($s.ReplicationRoleInstalled) { 'Installed' } else { 'Not installed' })".PadRight(72))│" -color $repColor + Write-OutputColor " │$(" Namespaces : $($s.Namespaces.Count)".PadRight(72))│" -color "Info" + Write-OutputColor " │$(" Replication groups: $($s.ReplicationGroups.Count)".PadRight(72))│" -color "Info" + Write-OutputColor " │$(" Replicated folders: $($s.ReplicatedFolderCount)".PadRight(72))│" -color "Info" + if ($s.Namespaces.Count -gt 0) { + Write-OutputColor " ├────────────────────────────────────────────────────────────────────────┤" -color "Info" + foreach ($n in ($s.Namespaces | Select-Object -First 6)) { + $line = " NS: $n" + if ($line.Length -gt 72) { $line = $line.Substring(0, 69) + "..." } + Write-OutputColor " │$($line.PadRight(72))│" -color "Info" + } + } + if ($s.ReplicationGroups.Count -gt 0) { + Write-OutputColor " ├────────────────────────────────────────────────────────────────────────┤" -color "Info" + foreach ($g in ($s.ReplicationGroups | Select-Object -First 6)) { + $line = " RG: $g" + if ($line.Length -gt 72) { $line = $line.Substring(0, 69) + "..." } + Write-OutputColor " │$($line.PadRight(72))│" -color "Info" + } + } + Write-OutputColor " └────────────────────────────────────────────────────────────────────────┘" -color "Info" + Write-OutputColor "" -color "Info" + + if (-not ($s.NamespaceRoleInstalled -and $s.ReplicationRoleInstalled)) { + if (Confirm-UserAction -Message "Install the missing DFS server role(s)?") { Install-DFSRoles } + } else { + Write-OutputColor " Both DFS roles are installed. Manage namespaces and replication groups in" -color "Info" + Write-OutputColor " the DFS Management console (dfsmgmt.msc)." -color "Info" + } + Write-PressEnter +} + +# CLI: DFSAudit — read-only DFS posture (JSON-aware). +function Start-DFSAudit { + $s = Get-DFSStatus + if ($script:CLIOutputFormat -eq 'JSON') { + Write-Output (@{ + Tool = $script:ToolFullName; Version = $script:ScriptVersion; Action = 'DFSAudit' + Timestamp = (Get-Date -Format "yyyy-MM-ddTHH:mm:ss"); Hostname = $env:COMPUTERNAME + Available = $s.Available + NamespaceRoleInstalled = $s.NamespaceRoleInstalled + ReplicationRoleInstalled = $s.ReplicationRoleInstalled + Namespaces = $s.Namespaces + ReplicationGroups = $s.ReplicationGroups + ReplicatedFolderCount = $s.ReplicatedFolderCount + } | ConvertTo-Json -Depth 4) + } + else { Show-DFSManagement } + return $true +} +#endregion diff --git a/README.md b/README.md index 7327448..8130d8f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5121 structural tests + 5142 structural tests Pester 312 tests SLSA Level 3

@@ -37,7 +37,7 @@ --- -RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." Where sconfig gives you 15 options, RackStack gives you 199 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. +RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." Where sconfig gives you 15 options, RackStack gives you 200 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly and want it done right every time. @@ -61,7 +61,7 @@ Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly **Automation** -- JSON-driven batch mode (24 idempotent steps with transaction rollback), Quick Setup Wizard, configuration export/import, HTML reports, JSON audit logging with rotation -**Monitoring** -- 199 CLI actions with JSON output for fleet automation, `ServerScore` (unified 0-100 health grade), `HealthDashboard` (all-in-one monitoring endpoint), `ClusterHealthScore`, `StorageHealthScore`, System Center (SCCM/SCOM/WAC) + Azure AD/Intune integration +**Monitoring** -- 200 CLI actions with JSON output for fleet automation, `ServerScore` (unified 0-100 health grade), `HealthDashboard` (all-in-one monitoring endpoint), `ClusterHealthScore`, `StorageHealthScore`, System Center (SCCM/SCOM/WAC) + Azure AD/Intune integration **Cloud & Security** -- Azure Arc server onboarding (install the Connected Machine Agent, connect the host to Azure's hybrid management plane via service-principal auth); Microsoft Defender for Endpoint onboarding (activate the built-in EDR sensor against your tenant, with a built-in detection test) @@ -141,7 +141,7 @@ Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process .\RackStack.ps1 ``` -> **`RackStack.ps1`** is the **modular loader** (~130 lines). It dot-sources all 79 modules from `Modules/` and starts the tool. Use this for development -- edit individual module files, then run. +> **`RackStack.ps1`** is the **modular loader** (~130 lines). It dot-sources all 80 modules from `Modules/` and starts the tool. Use this for development -- edit individual module files, then run. ### Single-File Deployment (Production) @@ -152,7 +152,7 @@ For production use, generate a monolithic single-file script (~66K lines) that y .\sync-to-monolithic.ps1 ``` -The output is **`RackStack v{version}.ps1`** -- a self-contained single file with all 79 modules baked in (version from `00-Initialization.ps1`). This is the file used to compile the `.exe`. +The output is **`RackStack v{version}.ps1`** -- a self-contained single file with all 80 modules baked in (version from `00-Initialization.ps1`). This is the file used to compile the `.exe`. > **Don't confuse the two:** `RackStack.ps1` = modular loader for development. `RackStack v{version}.ps1` = monolithic build for deployment/compilation. @@ -439,7 +439,7 @@ $report.Issues **Tiers:** `Light` (minimal, safe for prod), `Standard` (recommended), `Aggressive` (maximum cleanup/debloat). -### 199 CLI Actions +### 200 CLI Actions | Category | Actions | |----------|---------| @@ -475,7 +475,7 @@ Run `RackStack.exe -ListActions` or `RackStack.exe -ListActions -OutputFormat JS ``` RackStack/ -├── RackStack.ps1 # Modular loader -- dot-sources 79 modules (dev use) +├── RackStack.ps1 # Modular loader -- dot-sources 80 modules (dev use) ├── RackStack v{version}.ps1 # Monolithic build -- all modules in one file (deploy/compile) ├── RackStack.exe # Compiled from the monolithic .ps1 via ps2exe ├── defaults.json # Your environment config (gitignored) @@ -496,7 +496,7 @@ RackStack/ ### Module Architecture -79 modules numbered for load order. Dependencies flow downward. +80 modules numbered for load order. Dependencies flow downward. | Range | Category | Highlights | |---|---|---| @@ -528,7 +528,7 @@ RackStack/ # Full test suite (5,048 tests, ~4 minutes) powershell -ExecutionPolicy Bypass -File Tests\Run-Tests.ps1 -# PSScriptAnalyzer (0 errors on all 79 modules + monolithic) +# PSScriptAnalyzer (0 errors on all 80 modules + monolithic) powershell -ExecutionPolicy Bypass -File Tests\pssa-check.ps1 # Pre-release validation (parse + PSSA + structure + sync + version + tests) diff --git a/RackStack.ps1 b/RackStack.ps1 index b51adb3..42a5d07 100644 --- a/RackStack.ps1 +++ b/RackStack.ps1 @@ -3,7 +3,7 @@ RackStack - Modular Loader (Development) .DESCRIPTION - This is the MODULAR LOADER -- it dot-sources all 79 modules from the Modules/ + This is the MODULAR LOADER -- it dot-sources all 80 modules from the Modules/ subfolder and starts RackStack. Use this file for development and testing. This is NOT the monolithic build. The monolithic single-file version is: @@ -13,7 +13,7 @@ Environment-specific settings are configured via defaults.json. .VERSION - 1.117.0 + 1.118.0 .NOTES - Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing) @@ -116,6 +116,7 @@ $moduleFiles = @( "76-SIEMForwarder.ps1" "77-WindowsAdminCenter.ps1" "78-CertificateAudit.ps1" + "79-DFS.ps1" ) # Load all modules diff --git a/RackStack.psd1 b/RackStack.psd1 index 066a777..eebce69 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -1,6 +1,6 @@ @{ RootModule = 'RackStack.psm1' - ModuleVersion = '1.117.0' + ModuleVersion = '1.118.0' GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91' Author = 'TheAbider' CompanyName = 'TheAbider' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 5b92ab3..0b64250 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -116,7 +116,7 @@ if (Test-Path $_testInitFile) { } } $monolithicPath = Join-Path (Join-Path $script:ModuleRoot "builds") "$_testToolFullName v$_testScriptVersion.ps1" -$expectedModuleCount = 79 # 00-78 inclusive +$expectedModuleCount = 80 # 00-79 inclusive # ============================================================================ # BANNER @@ -742,8 +742,8 @@ try { try { $firstName = $moduleFiles[0].Name $lastName = $moduleFiles[-1].Name - $pass = $firstName -eq "00-Initialization.ps1" -and $lastName -eq "78-CertificateAudit.ps1" - Write-TestResult "Module range 00-Initialization to 78-CertificateAudit" $pass "First=$firstName, Last=$lastName" + $pass = $firstName -eq "00-Initialization.ps1" -and $lastName -eq "79-DFS.ps1" + Write-TestResult "Module range 00-Initialization to 79-DFS" $pass "First=$firstName, Last=$lastName" } catch { Write-TestResult "Module range verification" $false $_.Exception.Message } @@ -2158,8 +2158,8 @@ try { if ($line -match '^\s*#region\s') { $regionStartCount++ } if ($line -match '^\s*#endregion') { $regionEndCount++ } } - Write-TestResult "Monolithic has 78 #region tags" ($regionStartCount -eq 78) "Found: $regionStartCount" - Write-TestResult "Monolithic has 78 #endregion tags" ($regionEndCount -eq 78) "Found: $regionEndCount" + Write-TestResult "Monolithic has 79 #region tags" ($regionStartCount -eq 79) "Found: $regionStartCount" + Write-TestResult "Monolithic has 79 #endregion tags" ($regionEndCount -eq 79) "Found: $regionEndCount" Write-TestResult "Region start/end counts match" ($regionStartCount -eq $regionEndCount) "Starts=$regionStartCount, Ends=$regionEndCount" } catch { Write-TestResult "Region count verification" $false $_.Exception.Message @@ -4490,7 +4490,7 @@ Write-TestResult "README.md exists" (Test-Path $readmePath) try { $readmeContent = Get-Content $readmePath -Raw - Write-TestResult "README: mentions 78 modules" ($readmeContent -match '79 module') + Write-TestResult "README: mentions 78 modules" ($readmeContent -match '80 module') Write-TestResult "README: has batch mode section" ($readmeContent -match 'Batch Mode') Write-TestResult "README: has testing section" ($readmeContent -match 'Testing') Write-TestResult "README: has defaults.json example" ($readmeContent -match 'defaults\.json') @@ -6898,11 +6898,11 @@ try { # RackStack.ps1 loader includes 62-HyperVReplica.ps1 $loaderContent = Get-Content $loaderPath -Raw Write-TestResult "RackStack.ps1: loads 62-HyperVReplica.ps1" ($loaderContent -match '62-HyperVReplica\.ps1') - Write-TestResult "RackStack.ps1: mentions 78 modules" ($loaderContent -match '79 modules') + Write-TestResult "RackStack.ps1: mentions 78 modules" ($loaderContent -match '80 modules') # Module count verification $moduleCount = (Get-ChildItem -Path $modulesPath -Filter "*.ps1").Count - Write-TestResult "Module count is 79" ($moduleCount -eq 79) "Found $moduleCount modules" + Write-TestResult "Module count is 80" ($moduleCount -eq 80) "Found $moduleCount modules" # Changelog mentions v1.4.0 $changelogPath = Join-Path $script:ModuleRoot "Changelog.md" @@ -9281,6 +9281,48 @@ catch { Write-TestResult "Certificate Binding Audit Tests" $false $_.Exception.Message } +# ============================================================================ +# SECTION 185: DFS NAMESPACES & REPLICATION (v1.118.0, NEW module 79) +# ============================================================================ +Write-SectionHeader "SECTION 185: DFS NAMESPACES & REPLICATION (79-DFS)" + +try { + $dfsPath = "$modulesPath\79-DFS.ps1" + Write-TestResult "79-DFS: module file exists" (Test-Path $dfsPath) + $dfsC = Get-Content $dfsPath -Raw + Write-TestResult "79-DFS: Test-DFSRoleInstalled exists" ($dfsC -match 'function\s+Test-DFSRoleInstalled\b') + Write-TestResult "79-DFS: Get-DFSStatus exists" ($dfsC -match 'function\s+Get-DFSStatus\b') + Write-TestResult "79-DFS: Install-DFSRoles exists" ($dfsC -match 'function\s+Install-DFSRoles\b') + Write-TestResult "79-DFS: Show-DFSManagement exists" ($dfsC -match 'function\s+Show-DFSManagement\b') + Write-TestResult "79-DFS: Start-DFSAudit exists" ($dfsC -match 'function\s+Start-DFSAudit\b') + # Reports both DFS-N namespaces and DFS-R replication. + Write-TestResult "79-DFS: audits namespaces + replication" ($dfsC -match 'Get-DfsnRoot' -and $dfsC -match 'Get-DfsReplicationGroup') + Write-TestResult "79-DFS: targets the real DFS role features" ($dfsC -match 'FS-DFS-Namespace' -and $dfsC -match 'FS-DFS-Replication') + # Role install uses the mandated timeout wrapper (not a bare Install-WindowsFeature in the live path). + Write-TestResult "79-DFS: role install uses timeout wrapper" ($dfsC -match 'Install-WindowsFeatureWithTimeout') + # Reversible: captures which features were missing + registers an undo that uninstalls only those. + Write-TestResult "79-DFS: role install is reversible (undo)" ($dfsC -match 'function\s+Install-DFSRoles[\s\S]{0,3000}Add-UndoAction[\s\S]{0,400}Uninstall-WindowsFeature') + Write-TestResult "79-DFS: role install is Dry-Run aware (reversible)" ($dfsC -match 'Install-DFSRoles[\s\S]{0,1500}Push-DryRunStep[\s\S]{0,400}-OneWay \$false') + # Server-SKU gated. + Write-TestResult "79-DFS: role install is server-SKU gated" ($dfsC -match 'Test-WindowsServer') + Write-TestResult "79-DFS: DFSAudit JSON-aware" ($dfsC -match "Start-DFSAudit[\s\S]{0,300}CLIOutputFormat -eq 'JSON'") + # Module + menu + CLI wiring. + $loaderD = Get-Content (Join-Path $script:ModuleRoot "RackStack.ps1") -Raw + Write-TestResult "RackStack.ps1: loads 79-DFS" ($loaderD -match '79-DFS\.ps1') + $menuD = Get-Content "$modulesPath\48-MenuDisplay.ps1" -Raw + Write-TestResult "48-MenuDisplay: Roles menu [14] DFS" ($menuD -match '\[14\]\s*DFS Namespaces') + $runnerD = Get-Content "$modulesPath\49-MenuRunner.ps1" -Raw + Write-TestResult "49-MenuRunner: Roles menu case 14 wired" ($runnerD -match '"14"\s*\{\s*Show-DFSManagement') + Write-TestResult "49-MenuRunner: Roles invalid msg bumped to 1-14" ($runnerD -match 'Enter 1-14 or B') + $dfsEntry = Get-Content "$modulesPath\50-EntryPoint.ps1" -Raw + Write-TestResult "50-EntryPoint: DFSAudit dispatch case" ($dfsEntry -match "'DFSAudit'\s*\{") + $dfsHeader = Get-Content (Join-Path $script:ModuleRoot "Header.ps1") -Raw + Write-TestResult "Header.ps1: DFSAudit in -Action ValidateSet" ($dfsHeader -match "'DFSAudit'") +} +catch { + Write-TestResult "DFS Namespaces & Replication Tests" $false $_.Exception.Message +} + # ============================================================================ # SECTION 174: DOCUMENTATION FRESHNESS (counts must match the codebase) # ============================================================================ @@ -13292,7 +13334,7 @@ try { # Action list in -ListActions block has 160 entries $listBlock = [regex]::Match($ep5, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $listActionCount = @([regex]::Matches($listBlock, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 199 entries" ($listActionCount -eq 199) "Found $listActionCount" + Write-TestResult "50-EntryPoint: action list has 200 entries" ($listActionCount -eq 200) "Found $listActionCount" } catch { Write-TestResult "v1.91.0 Tests" $false $_.Exception.Message } @@ -13325,7 +13367,7 @@ try { # Action list count (should be 167 now) $listBlock2 = [regex]::Match($ep6, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount2 = @([regex]::Matches($listBlock2, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 199 entries" ($actionCount2 -eq 199) "Found $actionCount2" + Write-TestResult "50-EntryPoint: action list has 200 entries" ($actionCount2 -eq 200) "Found $actionCount2" } catch { Write-TestResult "v1.92.0 Tests" $false $_.Exception.Message } @@ -13351,7 +13393,7 @@ try { # Action count updated $listBlock3 = [regex]::Match($ep7, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount3 = @([regex]::Matches($listBlock3, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 199 entries" ($actionCount3 -eq 199) "Found $actionCount3" + Write-TestResult "50-EntryPoint: action list has 200 entries" ($actionCount3 -eq 200) "Found $actionCount3" } catch { Write-TestResult "v1.93.0 Tests" $false $_.Exception.Message } @@ -13389,7 +13431,7 @@ try { # Action list count $listBlock4 = [regex]::Match($ep8, '\$actionList = @\([\s\S]*?\)[\s\S]{0,50}CLIOutputFormat').Value $actionCount4 = @([regex]::Matches($listBlock4, "Action\s*=\s*'")).Count - Write-TestResult "50-EntryPoint: action list has 199 entries" ($actionCount4 -eq 199) "Found $actionCount4" + Write-TestResult "50-EntryPoint: action list has 200 entries" ($actionCount4 -eq 200) "Found $actionCount4" } catch { Write-TestResult "v1.94.1 Tests" $false $_.Exception.Message } diff --git a/dist/chocolatey/rackstack.nuspec b/dist/chocolatey/rackstack.nuspec index d649816..757c663 100644 --- a/dist/chocolatey/rackstack.nuspec +++ b/dist/chocolatey/rackstack.nuspec @@ -18,7 +18,7 @@ windows-server hyper-v iscsi clustering powershell sysadmin automation msp admin-tools PowerShell automation toolkit for configuring Windows Server hosts -RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." 199 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, and batch automation, all with undo, transaction rollback, and audit logging. +RackStack is a menu-driven PowerShell tool that automates everything between "Windows is installed" and "server is in production." 200 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins, and infrastructure teams who build servers repeatedly and want it done right every time. diff --git a/dist/scoop/rackstack.json b/dist/scoop/rackstack.json index 6deb4a3..d8405a4 100644 --- a/dist/scoop/rackstack.json +++ b/dist/scoop/rackstack.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", "version": "0.0.0", - "description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a code-signed EXE plus a PowerShell Gallery wrapper module exposing 199 structured CLI actions.", + "description": "PowerShell automation toolkit for configuring Windows Server hosts — Hyper-V virtualization hosts, failover cluster nodes, iSCSI storage clients, Active Directory members, and standalone servers. Ships as a code-signed EXE plus a PowerShell Gallery wrapper module exposing 200 structured CLI actions.", "homepage": "https://github.com/TheAbider/RackStack", "license": "MIT", "url": "https://github.com/TheAbider/RackStack/releases/download/v0.0.0/RackStack.exe", diff --git a/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml b/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml index afa0684..cec5b04 100644 --- a/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml +++ b/dist/winget/1.99.1/TheAbider.RackStack.locale.en-US.yaml @@ -13,7 +13,7 @@ Copyright: Copyright (c) 2026 TheAbider ShortDescription: PowerShell automation toolkit for configuring Windows Server hosts. Description: |- RackStack is a menu-driven PowerShell tool that automates everything between - "Windows is installed" and "server is in production." It provides 199 CLI + "Windows is installed" and "server is in production." It provides 200 CLI actions and 60+ interactive menus covering networking, Hyper-V, SAN/iSCSI, clustering, VM deployment, cloud onboarding, and batch automation, all with undo, transaction rollback, and audit logging. Built for MSPs, sysadmins, diff --git a/sync-to-monolithic.ps1 b/sync-to-monolithic.ps1 index 68f0d1a..6caffee 100644 --- a/sync-to-monolithic.ps1 +++ b/sync-to-monolithic.ps1 @@ -42,7 +42,7 @@ if (-not (Test-Path $monoPath)) { $initModuleFiles = Get-ChildItem $modulesDir -Filter "*.ps1" | Sort-Object Name # Verify expected module count - $expectedModuleCount = 79 + $expectedModuleCount = 80 $actualModuleCount = @($initModuleFiles).Count if ($actualModuleCount -ne $expectedModuleCount) { Write-Warning "Expected $expectedModuleCount modules but found $actualModuleCount" @@ -120,7 +120,7 @@ Write-Host "Monolithic: $($monoLines.Count) lines" $moduleFiles = Get-ChildItem $modulesDir -Filter "*.ps1" | Sort-Object Name # Verify expected module count -$expectedModuleCount = 79 +$expectedModuleCount = 80 $actualModuleCount = @($moduleFiles).Count if ($actualModuleCount -ne $expectedModuleCount) { Write-Warning "Expected $expectedModuleCount modules but found $actualModuleCount"