From a54ee58c084cb332995885fcc3f039572e35b9bf Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 23 Apr 2026 17:04:50 -0700 Subject: [PATCH 1/2] {Packaging} Fix azps.ps1 wrapper to propagate exit codes and forward stdin The azps.ps1 wrapper scripts (both the MSI and pip variants) had two minor issues: 1. Exit codes from the underlying python.exe invocation were not propagated, so callers (especially pwsh.exe -Command) would see a successful exit even when the CLI failed. 2. Stdin piped into the wrapper was not forwarded to the Python process, so commands that read from stdin did not receive input. Pipe $input into the child process and add an explicit `exit $LASTEXITCODE` at the end of both wrappers. Add regression tests to test_msi_installation.ps1 to cover both behaviors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build_scripts/windows/scripts/azps.ps1 | 3 +- .../windows/scripts/test_msi_installation.ps1 | 34 +++++++++++++++++++ src/azure-cli/azps.ps1 | 6 ++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/build_scripts/windows/scripts/azps.ps1 b/build_scripts/windows/scripts/azps.ps1 index 7e644639bf6..df536c7b5dc 100644 --- a/build_scripts/windows/scripts/azps.ps1 +++ b/build_scripts/windows/scripts/azps.ps1 @@ -1,5 +1,6 @@ $env:AZ_INSTALLER="MSI" -& "$PSScriptRoot\..\python.exe" -IBm azure.cli $args +$input | & "$PSScriptRoot\..\python.exe" -IBm azure.cli $args +exit $LASTEXITCODE # SIG # Begin signature block # MIInuQYJKoZIhvcNAQcCoIInqjCCJ6YCAQExDzANBglghkgBZQMEAgEFADB5Bgor diff --git a/build_scripts/windows/scripts/test_msi_installation.ps1 b/build_scripts/windows/scripts/test_msi_installation.ps1 index c65bb99676c..a480f856844 100644 --- a/build_scripts/windows/scripts/test_msi_installation.ps1 +++ b/build_scripts/windows/scripts/test_msi_installation.ps1 @@ -74,3 +74,37 @@ if ($installed_version -ne $artifact_version){ # Test bundled pip with extension installation & $az_full_path extension add -n account & $az_full_path self-test + +# --------------------------------------------------------------------- +# azps.ps1 wrapper regression tests +# --------------------------------------------------------------------- +$azps_full_path = Join-Path (Split-Path $az_full_path -Parent) "azps.ps1" +if (-not (Test-Path $azps_full_path)) { + Write-Output "azps.ps1 was not installed at $azps_full_path" + Exit 1 +} + +# Baseline: the wrapper runs and --version returns success. +& $azps_full_path --version +if ($LASTEXITCODE -ne 0) { + Write-Output "azps.ps1 --version returned $LASTEXITCODE (expected 0)" + Exit 1 +} + +# A .ps1 wrapper must propagate non-zero exit codes from the +# child process. Unknown commands exit with code 2; an unfixed wrapper +# would exit 0 here. +& $azps_full_path this-command-does-not-exist-xyz *> $null +if ($LASTEXITCODE -eq 0) { + Write-Output "azps.ps1 did not propagate a failure exit code" + Exit 1 +} + +# When pwsh.exe invokes a .ps1 via +# -Command, the script's exit code is only surfaced if the script +# itself calls 'exit N'. +& pwsh.exe -NoProfile -Command "& '$azps_full_path' this-command-does-not-exist-xyz *> `$null; exit `$LASTEXITCODE" +if ($LASTEXITCODE -eq 0) { + Write-Output "azps.ps1 exit code swallowed when invoked via 'pwsh.exe -Command'" + Exit 1 +} diff --git a/src/azure-cli/azps.ps1 b/src/azure-cli/azps.ps1 index 9b6de0d8d0a..ae91dcd37b4 100644 --- a/src/azure-cli/azps.ps1 +++ b/src/azure-cli/azps.ps1 @@ -2,13 +2,15 @@ $env:AZ_INSTALLER="PIP" if (Test-Path "$PSScriptRoot\python.exe") { # Perfer python.exe in venv - & "$PSScriptRoot\python.exe" -m azure.cli $args + $input | & "$PSScriptRoot\python.exe" -m azure.cli $args } else { # Run system python.exe - python.exe -m azure.cli $args + $input | python.exe -m azure.cli $args } +exit $LASTEXITCODE + # SIG # Begin signature block # MIInqgYJKoZIhvcNAQcCoIInmzCCJ5cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG From f43c343d1418ab10a1ccaa1ef609758b0f96684f Mon Sep 17 00:00:00 2001 From: Aaron R Robinson Date: Thu, 23 Apr 2026 18:01:55 -0700 Subject: [PATCH 2/2] Address PR review: gate $input on ExpectingInput; harden pwsh test - Only pipe $input into python.exe when the wrapper is actually receiving pipeline input ($MyInvocation.ExpectingInput). Otherwise invoke python.exe directly so interactive stdin is inherited from the console instead of being closed at EOF. - Fix pre-existing 'Perfer' typo in src/azure-cli/azps.ps1. - Harden the pwsh.exe -Command regression test: skip if pwsh.exe is unavailable, reset $global:LASTEXITCODE before the call, and also check $? so a command-not-found doesn't false-pass via a stale non-zero $LASTEXITCODE. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build_scripts/windows/scripts/azps.ps1 | 7 ++++++- .../windows/scripts/test_msi_installation.ps1 | 17 ++++++++++++----- src/azure-cli/azps.ps1 | 16 +++++++++++++--- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/build_scripts/windows/scripts/azps.ps1 b/build_scripts/windows/scripts/azps.ps1 index df536c7b5dc..651c1822d4a 100644 --- a/build_scripts/windows/scripts/azps.ps1 +++ b/build_scripts/windows/scripts/azps.ps1 @@ -1,5 +1,10 @@ $env:AZ_INSTALLER="MSI" -$input | & "$PSScriptRoot\..\python.exe" -IBm azure.cli $args +if ($MyInvocation.ExpectingInput) { + $input | & "$PSScriptRoot\..\python.exe" -IBm azure.cli $args +} +else { + & "$PSScriptRoot\..\python.exe" -IBm azure.cli $args +} exit $LASTEXITCODE # SIG # Begin signature block diff --git a/build_scripts/windows/scripts/test_msi_installation.ps1 b/build_scripts/windows/scripts/test_msi_installation.ps1 index a480f856844..a277a31a6ea 100644 --- a/build_scripts/windows/scripts/test_msi_installation.ps1 +++ b/build_scripts/windows/scripts/test_msi_installation.ps1 @@ -102,9 +102,16 @@ if ($LASTEXITCODE -eq 0) { # When pwsh.exe invokes a .ps1 via # -Command, the script's exit code is only surfaced if the script -# itself calls 'exit N'. -& pwsh.exe -NoProfile -Command "& '$azps_full_path' this-command-does-not-exist-xyz *> `$null; exit `$LASTEXITCODE" -if ($LASTEXITCODE -eq 0) { - Write-Output "azps.ps1 exit code swallowed when invoked via 'pwsh.exe -Command'" - Exit 1 +# itself calls 'exit N'. Skip this check if pwsh.exe is unavailable +# so we don't false-pass on a stale $LASTEXITCODE value. +if (Get-Command pwsh.exe -ErrorAction SilentlyContinue) { + $global:LASTEXITCODE = 0 + & pwsh.exe -NoProfile -Command "& '$azps_full_path' this-command-does-not-exist-xyz *> `$null; exit `$LASTEXITCODE" + if (-not $? -or $LASTEXITCODE -eq 0) { + Write-Output "azps.ps1 exit code swallowed when invoked via 'pwsh.exe -Command'" + Exit 1 + } +} +else { + Write-Output "pwsh.exe not found; skipping pwsh.exe -Command regression check" } diff --git a/src/azure-cli/azps.ps1 b/src/azure-cli/azps.ps1 index ae91dcd37b4..3dc41dcb92c 100644 --- a/src/azure-cli/azps.ps1 +++ b/src/azure-cli/azps.ps1 @@ -1,12 +1,22 @@ $env:AZ_INSTALLER="PIP" if (Test-Path "$PSScriptRoot\python.exe") { - # Perfer python.exe in venv - $input | & "$PSScriptRoot\python.exe" -m azure.cli $args + # Prefer python.exe in venv + if ($MyInvocation.ExpectingInput) { + $input | & "$PSScriptRoot\python.exe" -m azure.cli $args + } + else { + & "$PSScriptRoot\python.exe" -m azure.cli $args + } } else { # Run system python.exe - $input | python.exe -m azure.cli $args + if ($MyInvocation.ExpectingInput) { + $input | python.exe -m azure.cli $args + } + else { + python.exe -m azure.cli $args + } } exit $LASTEXITCODE