# RackStack.exe launcher `RackStack.exe` is a small native host for the monolithic script, not a packed or converted copy of it. | File | Purpose | |---|---| | `RackStack.Launcher.cs` | The whole program. Starts Windows PowerShell's own console host in-process and runs the embedded script in it. | | `app.manifest` | Requests elevation (`requireAdministrator`), declares supported Windows versions, opts into long paths. | ## How the executable is built CI compiles the launcher with `csc.exe` from the .NET Framework that is part of Windows, and embeds `RackStack v{version}.ps1` as a plain-text resource named `RackStack.ps1`. There is no third-party compiler, wrapper, or packer in the path from source to binary. The exact command is in [`.github/workflows/ci.yml`](../../.github/workflows/ci.yml), step "Compile RackStack.exe". The embedded script is byte-identical to the monolithic `.ps1` published in the same release, which is Sigstore-signed like every other release artifact. ## Why the launcher exists Earlier releases were produced with ps2exe, which wraps a script in its own PowerShell host implementation. That wrapper is widely reused by malware droppers, so antivirus heuristics scored every RackStack build as a packed script host regardless of what the script did. Running the script under the genuine console host removes the wrapper entirely and gives users the same console behaviour as `powershell.exe`. ## Local build From a Windows PowerShell 5.1 machine, with the monolithic already generated by `sync-to-monolithic.ps1`: ```powershell $ver = '1.123.0' $csc = "$env:WINDIR\Microsoft.NET\Framework64\v4.0.30319\csc.exe" $gac = "$env:WINDIR\Microsoft.Net\assembly\GAC_MSIL" $sma = (Get-ChildItem "$gac\System.Management.Automation" -Recurse -Filter System.Management.Automation.dll | Select-Object -First 1).FullName $chst = (Get-ChildItem "$gac\Microsoft.PowerShell.ConsoleHost" -Recurse -Filter Microsoft.PowerShell.ConsoleHost.dll | Select-Object -First 1).FullName $src = (Get-Content dist\launcher\RackStack.Launcher.cs -Raw).Replace('Version = "0.0.0.0"', "Version = `"$ver.0`"") [IO.File]::WriteAllText("$env:TEMP\RackStack.Launcher.cs", $src) Copy-Item "builds\RackStack v$ver.ps1" "$env:TEMP\RackStack.ps1" -Force & $csc /nologo /target:exe /platform:anycpu /optimize+ /debug- /warnaserror+ ` /r:$sma /r:$chst ` /win32icon:RackStack.ico /win32manifest:dist\launcher\app.manifest ` /resource:"$env:TEMP\RackStack.ps1",RackStack.ps1 ` /out:builds\RackStack.exe "$env:TEMP\RackStack.Launcher.cs" ``` ## Runtime behaviour - Arguments pass straight through: `RackStack.exe -Action HealthCheck -Silent` behaves exactly like running the script with those parameters. - `$PSCommandPath` is empty inside the script (as it was under ps2exe); the script already resolves its own location from the process path. - If the embedded resource is missing (a development build compiled without `/resource:`), the launcher falls back to a `RackStack.ps1` beside the executable and otherwise exits with code 2. - Exit code 3 means Windows PowerShell 5.1 is not installed.