diff --git a/changelog/63590.fixed b/changelog/63590.fixed new file mode 100644 index 00000000000..0b7ea3dae4a --- /dev/null +++ b/changelog/63590.fixed @@ -0,0 +1 @@ +When the shell is passed as powershell or pwsh, only wrapper the shell in quotes if cmd.run is running on Windows. When quoted on Linux hosts, this results in an error when the keyword arguments are appended. diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py index 937e7bb3b79..3b4424b02af 100644 --- a/salt/modules/cmdmod.py +++ b/salt/modules/cmdmod.py @@ -256,6 +256,34 @@ def _check_avail(cmd): return bret and wret +def _prep_powershell_cmd(shell, cmd, stack, encoded_cmd): + """ + Prep cmd when shell is powershell + """ + + # If this is running on Windows wrap + # the shell in quotes in case there are + # spaces in the paths. + if salt.utils.platform.is_windows(): + shell = '"{}"'.format(shell) + + # extract_stack() returns a list of tuples. + # The last item in the list [-1] is the current method. + # The third item[2] in each tuple is the name of that method. + if stack[-2][2] == "script": + cmd = '{} -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command {}'.format( + shell, cmd + ) + elif encoded_cmd: + cmd = '{} -NonInteractive -NoProfile -EncodedCommand {}'.format( + shell, cmd + ) + else: + cmd = '{} -NonInteractive -NoProfile -Command "{}"'.format(shell, cmd) + + return cmd + + def _run( cmd, cwd=None, @@ -368,19 +396,7 @@ def _run( # Else just run a Powershell command. stack = traceback.extract_stack(limit=2) - # extract_stack() returns a list of tuples. - # The last item in the list [-1] is the current method. - # The third item[2] in each tuple is the name of that method. - if stack[-2][2] == "script": - cmd = '"{}" -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command {}'.format( - shell, cmd - ) - elif encoded_cmd: - cmd = '"{}" -NonInteractive -NoProfile -EncodedCommand {}'.format( - shell, cmd - ) - else: - cmd = '"{}" -NonInteractive -NoProfile -Command "{}"'.format(shell, cmd) + cmd = _prep_powershell_cmd(shell, cmd, stack, encoded_cmd) # munge the cmd and cwd through the template (cmd, cwd) = _render_cmd(cmd, cwd, template, saltenv, pillarenv, pillar_override) diff --git a/tests/pytests/unit/modules/test_cmdmod.py b/tests/pytests/unit/modules/test_cmdmod.py index c19277645af..abb73c1a01b 100644 --- a/tests/pytests/unit/modules/test_cmdmod.py +++ b/tests/pytests/unit/modules/test_cmdmod.py @@ -302,6 +302,16 @@ def test_powershell(): assert ret == "foo" +@pytest.mark.skip_unless_on_windows +def test_powershell_empty(): + """ + Tests cmd.powershell when the output is an empty string + """ + mock_run = {"pid": 1234, "retcode": 0, "stderr": "", "stdout": ""} + with patch("salt.modules.cmdmod._run", return_value=mock_run): + ret = cmdmod.powershell("Set-ExecutionPolicy RemoteSigned") + assert ret == {} + @pytest.mark.skip_unless_on_windows def test_powershell_empty(): """ @@ -1056,3 +1066,49 @@ def test_runas_env_sudo_group(bundled): popen_mock.call_args_list[0][0][0] == exp_ret ) + +def test_prep_powershell_cmd(): + """ + Tests _prep_powershell_cmd returns correct cmd + """ + stack = [['', '', ''], ['', '', ''], ['', '', '']] + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=False) + assert ret == 'powershell -NonInteractive -NoProfile -Command "$PSVersionTable"' + + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=True) + assert ret == 'powershell -NonInteractive -NoProfile -EncodedCommand $PSVersionTable' + + stack = [['', '', ''], ['', '', 'script'], ['', '', '']] + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=False) + assert ret == 'powershell -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command $PSVersionTable' + + with patch("salt.utils.platform.is_windows", MagicMock(return_value=True)): + stack = [['', '', ''], ['', '', ''], ['', '', '']] + + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=False) + assert ret == '"powershell" -NonInteractive -NoProfile -Command "$PSVersionTable"' + + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=True) + assert ret == '"powershell" -NonInteractive -NoProfile -EncodedCommand $PSVersionTable' + + stack = [['', '', ''], ['', '', 'script'], ['', '', '']] + ret = cmdmod._prep_powershell_cmd(shell="powershell", + cmd="$PSVersionTable", + stack=stack, + encoded_cmd=False) + assert ret == '"powershell" -NonInteractive -NoProfile -ExecutionPolicy Bypass -Command $PSVersionTable'