ssh-handler 1.3.0__tar.gz → 1.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {ssh_handler-1.3.0/ssh_handler.egg-info → ssh_handler-1.4.1}/PKG-INFO +28 -15
  2. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/README.md +27 -14
  3. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/pyproject.toml +1 -1
  4. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/__init__.py +1 -1
  5. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/core.py +63 -14
  6. {ssh_handler-1.3.0 → ssh_handler-1.4.1/ssh_handler.egg-info}/PKG-INFO +28 -15
  7. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/LICENSE +0 -0
  8. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/setup.cfg +0 -0
  9. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/__main__.py +0 -0
  10. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/cli.py +0 -0
  11. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/config.py +0 -0
  12. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/credentials.py +0 -0
  13. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/exceptions.py +0 -0
  14. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/ftp.py +0 -0
  15. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/openssh/OpenSSH-ARM64.zip +0 -0
  16. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/openssh/OpenSSH-Win32.zip +0 -0
  17. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/openssh/OpenSSH-Win64.zip +0 -0
  18. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/pool.py +0 -0
  19. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/pyqt_worker.py +0 -0
  20. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/results.py +0 -0
  21. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/serial_handler.py +0 -0
  22. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/setup_openssh_server.ps1 +0 -0
  23. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler/winrm_bootstrap.py +0 -0
  24. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler.egg-info/SOURCES.txt +0 -0
  25. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler.egg-info/dependency_links.txt +0 -0
  26. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler.egg-info/entry_points.txt +0 -0
  27. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler.egg-info/requires.txt +0 -0
  28. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/ssh_handler.egg-info/top_level.txt +0 -0
  29. {ssh_handler-1.3.0 → ssh_handler-1.4.1}/tests/test_offline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-handler
3
- Version: 1.3.0
3
+ Version: 1.4.1
4
4
  Summary: Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools.
5
5
  Author: ssh-handler contributors
6
6
  License-Expression: MIT
@@ -304,26 +304,39 @@ with SerialHandler("COM5", baudrate=115200, quiet=True) as ser:
304
304
  ser.stream(on_line=print, match=r"login:", stop_on_match=True, save_to="console.log")
305
305
  ```
306
306
 
307
- ### Serial via RDP / jump host (device on the remote target)
308
- `pyserial` only opens a *local* port, so when the serial cable is on the remote
309
- box (reached through your RDP jump), stream it **over SSH** with `serial_stream()`
310
- same live match + save, routed through the jump:
307
+ ### Serial via RDP / SSH (port on a *remote* machine)
308
+ `pyserial` only opens a *local* port, so when the serial port is on a remote
309
+ machine, stream it **over SSH** with `serial_stream()` — same live match + save.
310
+ It auto-detects the OS from the device name: `COM*` → Windows (PowerShell
311
+ SerialPort reader), `/dev/tty*` → Linux (`stty` + `cat`).
312
+
313
+ **Windows COM port on the remote machine** (connect SSH straight to that machine
314
+ — it has sshd from `ssh-handler-setup`):
311
315
 
312
316
  ```python
313
- rdp_box = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser", password="pw")
314
- target = SSHConfig(host="10.120.1.91", username="root", password="pw",
315
- jump_host=rdp_box, host_key_policy="ignore")
317
+ cfg = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser",
318
+ password="pw", host_key_policy="ignore")
319
+ with SSHHandler(cfg, quiet=True) as ssh:
320
+ ssh.serial_write("COM5", "version", baudrate=115200) # write a line
321
+ ssh.serial_stream("COM5", baudrate=115200, # read it live
322
+ on_line=print, match=r"login:|ERROR",
323
+ save_to="com5.log", timeout=120)
324
+ ```
316
325
 
326
+ **Linux device file on a target reached through the jump:**
327
+
328
+ ```python
329
+ target = SSHConfig(host="10.120.1.91", username="root", password="pw",
330
+ jump_host=rdp_box, host_key_policy="ignore")
317
331
  with SSHHandler(target, quiet=True) as ssh:
318
- ssh.serial_write("/dev/ttyUSB0", "version") # write to the device
319
- ssh.serial_stream("/dev/ttyUSB0", baudrate=115200, # read it live
320
- on_line=print, match=r"login:|ERROR",
321
- save_to="device_console.log", timeout=120)
332
+ ssh.serial_stream("/dev/ttyUSB0", baudrate=115200,
333
+ on_line=print, match=r"login:", save_to="ttyusb0.log")
322
334
  ```
323
335
 
324
- `serial_stream()` sets the line speed with `stty` then streams the device for
325
- **Linux targets**. (Windows COM ports on the remote side need a remote helper;
326
- ask if you need that.)
336
+ > Note: on Windows a COM port can't be shareddon't run `serial_write` while a
337
+ > `serial_stream` on the same port is open (`serial_write` opens/writes/closes).
338
+ > If the port is on **your own laptop**, use the local `SerialHandler("COM5")`
339
+ > above instead — no SSH needed.
327
340
 
328
341
  ## File transfer (SFTP / SCP / FTP) via RDP
329
342
 
@@ -278,26 +278,39 @@ with SerialHandler("COM5", baudrate=115200, quiet=True) as ser:
278
278
  ser.stream(on_line=print, match=r"login:", stop_on_match=True, save_to="console.log")
279
279
  ```
280
280
 
281
- ### Serial via RDP / jump host (device on the remote target)
282
- `pyserial` only opens a *local* port, so when the serial cable is on the remote
283
- box (reached through your RDP jump), stream it **over SSH** with `serial_stream()`
284
- same live match + save, routed through the jump:
281
+ ### Serial via RDP / SSH (port on a *remote* machine)
282
+ `pyserial` only opens a *local* port, so when the serial port is on a remote
283
+ machine, stream it **over SSH** with `serial_stream()` — same live match + save.
284
+ It auto-detects the OS from the device name: `COM*` → Windows (PowerShell
285
+ SerialPort reader), `/dev/tty*` → Linux (`stty` + `cat`).
286
+
287
+ **Windows COM port on the remote machine** (connect SSH straight to that machine
288
+ — it has sshd from `ssh-handler-setup`):
285
289
 
286
290
  ```python
287
- rdp_box = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser", password="pw")
288
- target = SSHConfig(host="10.120.1.91", username="root", password="pw",
289
- jump_host=rdp_box, host_key_policy="ignore")
291
+ cfg = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser",
292
+ password="pw", host_key_policy="ignore")
293
+ with SSHHandler(cfg, quiet=True) as ssh:
294
+ ssh.serial_write("COM5", "version", baudrate=115200) # write a line
295
+ ssh.serial_stream("COM5", baudrate=115200, # read it live
296
+ on_line=print, match=r"login:|ERROR",
297
+ save_to="com5.log", timeout=120)
298
+ ```
290
299
 
300
+ **Linux device file on a target reached through the jump:**
301
+
302
+ ```python
303
+ target = SSHConfig(host="10.120.1.91", username="root", password="pw",
304
+ jump_host=rdp_box, host_key_policy="ignore")
291
305
  with SSHHandler(target, quiet=True) as ssh:
292
- ssh.serial_write("/dev/ttyUSB0", "version") # write to the device
293
- ssh.serial_stream("/dev/ttyUSB0", baudrate=115200, # read it live
294
- on_line=print, match=r"login:|ERROR",
295
- save_to="device_console.log", timeout=120)
306
+ ssh.serial_stream("/dev/ttyUSB0", baudrate=115200,
307
+ on_line=print, match=r"login:", save_to="ttyusb0.log")
296
308
  ```
297
309
 
298
- `serial_stream()` sets the line speed with `stty` then streams the device for
299
- **Linux targets**. (Windows COM ports on the remote side need a remote helper;
300
- ask if you need that.)
310
+ > Note: on Windows a COM port can't be shareddon't run `serial_write` while a
311
+ > `serial_stream` on the same port is open (`serial_write` opens/writes/closes).
312
+ > If the port is on **your own laptop**, use the local `SerialHandler("COM5")`
313
+ > above instead — no SSH needed.
301
314
 
302
315
  ## File transfer (SFTP / SCP / FTP) via RDP
303
316
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ssh-handler"
7
- version = "1.3.0"
7
+ version = "1.4.1"
8
8
  description = "Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.8"
@@ -22,7 +22,7 @@ dependencies (PyQt5 / scp) don't break the core package.
22
22
 
23
23
  from __future__ import annotations
24
24
 
25
- __version__ = "1.3.0"
25
+ __version__ = "1.4.1"
26
26
 
27
27
  from .config import SSHConfig, FTPConfig
28
28
  from .core import SSHHandler, ShellSession
@@ -698,36 +698,85 @@ class SSHHandler:
698
698
 
699
699
  return self._guard("stream", _do, safe=safe)
700
700
 
701
+ @staticmethod
702
+ def _ps_encode(script: str) -> str:
703
+ """Encode a PowerShell script for `powershell -EncodedCommand` (UTF-16LE
704
+ base64) - sidesteps all quoting issues over SSH -> cmd -> powershell."""
705
+ import base64
706
+ return base64.b64encode(script.encode("utf-16-le")).decode("ascii")
707
+
701
708
  def serial_stream(self, device: str = "/dev/ttyUSB0", *, baudrate: int = 115200,
702
- on_line=None, on_match=None, match=None,
709
+ mode: str = "auto", on_line=None, on_match=None, match=None,
703
710
  stop_on_match: bool = False, save_to: Optional[str] = None,
704
711
  timeout: Optional[float] = None, stop_event=None,
705
712
  configure: bool = True, safe: Optional[bool] = None):
706
713
  """
707
- Stream a serial device attached to the **remote** host, over SSH — so it
714
+ Stream a serial port attached to the **remote** host, over SSH — so it
708
715
  works through a jump host (laptop -> RDP machine -> target). Same live
709
716
  match + save-to-file model as :meth:`stream`.
710
717
 
711
- Linux target: sets the line speed with ``stty`` then ``cat``s the device.
712
- Use this when the serial cable is plugged into the remote box, not your
713
- laptop (pyserial can only open a *local* port).
718
+ ``mode``:
719
+ * "auto" - "COMx" -> windows, otherwise linux (default)
720
+ * "windows" - read a COM port via a PowerShell SerialPort reader
721
+ * "linux" - set speed with ``stty`` then ``cat`` the device file
714
722
 
715
- >>> ssh.serial_stream("/dev/ttyUSB0", baudrate=115200,
716
- ... match="login:", save_to="console.log", on_line=print)
723
+ >>> # Windows COM port on the remote machine:
724
+ >>> ssh.serial_stream("COM5", baudrate=115200, match="login:",
725
+ ... save_to="console.log", on_line=print)
726
+ >>> # Linux device file on the remote machine:
727
+ >>> ssh.serial_stream("/dev/ttyUSB0", baudrate=115200, on_line=print)
717
728
  """
718
- dev = shlex.quote(device)
719
- if configure:
720
- cmd = (f"stty -F {dev} {int(baudrate)} raw -echo -echoe -echok "
721
- f"2>/dev/null; cat {dev}")
729
+ is_windows = (mode == "windows" or
730
+ (mode == "auto" and device.upper().startswith("COM")))
731
+ if is_windows:
732
+ # Force UTF-8 console output so our UTF-8 reader doesn't get mojibake,
733
+ # and read whatever bytes are available (robust to any line ending)
734
+ # rather than ReadLine (which hangs if the device's EOL differs).
735
+ ps = (
736
+ "$ErrorActionPreference='SilentlyContinue';"
737
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;"
738
+ f"$p=New-Object System.IO.Ports.SerialPort '{device}',{int(baudrate)},"
739
+ "'None',8,'One';"
740
+ "$p.Encoding=[System.Text.Encoding]::UTF8;"
741
+ "$p.Open();"
742
+ "while($true){"
743
+ "if($p.BytesToRead -gt 0){"
744
+ "$s=$p.ReadExisting();[Console]::Out.Write($s);[Console]::Out.Flush()}"
745
+ "else{Start-Sleep -Milliseconds 30}}"
746
+ )
747
+ cmd = f"powershell -NoProfile -EncodedCommand {self._ps_encode(ps)}"
722
748
  else:
723
- cmd = f"cat {dev}"
749
+ dev = shlex.quote(device)
750
+ if configure:
751
+ cmd = (f"stty -F {dev} {int(baudrate)} raw -echo -echoe -echok "
752
+ f"2>/dev/null; cat {dev}")
753
+ else:
754
+ cmd = f"cat {dev}"
724
755
  return self.stream(cmd, on_line=on_line, on_match=on_match, match=match,
725
756
  stop_on_match=stop_on_match, save_to=save_to,
726
757
  timeout=timeout, stop_event=stop_event, safe=safe)
727
758
 
728
- def serial_write(self, device: str, data: str, *, newline: bool = True,
759
+ def serial_write(self, device: str, data: str, *, baudrate: int = 115200,
760
+ mode: str = "auto", newline: bool = True,
729
761
  safe: Optional[bool] = None):
730
- """Write a line to a serial device on the remote host (over SSH/jump)."""
762
+ """
763
+ Write a line to a serial port on the remote host (over SSH/jump).
764
+ Opens, writes, and closes the port (don't run while serial_stream holds
765
+ it open on Windows - the port can't be shared).
766
+ """
767
+ is_windows = (mode == "windows" or
768
+ (mode == "auto" and device.upper().startswith("COM")))
769
+ if is_windows:
770
+ payload = data.replace("'", "''")
771
+ meth = "WriteLine" if newline else "Write"
772
+ ps = (
773
+ f"$p=New-Object System.IO.Ports.SerialPort '{device}',{int(baudrate)},"
774
+ "'None',8,'One';"
775
+ f"$p.Open();$p.{meth}('{payload}');Start-Sleep -Milliseconds 200;"
776
+ "$p.Close()"
777
+ )
778
+ cmd = f"powershell -NoProfile -EncodedCommand {self._ps_encode(ps)}"
779
+ return self.run(cmd, safe=safe)
731
780
  payload = data + ("\n" if newline else "")
732
781
  dev = shlex.quote(device)
733
782
  return self.run(f"printf %s {shlex.quote(payload)} > {dev}", safe=safe)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ssh-handler
3
- Version: 1.3.0
3
+ Version: 1.4.1
4
4
  Summary: Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools.
5
5
  Author: ssh-handler contributors
6
6
  License-Expression: MIT
@@ -304,26 +304,39 @@ with SerialHandler("COM5", baudrate=115200, quiet=True) as ser:
304
304
  ser.stream(on_line=print, match=r"login:", stop_on_match=True, save_to="console.log")
305
305
  ```
306
306
 
307
- ### Serial via RDP / jump host (device on the remote target)
308
- `pyserial` only opens a *local* port, so when the serial cable is on the remote
309
- box (reached through your RDP jump), stream it **over SSH** with `serial_stream()`
310
- same live match + save, routed through the jump:
307
+ ### Serial via RDP / SSH (port on a *remote* machine)
308
+ `pyserial` only opens a *local* port, so when the serial port is on a remote
309
+ machine, stream it **over SSH** with `serial_stream()` — same live match + save.
310
+ It auto-detects the OS from the device name: `COM*` → Windows (PowerShell
311
+ SerialPort reader), `/dev/tty*` → Linux (`stty` + `cat`).
312
+
313
+ **Windows COM port on the remote machine** (connect SSH straight to that machine
314
+ — it has sshd from `ssh-handler-setup`):
311
315
 
312
316
  ```python
313
- rdp_box = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser", password="pw")
314
- target = SSHConfig(host="10.120.1.91", username="root", password="pw",
315
- jump_host=rdp_box, host_key_policy="ignore")
317
+ cfg = SSHConfig(host="10.232.9.22", domain="CORP", username="myuser",
318
+ password="pw", host_key_policy="ignore")
319
+ with SSHHandler(cfg, quiet=True) as ssh:
320
+ ssh.serial_write("COM5", "version", baudrate=115200) # write a line
321
+ ssh.serial_stream("COM5", baudrate=115200, # read it live
322
+ on_line=print, match=r"login:|ERROR",
323
+ save_to="com5.log", timeout=120)
324
+ ```
316
325
 
326
+ **Linux device file on a target reached through the jump:**
327
+
328
+ ```python
329
+ target = SSHConfig(host="10.120.1.91", username="root", password="pw",
330
+ jump_host=rdp_box, host_key_policy="ignore")
317
331
  with SSHHandler(target, quiet=True) as ssh:
318
- ssh.serial_write("/dev/ttyUSB0", "version") # write to the device
319
- ssh.serial_stream("/dev/ttyUSB0", baudrate=115200, # read it live
320
- on_line=print, match=r"login:|ERROR",
321
- save_to="device_console.log", timeout=120)
332
+ ssh.serial_stream("/dev/ttyUSB0", baudrate=115200,
333
+ on_line=print, match=r"login:", save_to="ttyusb0.log")
322
334
  ```
323
335
 
324
- `serial_stream()` sets the line speed with `stty` then streams the device for
325
- **Linux targets**. (Windows COM ports on the remote side need a remote helper;
326
- ask if you need that.)
336
+ > Note: on Windows a COM port can't be shareddon't run `serial_write` while a
337
+ > `serial_stream` on the same port is open (`serial_write` opens/writes/closes).
338
+ > If the port is on **your own laptop**, use the local `SerialHandler("COM5")`
339
+ > above instead — no SSH needed.
327
340
 
328
341
  ## File transfer (SFTP / SCP / FTP) via RDP
329
342
 
File without changes
File without changes