diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee96845..3b2d9ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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)" diff --git a/Changelog.md b/Changelog.md index 5d9de2c..6f31268 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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. diff --git a/Header.ps1 b/Header.ps1 index 41a3ed8..d2f4a39 100644 --- a/Header.ps1 +++ b/Header.ps1 @@ -30,7 +30,7 @@ 7h3 4b1d3r .VERSION - 1.122.3 + 1.122.4 .LAST UPDATED 07/28/2026 diff --git a/Modules/00-Initialization.ps1 b/Modules/00-Initialization.ps1 index dd0a1fa..3752548 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.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. diff --git a/README.md b/README.md index 369882c..bed2376 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ OpenSSF Best Practices codecov PSScriptAnalyzer 0 errors - 5474 structural tests + 5485 structural tests Pester 312 tests SLSA Level 3

diff --git a/RackStack.ps1 b/RackStack.ps1 index 0c69c06..c537a44 100644 --- a/RackStack.ps1 +++ b/RackStack.ps1 @@ -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 diff --git a/RackStack.psd1 b/RackStack.psd1 index 70354f8..2309036 100644 --- a/RackStack.psd1 +++ b/RackStack.psd1 @@ -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' diff --git a/Tests/Run-Tests.ps1 b/Tests/Run-Tests.ps1 index 7f0b88d..8f1968a 100644 --- a/Tests/Run-Tests.ps1 +++ b/Tests/Run-Tests.ps1 @@ -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: @@ -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 # ============================================================================