Skip to content

PowerShell-on-Linux: multi-shell Calamari pipeline

Placeholder ppxd requested to merge feat/calamari-powershell-multi-shell into main

Summary

Calamari can now execute PowerShell scripts natively. Operator's .ps1 main script flows through the same RunScriptCommand pipeline as .sh — same variable preamble, extract step, rewriters, convention hooks. PowerShell-on-Linux works through pwsh; Windows falls back to powershell.exe if pwsh isn't installed.

Zero new NuGet dependencies. pwsh / powershell.exe resolved at runtime via PATH probe (same pattern as 1.7.x #352 Tentacle fallback).

Operator UX

Name your file by extension — the right shell runs it:

Extension Syntax Executor
.ps1, .psm1 PowerShell pwsh → fallback powershell.exe on Windows
.sh, no extension, anything else Bash bash (existing)

No new wire literal. No operator opt-in. Same Squid.Calamari run-script invocation handles both.

What's in the box

Layer File
Syntax detector src/Squid.Calamari/Scripting/ScriptSyntaxDetector.cs (new)
PS executor src/Squid.Calamari/Execution/PowerShellScriptExecutor.cs (new)
PS executor adapter src/Squid.Calamari/Scripting/PowerShellScriptExecutorAdapter.cs (new)
PS bootstrapper src/Squid.Calamari/Variables/PowerShellVariableBootstrapper.cs (new)
Syntax-aware bootstrap step src/Squid.Calamari/Commands/RunScriptCommandPipeline.cs — WriteBootstrappedBashScriptStep → WriteBootstrappedScriptStep
Engine registers both adapters src/Squid.Calamari/Scripting/ScriptEngine.cs
Tests (27 new) ScriptSyntaxDetectorTests + PowerShellVariableBootstrapperTests + PowerShellScriptExecutorTests + WriteBootstrappedScriptStepTests

Key parity decisions

  • Same variable-name sanitiser as bash bootstrapper (./-// → _). Operator's My.Var resolves under either shell without renaming.
  • -NoProfile -NonInteractive -File argument shape matches established Squid pattern in SshExecutionStrategy + LocalProcessExecutionStrategy.
  • UTF-8 stdout pin before first $env: line — matches Tentacle's LocalScriptService.PowerShellUtf8Preamble. Without it Windows hosts mangle Chinese/emoji in captured logs.

What's NOT in this PR (scoped out, separate follow-up)

  • Tentacle routing is unchanged. LocalScriptService.IsCalamariCompatible still only returns true for Bash; PowerShell scripts dispatched through Tentacle still route via the direct launcher (PR #352 path). This PR adds Calamari's capability to handle PS; expanding Tentacle dispatch to USE it is a separate small PR.
  • PreDeploy.ps1 / PostDeploy.ps1 convention scripts — convention scripts are still bash-only (file extension hard-coded in ConventionScriptStep). Multi-shell conventions are a separate follow-up.

Test plan

  • Squid.Calamari.Tests: 449/449 (was 422; +27)
  • Squid.UnitTests: 5625/5625 (unchanged)
  • Solution build: 0 errors
  • Pre-merge audit GREEN on 8/8 invariants
  • Tentacle.Tests env-dependent failures unchanged from main baseline (Python/F#/K8s tests need installs locally)
  • Staging: .ps1 deploy with non-ASCII variable values (CJK + emoji) round-tripping through captured logs

Non-breaking guarantee

Surface Status
Existing .sh deploys Byte-identical behaviour — WriteBootstrappedScriptStep picks Bash for everything without .ps1/.psm1
Existing scripts without extension Default Bash (existing behaviour preserved)
Wire literals Unchanged (zero new literals)
Tentacle dispatch routing Unchanged — PS still goes through launcher, not Calamari
IIS PS1 deploy path Unchanged — separate from Calamari
WriteBootstrappedBashScriptStep class name Renamed to WriteBootstrappedScriptStep (internal class — only Calamari + 2 doc-strings in Tentacle updated)
SquidWeb Untouched

Merge request reports

Loading