Skip to content

Commit 6ee2ec2

Browse files
committed
Add Windows Shell Support
1 parent 6f8c65a commit 6ee2ec2

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

README.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ Optional arguments:
100100
set to their default values:
101101
``cwd``, ``update_env``, and ``store_pid``.
102102

103+
- ``spur.ssh.ShellTypes.windows`` -- the Windows cmd.exe shell. Supports all
104+
documented features. Undocumented features may not work.
105+
103106
* ``look_for_private_keys`` -- by default, Spur will search for discoverable
104107
private key files in ``~/.ssh/``.
105108
Set to ``False`` to disable this behaviour.
@@ -238,6 +241,9 @@ Has the following methods:
238241
``RunProcessError`` if the return code is not zero and
239242
``shell.spawn`` was not called with ``allow_error=True``.
240243
* ``send_signal(signal)`` -- sends the process the signal ``signal``.
244+
Only available if ``store_pid`` was set to ``True`` when calling
245+
``spawn``. (Unavailable in Windows Shell)
246+
* ``kill`` -- kills the process.
241247
Only available if ``store_pid`` was set to ``True`` when calling
242248
``spawn``.
243249

spur/ssh.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,39 @@ class MissingHostKey(object):
4343
auto_add = paramiko.AutoAddPolicy()
4444
accept = AcceptParamikoPolicy()
4545

46+
class WindowsShellType(object):
47+
supports_which = True
48+
49+
def generate_run_command(self, command_args, store_pid,
50+
cwd=None, update_env={}, new_process_group=False):
51+
52+
if new_process_group:
53+
raise UnsupportedArgumentError("'new_process_group' is not supported when using a windows shell")
54+
55+
commands = []
56+
57+
if store_pid:
58+
commands.append("powershell (Get-WmiObject Win32_Process -Filter ProcessId=$PID).ParentProcessId")
59+
60+
if cwd is not None:
61+
commands.append("cd {0} 2>&1 || ( echo. & echo spur-cd: %errorlevel% & exit 1 )".format(win_escape_sh(cwd)))
62+
commands.append("echo. & echo spur-cd: 0")
63+
64+
update_env_commands = [
65+
"SET {0}={1}".format(key, value)
66+
for key, value in _iteritems(update_env)
67+
]
68+
commands += update_env_commands
69+
commands.append(
70+
"( (powershell Get-Command {0} > nul 2>&1) && echo 0) || (echo %errorlevel% & exit 1)".format(
71+
win_escape_sh(command_args[0])))
72+
73+
commands.append(" ".join(command_args))
74+
return " & ".join(commands)
75+
76+
def generate_kill_command(self, pid):
77+
return "taskkill /F /PID {0}".format(pid)
78+
4679

4780
class MinimalShellType(object):
4881
supports_which = False
@@ -64,6 +97,8 @@ def generate_run_command(self, command_args, store_pid,
6497

6598
return " ".join(map(escape_sh, command_args))
6699

100+
def generate_kill_command(self, pid):
101+
return "kill {0}".format(pid)
67102

68103
def _unsupported_argument_error(self, name):
69104
return UnsupportedArgumentError("'{0}' is not supported when using a minimal shell".format(name))
@@ -99,6 +134,9 @@ def generate_run_command(self, command_args, store_pid,
99134
commands.append(command)
100135
return "; ".join(commands)
101136

137+
def generate_kill_command(self, pid):
138+
return "kill {0}".format(pid)
139+
102140
def _generate_which_commands(self, command):
103141
which_commands = ["command -v {0}", "which {0}"]
104142
return (
@@ -113,6 +151,7 @@ def _generate_which_command(self, which, command):
113151
class ShellTypes(object):
114152
minimal = MinimalShellType()
115153
sh = ShShellType()
154+
windows = WindowsShellType()
116155

117156

118157
class SshShell(object):
@@ -141,7 +180,7 @@ def __init__(self,
141180
self._password = password
142181
self._private_key_file = private_key_file
143182
self._client = None
144-
self._connect_timeout = connect_timeout if not None else _ONE_MINUTE
183+
self._connect_timeout = connect_timeout if connect_timeout is not None else _ONE_MINUTE
145184
self._look_for_private_keys = look_for_private_keys
146185
self._load_system_host_keys = load_system_host_keys
147186
self._closed = False
@@ -358,6 +397,10 @@ def escape_sh(value):
358397
return "'" + value.replace("'", "'\\''") + "'"
359398

360399

400+
def win_escape_sh(value):
401+
return '"' + value + '"'
402+
403+
361404
class SshProcess(object):
362405
def __init__(self, channel, allow_error, process_stdout, stdout, stderr, encoding, shell):
363406
self._channel = channel
@@ -382,6 +425,9 @@ def stdin_write(self, value):
382425
def send_signal(self, signal):
383426
self._shell.run(["kill", "-{0}".format(signal), str(self.pid)])
384427

428+
def kill(self):
429+
self._shell.run([self._shell._shell_type.generate_kill_command(self.pid)])
430+
385431
def wait_for_result(self):
386432
if self._result is None:
387433
self._result = self._generate_result()

0 commit comments

Comments
 (0)