From b128203e9c506db94544c383efada0a10944f4bc Mon Sep 17 00:00:00 2001 From: twangboy Date: Wed, 5 Mar 2025 11:26:30 -0700 Subject: [PATCH] Pass lastexitcode through to retcode --- changelog/60884.fixed.md | 2 ++ salt/modules/cmdmod.py | 5 +++- .../functional/modules/cmd/test_script.py | 17 +++++++++++ .../functional/utils/test_win_runas.py | 30 +++++++++++++++---- 4 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 changelog/60884.fixed.md diff --git a/changelog/60884.fixed.md b/changelog/60884.fixed.md new file mode 100644 index 00000000000..85f074e7b67 --- /dev/null +++ b/changelog/60884.fixed.md @@ -0,0 +1,2 @@ +Fix an issue with cmd.script in Windows so that the exit code from a script will +be passed through to the retcode of the state diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py index cb7aec28bb9..dd01fb11c87 100644 --- a/salt/modules/cmdmod.py +++ b/salt/modules/cmdmod.py @@ -283,7 +283,10 @@ def _prep_powershell_cmd(win_shell, cmd, encoded_cmd): new_cmd.append("-Command") if isinstance(cmd, list): cmd = " ".join(cmd) - new_cmd.append(f"& {cmd.strip()}") + # We need to append $LASTEXITCODE here to return the actual exit code + # from the script. Otherwise, it will always return 1 on any non-zero + # exit code failure. Issue: #60884 + new_cmd.append(f"& {cmd.strip()}; exit $LASTEXITCODE") elif encoded_cmd: new_cmd.extend(["-EncodedCommand", f"{cmd}"]) else: diff --git a/tests/pytests/functional/modules/cmd/test_script.py b/tests/pytests/functional/modules/cmd/test_script.py index dcdd632fa70..9cd8fa85e08 100644 --- a/tests/pytests/functional/modules/cmd/test_script.py +++ b/tests/pytests/functional/modules/cmd/test_script.py @@ -13,6 +13,17 @@ def cmd(modules): return modules.cmd +@pytest.fixture(scope="module") +def exitcode_script(state_tree): + exit_code = 12345 + script_contents = f""" + Write-Host "Expected exit code: {exit_code}" + exit {exit_code} + """ + with pytest.helpers.temp_file("exit_code.ps1", script_contents, state_tree): + yield exit_code + + @pytest.fixture(params=["powershell", "pwsh"]) def shell(request): """ @@ -85,3 +96,9 @@ def test_windows_script_args_powershell_runas(cmd, shell, account, issue_56195): ) assert ret["stdout"] == password + + +@pytest.mark.skip_unless_on_windows(reason="Minion is not Windows") +def test_windows_script_exitcode(cmd, shell, exitcode_script): + ret = cmd.script("salt://exit_code.ps1", shell=shell, saltenv="base") + assert ret["retcode"] == exitcode_script diff --git a/tests/pytests/functional/utils/test_win_runas.py b/tests/pytests/functional/utils/test_win_runas.py index e06e784df72..b6bdabec375 100644 --- a/tests/pytests/functional/utils/test_win_runas.py +++ b/tests/pytests/functional/utils/test_win_runas.py @@ -18,21 +18,39 @@ def user(): yield account -def test_compound_runas(user): - cmd = "hostname && whoami" +@pytest.mark.parametrize( + "cmd, expected", + [ + ("hostname && whoami", "username"), + ("hostname && echo foo", "foo"), + ("hostname && python --version", "Python"), + ], +) +def test_compound_runas(user, cmd, expected): + if expected == "username": + expected = user.username result = win_runas.runas( cmdLine=cmd, username=user.username, password=user.password, ) - assert user.username in result["stdout"] + assert expected in result["stdout"] -def test_compound_runas_unpriv(user): - cmd = "hostname && whoami" +@pytest.mark.parametrize( + "cmd, expected", + [ + ("hostname && whoami", "username"), + ("hostname && echo foo", "foo"), + ("hostname && python --version", "Python"), + ], +) +def test_compound_runas_unpriv(user, cmd, expected): + if expected == "username": + expected = user.username result = win_runas.runas_unpriv( cmd=cmd, username=user.username, password=user.password, ) - assert user.username in result["stdout"] + assert expected in result["stdout"]