Skip to content

Commit 815dbe1

Browse files
committed
Try to handle script with spaces
1 parent b8180fc commit 815dbe1

4 files changed

Lines changed: 143 additions & 21 deletions

File tree

salt/modules/cmdmod.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,6 @@ def _run(
729729
if (
730730
python_shell is not True
731731
and shell is not None
732-
# and not salt.utils.platform.is_windows()
733732
and not isinstance(cmd, list)
734733
):
735734
cmd = salt.utils.args.shlex_split(cmd)

salt/platform/win.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,7 +1116,7 @@ def grant_winsta_and_desktop(th):
11161116
"""
11171117
current_sid = win32security.GetTokenInformation(th, win32security.TokenUser)[0]
11181118
# Add permissions for the sid to the current windows station and thread id.
1119-
# This prevents windows error 0xC0000142.
1119+
# This prevents Windows error 0xC0000142.
11201120
winsta = win32process.GetProcessWindowStation()
11211121
set_user_perm(winsta, WINSTA_ALL, current_sid)
11221122
desktop = win32service.GetThreadDesktop(win32api.GetCurrentThreadId())
@@ -1162,8 +1162,8 @@ def CreateProcessWithTokenW(
11621162
ctypes.byref(startupinfo),
11631163
ctypes.byref(process_info),
11641164
)
1165+
winerr = win32api.GetLastError()
11651166
if ret == 0:
1166-
winerr = win32api.GetLastError()
11671167
exc = OSError(win32api.FormatMessage(winerr))
11681168
exc.winerror = winerr
11691169
raise exc
@@ -1341,11 +1341,12 @@ def CreateProcessWithLogonW(
13411341

13421342
def prepend_cmd(win_shell, cmd):
13431343
"""
1344-
Prep cmd when shell is cmd.exe. Always use a command string instead of a list to satisfy
1345-
both CreateProcess and CreateProcessWithToken.
1344+
Prep cmd when shell is cmd.exe. Always use a command string instead of a
1345+
list to satisfy both CreateProcess and CreateProcessWithToken.
13461346
1347-
cmd must be double-quoted to ensure proper handling of space characters. The first opening
1348-
quote and the closing quote are stripped automatically by the Win32 API.
1347+
cmd must be double-quoted to ensure proper handling of space characters.
1348+
The first opening quote and the closing quote are stripped automatically by
1349+
the Win32 API.
13491350
"""
13501351
if isinstance(cmd, (list, tuple)):
13511352
args = subprocess.list2cmdline(cmd)

salt/utils/win_runas.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def split_username(username):
6868
return str(user_name), str(domain)
6969

7070

71-
def create_env(user_token, inherit, timeout=1):
71+
def create_env(username, user_token, inherit=False, timeout=1):
7272
"""
7373
CreateEnvironmentBlock might fail when we close a login session and then
7474
try to re-open one very quickly. Run the method multiple times to work
@@ -79,11 +79,16 @@ def create_env(user_token, inherit, timeout=1):
7979
exc = None
8080
while True:
8181
try:
82-
env = win32profile.CreateEnvironmentBlock(user_token, False)
82+
profile_info_dict = {"UserName": username}
83+
profile_handle = win32profile.LoadUserProfile(user_token, profile_info_dict)
84+
env = win32profile.CreateEnvironmentBlock(user_token, inherit)
8385
except pywintypes.error as exc:
8486
pass
8587
else:
8688
break
89+
finally:
90+
win32profile.UnloadUserProfile(user_token, profile_handle)
91+
8792
if time.time() - start > timeout:
8893
break
8994
if env is not None:
@@ -95,8 +100,8 @@ def create_env(user_token, inherit, timeout=1):
95100
def runas(cmd, username, password=None, cwd=None):
96101
"""
97102
Run a command as another user. If the process is running as an admin or
98-
system account this method does not require a password. Other non
99-
privileged accounts need to provide a password for the user to runas.
103+
system account, this method does not require a password. Other
104+
non-privileged accounts need to provide a password for the user to "runas".
100105
Commands are run in with the highest level privileges possible for the
101106
account provided.
102107
"""
@@ -211,7 +216,7 @@ def runas(cmd, username, password=None, cwd=None):
211216
)
212217

213218
# Create the environment for the user
214-
env = create_env(user_token, False)
219+
env = create_env(username, user_token, inherit=False)
215220

216221
hProcess = None
217222
try:
@@ -232,7 +237,7 @@ def runas(cmd, username, password=None, cwd=None):
232237
dwProcessId = process_info.dwProcessId
233238
dwThreadId = process_info.dwThreadId
234239

235-
# We don't use these so let's close the handle
240+
# We don't use these, so let's close the handle
236241
salt.platform.win.kernel32.CloseHandle(stdin_write.handle)
237242
salt.platform.win.kernel32.CloseHandle(stdout_write.handle)
238243
salt.platform.win.kernel32.CloseHandle(stderr_write.handle)

tests/pytests/functional/modules/cmd/test_script.py

Lines changed: 125 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def account():
1818

1919

2020
@pytest.fixture
21-
def echo_script(state_tree):
21+
def echo_script_contents():
2222
if salt.utils.platform.is_windows():
2323
file_name = "echo_script.bat"
2424
contents = dedent(
@@ -39,12 +39,25 @@ def echo_script(state_tree):
3939
echo "a: $a, b: $b"
4040
"""
4141
)
42+
return file_name, contents
43+
44+
45+
@pytest.fixture
46+
def echo_script(state_tree, echo_script_contents):
47+
file_name, contents = echo_script_contents
4248
with pytest.helpers.temp_file(file_name, contents, state_tree / "echo-script"):
4349
yield file_name
4450

4551

4652
@pytest.fixture
47-
def pipe_script(state_tree):
53+
def echo_script_with_space(state_tree, echo_script_contents):
54+
file_name, contents = echo_script_contents
55+
with pytest.helpers.temp_file(file_name, contents, state_tree / "echo script"):
56+
yield file_name
57+
58+
59+
@pytest.fixture
60+
def pipe_script_contents():
4861
if salt.utils.platform.is_windows():
4962
file_name = "pipe_script.bat"
5063
contents = dedent(
@@ -69,6 +82,12 @@ def pipe_script(state_tree):
6982
fi
7083
"""
7184
)
85+
return file_name, contents
86+
87+
88+
@pytest.fixture
89+
def pipe_script(pipe_script_contents, state_tree):
90+
file_name, contents = pipe_script_contents
7291
with pytest.helpers.temp_file(file_name, contents, state_tree / "echo-script") as f:
7392
if not salt.utils.platform.is_windows():
7493
current_perms = f.stat().st_mode
@@ -78,6 +97,22 @@ def pipe_script(state_tree):
7897
yield f
7998

8099

100+
@pytest.fixture
101+
def pipe_script_with_space(pipe_script_contents, state_tree):
102+
file_name, contents = pipe_script_contents
103+
with pytest.helpers.temp_directory() as temp_path:
104+
temp_file_path = temp_path / "dir with spaces" / file_name
105+
temp_file_path.parent.mkdir(parents=True)
106+
assert temp_file_path.parent.exists()
107+
temp_file_path.write_text(contents)
108+
if not salt.utils.platform.is_windows():
109+
current_perms = temp_file_path.stat().st_mode
110+
new_perms = current_perms | stat.S_IXUSR
111+
temp_file_path.chmod(new_perms)
112+
temp_file_path.chmod(0o755)
113+
yield temp_file_path
114+
115+
81116
@pytest.mark.parametrize(
82117
"args, expected",
83118
[
@@ -87,7 +122,7 @@ def pipe_script(state_tree):
87122
(["foo foo", "bar bar"], "a: foo foo, b: bar bar"),
88123
],
89124
)
90-
def test_echo(modules, echo_script, args, expected):
125+
def test_script_args(modules, echo_script, args, expected):
91126
"""
92127
Test argument processing with a batch script
93128
"""
@@ -105,7 +140,25 @@ def test_echo(modules, echo_script, args, expected):
105140
(["foo foo", "bar bar"], "a: foo foo, b: bar bar"),
106141
],
107142
)
108-
def test_echo_runas(modules, account, echo_script, args, expected):
143+
def test_script_args_with_space(modules, echo_script_with_space, args, expected):
144+
"""
145+
Test argument processing with a batch script
146+
"""
147+
script = f"salt://echo script/{echo_script_with_space}"
148+
result = modules.cmd.script(script, args=args)
149+
assert result["stdout"] == expected
150+
151+
152+
@pytest.mark.parametrize(
153+
"args, expected",
154+
[
155+
("foo bar", "a: foo, b: bar"),
156+
('foo "bar bar"', "a: foo, b: bar bar"),
157+
(["foo", "bar"], "a: foo, b: bar"),
158+
(["foo foo", "bar bar"], "a: foo foo, b: bar bar"),
159+
],
160+
)
161+
def test_script_args_runas(modules, account, echo_script, args, expected):
109162
"""
110163
Test argument processing with a batch/bash script and runas
111164
"""
@@ -119,7 +172,30 @@ def test_echo_runas(modules, account, echo_script, args, expected):
119172
assert result["stdout"] == expected
120173

121174

122-
def test_pipe_run_python_shell_true(modules, pipe_script):
175+
@pytest.mark.parametrize(
176+
"args, expected",
177+
[
178+
("foo bar", "a: foo, b: bar"),
179+
('foo "bar bar"', "a: foo, b: bar bar"),
180+
(["foo", "bar"], "a: foo, b: bar"),
181+
(["foo foo", "bar bar"], "a: foo foo, b: bar bar"),
182+
],
183+
)
184+
def test_script_args_runas_with_space(modules, account, echo_script_with_space, args, expected):
185+
"""
186+
Test argument processing with a batch/bash script and runas
187+
"""
188+
script = f"salt://echo script/{echo_script_with_space}"
189+
result = modules.cmd.script(
190+
script,
191+
args=args,
192+
runas=account.username,
193+
password=account.password,
194+
)
195+
assert result["stdout"] == expected
196+
197+
198+
def test_run_pipe_python_shell_true(modules, pipe_script):
123199
if salt.utils.platform.is_windows():
124200
cmd = f'{str(pipe_script)} | find /c /v ""'
125201
else:
@@ -128,7 +204,7 @@ def test_pipe_run_python_shell_true(modules, pipe_script):
128204
assert result == "1"
129205

130206

131-
def test_pipe_run_python_shell_false(modules, pipe_script):
207+
def test_run_pipe_python_shell_false(modules, pipe_script):
132208
if salt.utils.platform.is_windows():
133209
cmd = f'{str(pipe_script)} | find /c /v ""'
134210
# Behavior is different on Windows, I think it has to do with how cmd
@@ -141,7 +217,7 @@ def test_pipe_run_python_shell_false(modules, pipe_script):
141217
assert result == expected
142218

143219

144-
def test_pipe_run_default(modules, pipe_script):
220+
def test_run_pipe_default(modules, pipe_script):
145221
if salt.utils.platform.is_windows():
146222
cmd = f'{str(pipe_script)} | find /c /v ""'
147223
else:
@@ -153,7 +229,7 @@ def test_pipe_run_default(modules, pipe_script):
153229
assert result == "1"
154230

155231

156-
def test_pipe_run_shell(modules, pipe_script):
232+
def test_run_pipe_shell(modules, pipe_script):
157233
if salt.utils.platform.is_windows():
158234
cmd = f'{str(pipe_script)} | find /c /v ""'
159235
shell = "cmd"
@@ -165,3 +241,44 @@ def test_pipe_run_shell(modules, pipe_script):
165241
# test suite, the value is empty
166242
result = modules.cmd.run(cmd, shell=shell, __pub_jid="test")
167243
assert result == "1"
244+
245+
246+
def test_run_spaces(modules, pipe_script_with_space):
247+
cmd = f"{str(pipe_script_with_space)}"
248+
result = modules.cmd.run(cmd)
249+
assert result == "fine"
250+
251+
252+
def test_run_spaces_runas(modules, pipe_script_with_space, account):
253+
cmd = f"{str(pipe_script_with_space)}"
254+
result = modules.cmd.run(
255+
cmd,
256+
runas=account.username,
257+
password=account.password,
258+
)
259+
assert result == "fine"
260+
261+
262+
def test_script_pipe_spaces(modules, pipe_script_with_space):
263+
cmd = f"{str(pipe_script_with_space)}"
264+
if salt.utils.platform.is_windows():
265+
args = '| find /c /v ""'
266+
else:
267+
args = "| wc -l"
268+
result = modules.cmd.script(cmd, args=args)
269+
assert result["stdout"] == "1"
270+
271+
272+
def test_script_pipe_spaces_runas(modules, pipe_script_with_space, account):
273+
cmd = f"{str(pipe_script_with_space)}"
274+
if salt.utils.platform.is_windows():
275+
args = '| find /c /v ""'
276+
else:
277+
args = "| wc -l"
278+
result = modules.cmd.script(
279+
cmd,
280+
args=args,
281+
runas=account.username,
282+
password=account.password,
283+
)
284+
assert result["stdout"] == "1"

0 commit comments

Comments
 (0)