Skip to content

Commit 03a3271

Browse files
committed
v1.9.56 — Operations & Safety
- Batch role template install pre-fetches feature states (N+1 fix) - Transcript cleanup .Count wrapped in @() for PS 5.1 - Remote service manager requires confirmation before actions - Remote service manager pre-checks connectivity - SMB3 backend detection no longer false-positives on mapped drives
1 parent 41c5a8b commit 03a3271

8 files changed

Lines changed: 75 additions & 36 deletions

File tree

Changelog.md

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

3+
## v1.9.56
4+
5+
- **Enhancement:** Batch role template installation pre-fetches all Windows Feature states in a single `Get-WindowsFeature` call instead of querying individually per feature — reduces WMI/DISM round-trips by up to 10x for large templates (50-EntryPoint).
6+
- **Bug Fix:** Transcript cleanup age-based removal now wraps filtered results in `@()` for PS 5.1 `.Count` safety — previously, cleaning up exactly one old transcript would report "Cleaned up old transcript(s)" with a blank count (50-EntryPoint).
7+
- **Enhancement:** Remote service manager now requires `Confirm-UserAction` before starting, stopping, or restarting services on remote servers — prevents accidental service disruption on production systems (56-OperationsMenu).
8+
- **Enhancement:** Remote service manager pre-checks connectivity with `Test-Connection` before attempting RPC service query — fails fast with a clear message instead of hanging for 60+ seconds on unreachable targets (56-OperationsMenu).
9+
- **Bug Fix:** Storage backend auto-detection no longer false-positives SMB3 whenever any SMB mapped drive exists — now checks specifically for cluster File Server or Scale-Out File Server resources instead of generic `Get-SmbMapping` (59-StorageBackends).
10+
- 63 modules, 1854 tests
11+
312
## v1.9.55
413

514
- **Enhancement:** Server role templates now validate that the PostInstall function exists before attempting to invoke it — prevents confusing errors when custom templates reference functions that haven't been loaded or don't exist, with a clear warning message instead (60-ServerRoleTemplates).

Header.ps1

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,19 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.9.55
33+
1.9.56
3434
3535
.LAST UPDATED
3636
03/03/2026
3737
38+
.CHANGELOG v1.9.56
39+
OPERATIONS & SAFETY PATCH:
40+
- IMPROVED: Batch role template install pre-fetches all feature states in one query instead of per-feature Get-WindowsFeature calls (N+1 optimization)
41+
- FIX: Transcript cleanup .Count on single-object result now wrapped in @() for PS 5.1 safety
42+
- IMPROVED: Remote service manager requires confirmation before start/stop/restart on remote servers
43+
- IMPROVED: Remote service manager pre-checks connectivity before attempting RPC service query
44+
- FIX: Storage backend detection no longer false-positives SMB3 on servers with unrelated mapped drives — now checks for cluster SMB resources specifically
45+
3846
.CHANGELOG v1.9.55
3947
REPORTING & TEMPLATES PATCH:
4048
- IMPROVED: Server role templates validate PostInstall function exists before invocation — prevents confusing errors from custom templates referencing missing functions

Modules/00-Initialization.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ if (-not $script:ModuleRoot) { $script:ModuleRoot = $PSScriptRoot }
135135
if (-not $script:ModuleRoot -and $script:ScriptPath) {
136136
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
137137
}
138-
$script:ScriptVersion = "1.9.55"
138+
$script:ScriptVersion = "1.9.56"
139139
$script:ScriptStartTime = Get-Date
140140

141141
# OS version detection (for feature compatibility)

Modules/50-EntryPoint.ps1

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ function Remove-OldTranscripts {
5656

5757
# Age-based cleanup: remove logs older than DaysToKeep
5858
$cutoffDate = (Get-Date).AddDays(-$DaysToKeep)
59-
$oldLogs = $allLogs | Where-Object { $_.LastWriteTime -lt $cutoffDate }
59+
$oldLogs = @($allLogs | Where-Object { $_.LastWriteTime -lt $cutoffDate })
6060

61-
if ($oldLogs) {
61+
if ($oldLogs.Count -gt 0) {
6262
$count = $oldLogs.Count
6363
$oldLogs | Remove-Item -Force -ErrorAction SilentlyContinue
6464
Write-OutputColor "Cleaned up $count old transcript(s) (older than $DaysToKeep days)" -color "Debug"
@@ -854,9 +854,11 @@ function Start-BatchMode {
854854
Write-OutputColor " [$stepNum/$totalSteps] Installing role template: $($template.FullName)..." -color "Info"
855855
try {
856856
$installCount = 0
857+
# Pre-fetch all feature states once (avoids N+1 Get-WindowsFeature calls per feature)
858+
$installedFeatures = @(Get-WindowsFeature -Name $template.Features -ErrorAction SilentlyContinue | Where-Object { $_.Installed })
859+
$installedNames = @($installedFeatures | ForEach-Object { $_.Name })
857860
foreach ($featureName in $template.Features) {
858-
$wf = Get-WindowsFeature -Name $featureName -ErrorAction SilentlyContinue
859-
if ($null -eq $wf -or -not $wf.Installed) {
861+
if ($featureName -notin $installedNames) {
860862
$null = Install-WindowsFeature -Name $featureName -IncludeManagementTools -ErrorAction Stop
861863
$installCount++
862864
}

Modules/56-OperationsMenu.ps1

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,14 @@ function Invoke-RemoteServiceManager {
282282
if ([string]::IsNullOrWhiteSpace($target)) { return }
283283

284284
Write-OutputColor "" -color "Info"
285+
Write-OutputColor " Testing connectivity to $target ..." -color "Info"
286+
287+
if (-not (Test-Connection -ComputerName $target -Count 1 -Quiet -ErrorAction SilentlyContinue)) {
288+
Write-OutputColor " Cannot reach $target — host is unreachable or offline." -color "Error"
289+
Write-PressEnter
290+
return
291+
}
292+
285293
Write-OutputColor " Fetching services from $target ..." -color "Info"
286294
Write-OutputColor "" -color "Info"
287295

@@ -315,32 +323,43 @@ function Invoke-RemoteServiceManager {
315323
if ([string]::IsNullOrWhiteSpace($svcAction) -or $svcAction -eq 'B' -or $svcAction -eq 'b') { return }
316324

317325
$action = Read-Host " Action: [S]tart, [T]op, [R]estart"
318-
switch ($action) {
319-
{ $_ -eq 'S' -or $_ -eq 's' } {
320-
try {
321-
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Start-Service -ErrorAction Stop
322-
Write-OutputColor " Service '$svcAction' started." -color "Success"
323-
}
324-
catch {
325-
Write-OutputColor " Failed to start service '$svcAction': $($_.Exception.Message)" -color "Error"
326-
}
327-
}
328-
{ $_ -eq 'T' -or $_ -eq 't' } {
329-
try {
330-
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Stop-Service -Force -ErrorAction Stop
331-
Write-OutputColor " Service '$svcAction' stopped." -color "Success"
332-
}
333-
catch {
334-
Write-OutputColor " Failed to stop service '$svcAction': $($_.Exception.Message)" -color "Error"
326+
$actionName = switch ($action) {
327+
{ $_ -eq 'S' -or $_ -eq 's' } { "start" }
328+
{ $_ -eq 'T' -or $_ -eq 't' } { "stop" }
329+
{ $_ -eq 'R' -or $_ -eq 'r' } { "restart" }
330+
default { $null }
331+
}
332+
if (-not $actionName) {
333+
Write-OutputColor " Invalid action." -color "Error"
334+
}
335+
elseif (Confirm-UserAction -Message "$($actionName.Substring(0,1).ToUpper() + $actionName.Substring(1)) service '$svcAction' on $target?") {
336+
switch ($action) {
337+
{ $_ -eq 'S' -or $_ -eq 's' } {
338+
try {
339+
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Start-Service -ErrorAction Stop
340+
Write-OutputColor " Service '$svcAction' started." -color "Success"
341+
}
342+
catch {
343+
Write-OutputColor " Failed to start service '$svcAction': $($_.Exception.Message)" -color "Error"
344+
}
335345
}
336-
}
337-
{ $_ -eq 'R' -or $_ -eq 'r' } {
338-
try {
339-
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Restart-Service -Force -ErrorAction Stop
340-
Write-OutputColor " Service '$svcAction' restarted." -color "Success"
346+
{ $_ -eq 'T' -or $_ -eq 't' } {
347+
try {
348+
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Stop-Service -Force -ErrorAction Stop
349+
Write-OutputColor " Service '$svcAction' stopped." -color "Success"
350+
}
351+
catch {
352+
Write-OutputColor " Failed to stop service '$svcAction': $($_.Exception.Message)" -color "Error"
353+
}
341354
}
342-
catch {
343-
Write-OutputColor " Failed to restart service '$svcAction': $($_.Exception.Message)" -color "Error"
355+
{ $_ -eq 'R' -or $_ -eq 'r' } {
356+
try {
357+
Get-Service -ComputerName $target -Name $svcAction -ErrorAction Stop | Restart-Service -Force -ErrorAction Stop
358+
Write-OutputColor " Service '$svcAction' restarted." -color "Success"
359+
}
360+
catch {
361+
Write-OutputColor " Failed to restart service '$svcAction': $($_.Exception.Message)" -color "Error"
362+
}
344363
}
345364
}
346365
}

Modules/59-StorageBackends.ps1

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,12 @@ function Get-DetectedStorageBackend {
7676
if ($fcDisks) { return "FC" }
7777
}
7878

79-
# Check for SMB shares used by cluster
79+
# Check for SMB shares used by cluster (File Share resource or CSVs backed by SMB)
8080
try {
81-
$clusterResources = Get-ClusterResource -ErrorAction SilentlyContinue | Where-Object { $_.ResourceType -eq "File Share Witness" }
82-
$smbDisks = Get-SmbMapping -ErrorAction SilentlyContinue
83-
if ($smbDisks) { return "SMB3" }
81+
$clusterSMBResources = Get-ClusterResource -ErrorAction SilentlyContinue | Where-Object {
82+
$_.ResourceType -eq "File Server" -or $_.ResourceType -eq "Scale-Out File Server"
83+
}
84+
if ($clusterSMBResources) { return "SMB3" }
8485
}
8586
catch { }
8687

RackStack.ps1

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

Tests/Run-Tests.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<#
22
.SYNOPSIS
3-
Automated Test Runner for RackStack v1.9.55
3+
Automated Test Runner for RackStack v1.9.56
44
55
.DESCRIPTION
66
Comprehensive non-interactive test suite covering:

0 commit comments

Comments
 (0)