Skip to content

Commit d070822

Browse files
committed
v1.122.4: stamp the EXE with its own identity
Every release through v1.122.3 shipped a binary whose CompanyName, ProductName, FileDescription and LegalCopyright were empty. Verified by reading the version resource straight out of the published v1.122.3 artifact, not inferred from the build script. Two costs. An empty version resource is a mild heuristic-AV signal, because legitimate software populates it and commodity malware often does not. More concretely, the build passes -RequireAdmin, so launching the tool raises a UAC prompt, and UAC displays FileDescription as the program name: users were being asked to elevate a blank. The values match the identity already published in RackStack.psd1, the Chocolatey nuspec and the Scoop manifest, so nothing new is asserted about who ships this. Run-Tests section 209 pins the whole build surface: the compiler stays pinned to an exact version, the ps2exe invocation keeps setting title, product, company, copyright and description, and the EXE's CompanyName must equal RackStack.psd1's so the published identity cannot drift apart across manifests. Section 209 applies the lesson from section 208: it asserts the Invoke-PS2EXE regex actually matched before trusting what the match did or did not contain, so renaming a nearby variable fails the section instead of silently passing it. Mutation-verified six ways, including breaking that anchor. Structural tests 5474 -> 5485.
1 parent 62215e4 commit d070822

8 files changed

Lines changed: 91 additions & 7 deletions

File tree

.github/workflows/ci.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,22 @@ jobs:
172172
$mono = "builds\RackStack v$ver.ps1"
173173
if (-not (Test-Path $mono)) { throw "Monolithic not found at $mono" }
174174
if (-not (Test-Path 'RackStack.ico')) { throw "RackStack.ico missing — required for compile" }
175-
Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' -Version $ver -RequireAdmin -IconFile 'RackStack.ico'
175+
# Populate the full version resource. Company/Product/Description
176+
# were empty in every release through v1.122.3, which is both a mild
177+
# heuristic-AV signal (legitimate software fills these in) and a real
178+
# UX gap: -RequireAdmin raises a UAC prompt, and UAC displays
179+
# FileDescription as the program name — so users were being asked to
180+
# elevate a blank. Values match the identity already published in
181+
# RackStack.psd1, the Chocolatey nuspec, and the Scoop manifest.
182+
Invoke-PS2EXE -InputFile $mono -OutputFile 'builds\RackStack.exe' `
183+
-Version $ver `
184+
-RequireAdmin `
185+
-IconFile 'RackStack.ico' `
186+
-title 'RackStack - Windows Server configuration toolkit' `
187+
-product 'RackStack' `
188+
-company 'TheAbider' `
189+
-copyright 'Copyright (c) 2026 TheAbider' `
190+
-description 'Menu-driven configuration and automation for Windows Server hosts.'
176191
$info = Get-Item 'builds\RackStack.exe'
177192
Write-Host "Compiled: $($info.FullName) ($([math]::Round($info.Length / 1MB, 2)) MB)"
178193

Changelog.md

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

3+
## v1.122.4
4+
5+
Hardens what the tool will let you exclude from Defender, and fixes an executable that shipped without a name.
6+
7+
- **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.
8+
- **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.
9+
- **The build pins the compiler that produces the executable**, so a published binary cannot change without a corresponding change in the repository.
10+
11+
No module or CLI action changes (81 modules, 201 actions).
12+
313
## v1.122.3
414

515
Closes the second self-update path, and corrects a packaging claim.

Header.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
7h3 4b1d3r
3131
3232
.VERSION
33-
1.122.3
33+
1.122.4
3434
.LAST UPDATED
3535
07/28/2026
3636

Modules/00-Initialization.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ if (-not $PSCommandPath -and $script:ScriptPath) {
233233
if (-not $script:ModuleRoot -and $script:ScriptPath) {
234234
$script:ModuleRoot = [System.IO.Path]::GetDirectoryName($script:ScriptPath)
235235
}
236-
$script:ScriptVersion = "1.122.3"
236+
$script:ScriptVersion = "1.122.4"
237237
$script:ScriptStartTime = Get-Date
238238

239239
# Post-update cleanup: UpdateSelf / Rollback leave a `.pending-delete` sibling next to RackStack.exe.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
<a href="https://www.bestpractices.dev/projects/12921"><img alt="OpenSSF Best Practices" src="https://www.bestpractices.dev/projects/12921/badge"></a>
2929
<a href="https://codecov.io/gh/TheAbider/RackStack"><img alt="codecov" src="https://codecov.io/gh/TheAbider/RackStack/branch/master/graph/badge.svg"></a>
3030
<img alt="PSScriptAnalyzer 0 errors" src="https://img.shields.io/badge/PSScriptAnalyzer-0%20errors-brightgreen">
31-
<img alt="5474 structural tests" src="https://img.shields.io/badge/structural%20tests-5474-brightgreen">
31+
<img alt="5485 structural tests" src="https://img.shields.io/badge/structural%20tests-5485-brightgreen">
3232
<img alt="Pester 312 tests" src="https://img.shields.io/badge/Pester-312%20tests-brightgreen">
3333
<img alt="SLSA Level 3" src="https://slsa.dev/images/gh-badge-level3.svg">
3434
</p>

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 rackstack.config.json (a legacy defaults.json is still read).
1414
1515
.VERSION
16-
1.122.3
16+
1.122.4
1717
.NOTES
1818
- Requires Windows Server 2012 R2 or later (or Windows 10/11 for testing)
1919
- Must be run as Administrator

RackStack.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@{
22
RootModule = 'RackStack.psm1'
3-
ModuleVersion = '1.122.3'
3+
ModuleVersion = '1.122.4'
44
GUID = 'c19b8e71-4a35-4f2b-9d06-8a24f7bc0e91'
55
Author = 'TheAbider'
66
CompanyName = 'TheAbider'

Tests/Run-Tests.ps1

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<#
22
.SYNOPSIS
3-
Automated Test Runner for RackStack v1.122.3
3+
Automated Test Runner for RackStack v1.122.4
44

55
.DESCRIPTION
66
Comprehensive non-interactive test suite covering:
@@ -10754,6 +10754,65 @@ catch {
1075410754
Write-TestResult "Defender Interpreter Guard Tests" $false $_.Exception.Message
1075510755
}
1075610756

10757+
# ============================================================================
10758+
# SECTION 209: BUILD METADATA INTEGRITY (what ps2exe stamps into the EXE)
10759+
# ============================================================================
10760+
# Every release through v1.122.3 shipped a binary whose CompanyName,
10761+
# ProductName, FileDescription and LegalCopyright were EMPTY — verified by
10762+
# reading the version resource out of the published v1.122.3 artifact. Two
10763+
# costs: an empty version resource is a mild heuristic-AV signal because
10764+
# legitimate software populates it, and -RequireAdmin raises a UAC prompt that
10765+
# displays FileDescription as the program name, so users were asked to elevate
10766+
# a blank.
10767+
#
10768+
# The compiler version is pinned here too. ps2exe builds the binary that ships
10769+
# to users, so an unpinned Install-Module let the released artifact change
10770+
# without a commit — the exposure the SHA-pinning policy already closes for
10771+
# actions, including transitive ones.
10772+
Write-SectionHeader "SECTION 209: BUILD METADATA INTEGRITY"
10773+
10774+
try {
10775+
$ciPath209 = Join-Path $script:ModuleRoot '.github\workflows\ci.yml'
10776+
if (Test-Path -LiteralPath $ciPath209) {
10777+
$ci209 = Get-Content -LiteralPath $ciPath209 -Raw
10778+
10779+
# Compiler must be pinned to an exact version, never floating.
10780+
Write-TestResult "Build: ps2exe is pinned to an explicit version" `
10781+
([bool]($ci209 -match "\`$ps2exeVersion\s*=\s*'\d+\.\d+\.\d+'"))
10782+
Write-TestResult "Build: ps2exe install uses -RequiredVersion" `
10783+
([bool]($ci209 -match 'Install-Module ps2exe -RequiredVersion'))
10784+
Write-TestResult "Build: ps2exe install is not unpinned" `
10785+
([bool]($ci209 -notmatch 'Install-Module ps2exe -Force'))
10786+
10787+
# The version resource must actually be populated.
10788+
$p2e209 = [regex]::Match($ci209, '(?s)Invoke-PS2EXE.*?(?=\r?\n\s*\$info\s*=)')
10789+
Write-TestResult "Build: Invoke-PS2EXE call is locatable" $p2e209.Success `
10790+
"regex found no ps2exe invocation — the checks below would pass vacuously"
10791+
$call209 = $p2e209.Value
10792+
foreach ($flag in @('title', 'product', 'company', 'copyright', 'description')) {
10793+
Write-TestResult "Build: EXE metadata sets -$flag" `
10794+
($p2e209.Success -and $call209 -match "-$flag\s+'")
10795+
}
10796+
Write-TestResult "Build: EXE still stamps -Version" `
10797+
($p2e209.Success -and $call209 -match '-Version\s+\$ver')
10798+
10799+
# One identity across every published surface. The EXE's CompanyName
10800+
# must agree with the Gallery manifest rather than drifting on its own.
10801+
$psd209 = Get-Content (Join-Path $script:ModuleRoot 'RackStack.psd1') -Raw
10802+
$psdCompany209 = [regex]::Match($psd209, "CompanyName\s*=\s*'([^']+)'").Groups[1].Value
10803+
$exeCompany209 = [regex]::Match($call209, "-company\s+'([^']+)'").Groups[1].Value
10804+
Write-TestResult "Build: EXE CompanyName matches RackStack.psd1 ('$psdCompany209')" `
10805+
($psdCompany209 -and $exeCompany209 -and $psdCompany209 -eq $exeCompany209) `
10806+
"psd1='$psdCompany209' exe='$exeCompany209'"
10807+
}
10808+
else {
10809+
Write-TestResult "Build: metadata integrity" -Skipped -Message "no .github/workflows in this layout"
10810+
}
10811+
}
10812+
catch {
10813+
Write-TestResult "Build Metadata Integrity Tests" $false $_.Exception.Message
10814+
}
10815+
1075710816
# ============================================================================
1075810817
# FINAL SUMMARY
1075910818
# ============================================================================

0 commit comments

Comments
 (0)