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
17 changes: 16 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,22 @@ jobs:
$mono = "builds\RackStack v$ver.ps1"
if (-not (Test-Path $mono)) { throw "Monolithic not found at $mono" }
if (-not (Test-Path 'RackStack.ico')) { throw "RackStack.ico missing — required for compile" }
Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' -Version $ver -RequireAdmin -IconFile 'RackStack.ico'
# Populate the full version resource. Company/Product/Description
# were empty in every release through v1.122.3, which is both a mild
# heuristic-AV signal (legitimate software fills these in) and a real
# UX gap: -RequireAdmin raises a UAC prompt, and UAC displays
# FileDescription as the program name — so users were being asked to
# elevate a blank. Values match the identity already published in
# RackStack.psd1, the Chocolatey nuspec, and the Scoop manifest.
Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' `
-Version $ver `
-RequireAdmin `
-IconFile 'RackStack.ico' `
-title 'RackStack - Windows Server configuration toolkit' `
-product 'RackStack' `
-company 'TheAbider' `
-copyright 'Copyright (c) 2026 TheAbider' `
-description 'Menu-driven configuration and automation for Windows Server hosts.'
$info = Get-Item 'builds\RackStack.exe'
Write-Host "Compiled: $($info.FullName) ($([math]::Round($info.Length / 1MB, 2)) MB)"

Expand Down
10 changes: 10 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## v1.122.4

Hardens what the tool will let you exclude from Defender, and fixes an executable that shipped without a name.

- **RackStack refuses to exclude a script interpreter from Defender.** A process exclusion does not exempt one application -- it exempts every payload that process will ever execute, so excluding `powershell.exe` stops Defender inspecting all PowerShell on the host from then on. The custom *path* prompt has warned before excluding an operating-system directory since v1.98; the custom *process* prompt accepted anything. It now declines `powershell`, `pwsh`, `cmd`, `wscript`, `cscript`, `mshta`, `rundll32`, `regsvr32` and the .NET command-line hosts, however they are spelled, and points at a path exclusion scoped to the application's own folder instead. The recommended Hyper-V exclusions this tool has always applied were never affected.
- **The executable now carries its own identity.** Company, product, description and copyright were empty in every previous release. Windows shows the description as the program name in the elevation prompt, so users were being asked to approve a blank; it now names the tool. A complete version resource also reads as ordinary software to antivirus heuristics, which an empty one does not.
- **The build pins the compiler that produces the executable**, so a published binary cannot change without a corresponding change in the repository.

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

## v1.122.3

Closes the second self-update path, and corrects a packaging claim.
Expand Down
2 changes: 1 addition & 1 deletion Header.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
7h3 4b1d3r

.VERSION
1.122.3
1.122.4
.LAST UPDATED
07/28/2026

Expand Down
2 changes: 1 addition & 1 deletion Modules/00-Initialization.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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.122.3"
$script:ScriptVersion = "1.122.4"
$script:ScriptStartTime = Get-Date

# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<a href="https://www.bestpractices.dev/projects/12921"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12921/badge"></a>
<a href="https://codecov.io/gh/TheAbider/RackStack"><img alt="codecov" src="https://codecov.io/gh/TheAbider/RackStack/branch/master/graph/badge.svg"></a>
<img alt="PSScriptAnalyzer 0 errors" src="https://img.shields.io/badge/PSScriptAnalyzer-0%20errors-brightgreen">
<img alt="5474 structural tests" src="https://img.shields.io/badge/structural%20tests-5474-brightgreen">
<img alt="5485 structural tests" src="https://img.shields.io/badge/structural%20tests-5485-brightgreen">
<img alt="Pester 312 tests" src="https://img.shields.io/badge/Pester-312%20tests-brightgreen">
<img alt="SLSA Level 3" src="https://slsa.dev/images/gh-badge-level3.svg">
</p>
Expand Down
2 changes: 1 addition & 1 deletion RackStack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
Environment-specific settings are configured via rackstack.config.json (a legacy defaults.json is still read).

.VERSION
1.122.3
1.122.4
.NOTES
- Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing)
- Must be run as Administrator
Expand Down
2 changes: 1 addition & 1 deletion RackStack.psd1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@{
RootModule = 'RackStack.psm1'
ModuleVersion = '1.122.3'
ModuleVersion = '1.122.4'
GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91'
Author = 'TheAbider'
CompanyName = 'TheAbider'
Expand Down
61 changes: 60 additions & 1 deletion Tests/Run-Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<#
.SYNOPSIS
Automated Test Runner for RackStack v1.122.3
Automated Test Runner for RackStack v1.122.4

.DESCRIPTION
Comprehensive non-interactive test suite covering:
Expand Down Expand Up @@ -10754,6 +10754,65 @@ catch {
Write-TestResult "Defender Interpreter Guard Tests" $false $_.Exception.Message
}

# ============================================================================
# SECTION 209: BUILD METADATA INTEGRITY (what ps2exe stamps into the EXE)
# ============================================================================
# Every release through v1.122.3 shipped a binary whose CompanyName,
# ProductName, FileDescription and LegalCopyright were EMPTY — verified by
# reading the version resource out of the published v1.122.3 artifact. Two
# costs: an empty version resource is a mild heuristic-AV signal because
# legitimate software populates it, and -RequireAdmin raises a UAC prompt that
# displays FileDescription as the program name, so users were asked to elevate
# a blank.
#
# The compiler version is pinned here too. ps2exe builds the binary that ships
# to users, so an unpinned Install-Module let the released artifact change
# without a commit — the exposure the SHA-pinning policy already closes for
# actions, including transitive ones.
Write-SectionHeader "SECTION 209: BUILD METADATA INTEGRITY"

try {
$ciPath209 = Join-Path $script:ModuleRoot '.github\workflows\ci.yml'
if (Test-Path -LiteralPath $ciPath209) {
$ci209 = Get-Content -LiteralPath $ciPath209 -Raw

# Compiler must be pinned to an exact version, never floating.
Write-TestResult "Build: ps2exe is pinned to an explicit version" `
([bool]($ci209 -match "\`$ps2exeVersion\s*=\s*'\d+\.\d+\.\d+'"))
Write-TestResult "Build: ps2exe install uses -RequiredVersion" `
([bool]($ci209 -match 'Install-Module ps2exe -RequiredVersion'))
Write-TestResult "Build: ps2exe install is not unpinned" `
([bool]($ci209 -notmatch 'Install-Module ps2exe -Force'))

# The version resource must actually be populated.
$p2e209 = [regex]::Match($ci209, '(?s)Invoke-PS2EXE.*?(?=\r?\n\s*\$info\s*=)')
Write-TestResult "Build: Invoke-PS2EXE call is locatable" $p2e209.Success `
"regex found no ps2exe invocation — the checks below would pass vacuously"
$call209 = $p2e209.Value
foreach ($flag in @('title', 'product', 'company', 'copyright', 'description')) {
Write-TestResult "Build: EXE metadata sets -$flag" `
($p2e209.Success -and $call209 -match "-$flag\s+'")
}
Write-TestResult "Build: EXE still stamps -Version" `
($p2e209.Success -and $call209 -match '-Version\s+\$ver')

# One identity across every published surface. The EXE's CompanyName
# must agree with the Gallery manifest rather than drifting on its own.
$psd209 = Get-Content (Join-Path $script:ModuleRoot 'RackStack.psd1') -Raw
$psdCompany209 = [regex]::Match($psd209, "CompanyName\s*=\s*'([^']+)'").Groups[1].Value
$exeCompany209 = [regex]::Match($call209, "-company\s+'([^']+)'").Groups[1].Value
Write-TestResult "Build: EXE CompanyName matches RackStack.psd1 ('$psdCompany209')" `
($psdCompany209 -and $exeCompany209 -and $psdCompany209 -eq $exeCompany209) `
"psd1='$psdCompany209' exe='$exeCompany209'"
}
else {
Write-TestResult "Build: metadata integrity" -Skipped -Message "no .github/workflows in this layout"
}
}
catch {
Write-TestResult "Build Metadata Integrity Tests" $false $_.Exception.Message
}

# ============================================================================
# FINAL SUMMARY
# ============================================================================
Expand Down