lib.winrm
This library collects some Microsoft WinRM related functions.
1#!/usr/bin/env python3 2# -*- coding: utf-8; py-indent-offset: 4 -*- 3# 4# Author: Linuxfabrik GmbH, Zurich, Switzerland 5# Contact: info (at) linuxfabrik (dot) ch 6# https://www.linuxfabrik.ch/ 7# License: The Unlicense, see LICENSE file. 8 9# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.rst 10 11"""This library collects some Microsoft WinRM related functions. 12""" 13 14__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland' 15__version__ = '2025111201' 16 17try: 18 import winrm 19 HAVE_WINRM = True 20except ImportError: 21 HAVE_WINRM = False 22 23try: 24 from pypsrp.client import Client 25 HAVE_JEA = True 26except ImportError: 27 HAVE_JEA = False 28 29from . import txt 30 31 32def run_cmd(args, cmd, params=None): 33 """ 34 Run a native command on a remote Windows host via WinRM/PSRP and return a 35 normalized result dictionary. 36 37 Prefers **pypsrp (PSRP)** if available (for JEA/PowerShell Remoting 38 compatibility); otherwise falls back to **pywinrm**. Authentication, 39 transport and SSL/port selection are derived from the provided `args`. 40 41 ### Parameters 42 - **args**: An object (e.g., `argparse.Namespace`) that provides at least: 43 - `WINRM_HOSTNAME` (`str`): Target host or IP. 44 - `WINRM_USERNAME` (`str`, optional): Username. If `None` or empty when using 45 Kerberos transport, will use existing Kerberos credentials from credential cache 46 (e.g., obtained via `kinit`). 47 - `WINRM_PASSWORD` (`str`, optional): Password. If `None` or empty when using 48 Kerberos transport, will use existing Kerberos credentials from credential cache. 49 - `WINRM_TRANSPORT` (`str`, optional): Transport (e.g., `'negotiate'`, `'kerberos'`, 50 `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`). Defaults to `'negotiate'` if unset. 51 - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`. 52 (Additional fields may be honored by the underlying libraries if present.) 53 - **cmd** (`str`): The executable/command to run remotely (native command, not a PowerShell 54 script block). 55 - **params** (`list[str]`, optional): Positional arguments passed to the command. Defaults 56 to `[]`. 57 58 ### Returns 59 - **dict**: A normalized result with: 60 - `retc` (`int`): Process return code (`0` on success). 61 - `stdout` (`str`): Captured standard output (text). 62 - `stderr` (`str`): Captured standard error (text). 63 64 ### Behavior 65 - If **pypsrp** is available, maps `WINRM_TRANSPORT` to an appropriate PSRP auth 66 and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command 67 via `Client.execute_cmd()`. 68 - If pypsrp is unavailable but **pywinrm** is installed, executes via 69 `Session.run_cmd()`. 70 - For Kerberos authentication: if `WINRM_USERNAME` and `WINRM_PASSWORD` are not provided 71 (or are empty/None), the function will attempt to use existing Kerberos credentials 72 from the credential cache (obtained via `kinit`). 73 - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`. 74 - If neither backend is present, returns an error indicating that no compatible 75 remoting library is available. 76 77 ### Example 78 >>> # With explicit credentials: 79 >>> run_cmd(args, "ipconfig", ["/all"]) 80 {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''} 81 >>> # With Kerberos using kinit credentials (username/password can be None): 82 >>> run_cmd(args, "ipconfig", ["/all"]) 83 {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''} 84 """ 85 # Determine authentication credentials 86 # For Kerberos, allow using existing credentials from kinit 87 username = getattr(args, 'WINRM_USERNAME', None) 88 password = getattr(args, 'WINRM_PASSWORD', None) 89 90 # Check if we should use Kerberos with existing credentials 91 _transport = (getattr(args, 'WINRM_TRANSPORT', None) or '').lower() 92 use_kerberos_cache = (_transport in ['kerberos', 'negotiate']) and (not username or not password) 93 94 if use_kerberos_cache: 95 # Use None for username/password to let Kerberos use credential cache 96 auth = (None, None) 97 else: 98 # Use provided credentials 99 auth = (username, password) 100 if getattr(args, 'WINRM_DOMAIN', None): 101 auth = (f'{username}@{args.WINRM_DOMAIN}', password) 102 103 if params is None: 104 params = [] 105 106 if HAVE_JEA: 107 try: 108 # translate pywinrm transport -> pypsrp auth/ssl/port 109 _auth_map = { 110 'kerberos': 'kerberos', 111 'negotiate': 'negotiate', 112 'ntlm': 'negotiate', # NTLM is negotiated under "negotiate" 113 'credssp': 'credssp', 114 'basic': 'basic', 115 'plaintext': 'basic', # basic over HTTP 116 'ssl': 'basic', # basic over HTTPS 117 } 118 _psrp_auth = _auth_map.get(_transport, 'negotiate') 119 _use_ssl = (_transport == 'ssl') 120 _port = 5986 if _use_ssl else 5985 121 122 # create PSRP client 123 session = Client( 124 server=args.WINRM_HOSTNAME, 125 username=auth[0], 126 password=auth[1], 127 auth=_psrp_auth, 128 ssl=_use_ssl, 129 port=_port, 130 cert_validation=True, 131 ) 132 133 # run native command (not PowerShell script) 134 stdout, stderr, rc = session.execute_cmd(cmd, params) 135 136 return { 137 'retc': rc, 138 'stdout': txt.to_text(stdout), 139 'stderr': txt.to_text(stderr), 140 } 141 except Exception as e: 142 return { 143 'retc': 1, 144 'stdout': '', 145 'stderr': txt.exception2text(e), 146 } 147 148 if HAVE_WINRM: 149 try: 150 session = winrm.Session( 151 args.WINRM_HOSTNAME, 152 auth=auth, 153 transport=args.WINRM_TRANSPORT, 154 ) 155 156 result = session.run_cmd(cmd, params) 157 return { 158 'retc': result.status_code, 159 'stdout': txt.to_text(result.std_out), 160 'stderr': txt.to_text(result.std_err), 161 } 162 except Exception as e: 163 return { 164 'retc': 1, 165 'stdout': '', 166 'stderr': txt.exception2text(e), 167 } 168 169 # Neither pypsrp nor pywinrm is available 170 return { 171 'retc': 1, 172 'stdout': '', 173 'stderr': 'No compatible remoting library available (pypsrp or pywinrm).', 174 } 175 176 177def run_ps(args, cmd): 178 """ 179 Run a PowerShell script/string on a remote Windows host via WinRM/PSRP and 180 return a normalized result dictionary. 181 182 Prefers **pypsrp (PSRP)** if available (best for JEA/PowerShell Remoting); 183 otherwise falls back to **pywinrm**. Authentication, transport, and SSL/port 184 are derived from the provided `args`. 185 186 ### Parameters 187 - **args**: An object (e.g., `argparse.Namespace`) that provides at least: 188 - `WINRM_HOSTNAME` (`str`): Target host or IP. 189 - `WINRM_USERNAME` (`str`, optional): Username. If `None` or empty when using 190 Kerberos transport, will use existing Kerberos credentials from credential cache 191 (e.g., obtained via `kinit`). 192 - `WINRM_PASSWORD` (`str`, optional): Password. If `None` or empty when using 193 Kerberos transport, will use existing Kerberos credentials from credential cache. 194 - `WINRM_TRANSPORT` (`str`, optional): Transport (`'negotiate'`, `'kerberos'`, 195 `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`, etc.). Defaults to `'negotiate'` 196 if unset. 197 - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`. 198 (Additional attributes may be honored by the underlying libraries if present.) 199 - **cmd** (`str`): PowerShell scriptblock/string to execute remotely. 200 201 ### Returns 202 - **dict**: A normalized result with: 203 - `retc` (`int`): Return code (`0` if no PowerShell errors were reported). 204 - `stdout` (`str`): Captured standard output/text from the script. 205 - `stderr` (`str`): Aggregated error/diagnostic output. 206 - For **PSRP**: collects entries from the PowerShell *Error* stream 207 (human-readable via `to_string()` when available). 208 - For **pywinrm**: uses `std_err`; if `retc == 0` and stderr begins with 209 `#< CLIXML`, it is suppressed as benign progress noise. 210 211 ### Behavior 212 - Maps `WINRM_TRANSPORT` to PSRP auth (`kerberos`, `negotiate`, `credssp`, `basic`) 213 and decides SSL/port (5986 for SSL, 5985 otherwise) when using **pypsrp**, 214 then executes via `Client.execute_ps()`. 215 - Falls back to **pywinrm** and executes via `Session.run_ps()` if pypsrp is not available. 216 - For Kerberos authentication: if `WINRM_USERNAME` and `WINRM_PASSWORD` are not provided 217 (or are empty/None), the function will attempt to use existing Kerberos credentials 218 from the credential cache (obtained via `kinit`). 219 - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`. 220 - If neither backend is installed, returns an error indicating that no compatible 221 remoting library is available. 222 223 ### Example 224 >>> # With explicit credentials: 225 >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize") 226 {'retc': 0, 'stdout': 'Name Id\\r\\n---- --\\r\\n...\\r\\n', 'stderr': ''} 227 >>> # With Kerberos using kinit credentials (username/password can be None): 228 >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize") 229 {'retc': 0, 'stdout': 'Name Id\\r\\n---- --\\r\\n...\\r\\n', 'stderr': ''} 230 """ 231 # Determine authentication credentials 232 # For Kerberos, allow using existing credentials from kinit 233 username = getattr(args, 'WINRM_USERNAME', None) 234 password = getattr(args, 'WINRM_PASSWORD', None) 235 236 # Check if we should use Kerberos with existing credentials 237 _transport = (getattr(args, 'WINRM_TRANSPORT', None) or '').lower() 238 use_kerberos_cache = (_transport in ['kerberos', 'negotiate']) and (not username or not password) 239 240 if use_kerberos_cache: 241 # Use None for username/password to let Kerberos use credential cache 242 auth = (None, None) 243 else: 244 # Use provided credentials 245 auth = (username, password) 246 if getattr(args, 'WINRM_DOMAIN', None): 247 auth = (f'{username}@{args.WINRM_DOMAIN}', password) 248 249 if HAVE_JEA: 250 try: 251 # translate pywinrm transport -> pypsrp auth/ssl/port 252 _auth_map = { 253 'kerberos': 'kerberos', 254 'negotiate': 'negotiate', 255 'ntlm': 'negotiate', # NTLM is negotiated under "negotiate" 256 'credssp': 'credssp', 257 'basic': 'basic', 258 'plaintext': 'basic', # basic over HTTP 259 'ssl': 'basic', # basic over HTTPS 260 } 261 _psrp_auth = _auth_map.get(_transport, 'negotiate') 262 _use_ssl = (_transport == 'ssl') 263 _port = 5986 if _use_ssl else 5985 264 265 # create PSRP client (like in winrm.Session) 266 session = Client( 267 server=args.WINRM_HOSTNAME, 268 username=auth[0], 269 password=auth[1], 270 auth=_psrp_auth, 271 ssl=_use_ssl, 272 port=_port, 273 cert_validation=True, 274 ) 275 276 # run PowerShell 277 stdout, streams, had_errors = session.execute_ps(cmd) 278 279 # stdout is already a string; stderr from PSRP error stream(s) 280 stderr_lines = [] 281 for err in getattr(streams, 'error', []): 282 # err.to_string() gives a readable message with category/position if available 283 try: 284 stderr_lines.append(err.to_string()) 285 except Exception: 286 # fallback to message text 287 msg = getattr(err, 'message', None) or str(err) 288 stderr_lines.append(str(msg)) 289 stderr = '\n'.join(stderr_lines) 290 291 result = { 292 'retc': 0 if not had_errors else 1, 293 'stdout': txt.to_text(stdout), 294 'stderr': txt.to_text(stderr), 295 } 296 return result 297 except Exception as e: 298 return { 299 'retc': 1, 300 'stdout': '', 301 'stderr': txt.exception2text(e), 302 } 303 304 if HAVE_WINRM: 305 try: 306 session = winrm.Session( 307 args.WINRM_HOSTNAME, 308 auth=auth, 309 transport=args.WINRM_TRANSPORT, 310 ) 311 312 # run PowerShell 313 result = session.run_ps(cmd) 314 315 result = { 316 'retc': result.status_code, 317 'stdout': txt.to_text(result.std_out), 318 'stderr': txt.to_text(result.std_err), 319 } 320 # if `result.status_code == 0`, ignore stderr that starts with `#< CLIXML` 321 # (it's just progress noise) 322 if result['retc'] == 0 and result['stderr'].startswith('#< CLIXML'): 323 result['stderr'] = '' 324 return result 325 except Exception as e: 326 return { 327 'retc': 1, 328 'stdout': '', 329 'stderr': txt.exception2text(e), 330 } 331 332 # Neither pypsrp nor pywinrm is available 333 return { 334 'retc': 1, 335 'stdout': '', 336 'stderr': 'No compatible remoting library available (pypsrp or pywinrm).', 337 }
def
run_cmd(args, cmd, params=None):
33def run_cmd(args, cmd, params=None): 34 """ 35 Run a native command on a remote Windows host via WinRM/PSRP and return a 36 normalized result dictionary. 37 38 Prefers **pypsrp (PSRP)** if available (for JEA/PowerShell Remoting 39 compatibility); otherwise falls back to **pywinrm**. Authentication, 40 transport and SSL/port selection are derived from the provided `args`. 41 42 ### Parameters 43 - **args**: An object (e.g., `argparse.Namespace`) that provides at least: 44 - `WINRM_HOSTNAME` (`str`): Target host or IP. 45 - `WINRM_USERNAME` (`str`, optional): Username. If `None` or empty when using 46 Kerberos transport, will use existing Kerberos credentials from credential cache 47 (e.g., obtained via `kinit`). 48 - `WINRM_PASSWORD` (`str`, optional): Password. If `None` or empty when using 49 Kerberos transport, will use existing Kerberos credentials from credential cache. 50 - `WINRM_TRANSPORT` (`str`, optional): Transport (e.g., `'negotiate'`, `'kerberos'`, 51 `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`). Defaults to `'negotiate'` if unset. 52 - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`. 53 (Additional fields may be honored by the underlying libraries if present.) 54 - **cmd** (`str`): The executable/command to run remotely (native command, not a PowerShell 55 script block). 56 - **params** (`list[str]`, optional): Positional arguments passed to the command. Defaults 57 to `[]`. 58 59 ### Returns 60 - **dict**: A normalized result with: 61 - `retc` (`int`): Process return code (`0` on success). 62 - `stdout` (`str`): Captured standard output (text). 63 - `stderr` (`str`): Captured standard error (text). 64 65 ### Behavior 66 - If **pypsrp** is available, maps `WINRM_TRANSPORT` to an appropriate PSRP auth 67 and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command 68 via `Client.execute_cmd()`. 69 - If pypsrp is unavailable but **pywinrm** is installed, executes via 70 `Session.run_cmd()`. 71 - For Kerberos authentication: if `WINRM_USERNAME` and `WINRM_PASSWORD` are not provided 72 (or are empty/None), the function will attempt to use existing Kerberos credentials 73 from the credential cache (obtained via `kinit`). 74 - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`. 75 - If neither backend is present, returns an error indicating that no compatible 76 remoting library is available. 77 78 ### Example 79 >>> # With explicit credentials: 80 >>> run_cmd(args, "ipconfig", ["/all"]) 81 {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''} 82 >>> # With Kerberos using kinit credentials (username/password can be None): 83 >>> run_cmd(args, "ipconfig", ["/all"]) 84 {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''} 85 """ 86 # Determine authentication credentials 87 # For Kerberos, allow using existing credentials from kinit 88 username = getattr(args, 'WINRM_USERNAME', None) 89 password = getattr(args, 'WINRM_PASSWORD', None) 90 91 # Check if we should use Kerberos with existing credentials 92 _transport = (getattr(args, 'WINRM_TRANSPORT', None) or '').lower() 93 use_kerberos_cache = (_transport in ['kerberos', 'negotiate']) and (not username or not password) 94 95 if use_kerberos_cache: 96 # Use None for username/password to let Kerberos use credential cache 97 auth = (None, None) 98 else: 99 # Use provided credentials 100 auth = (username, password) 101 if getattr(args, 'WINRM_DOMAIN', None): 102 auth = (f'{username}@{args.WINRM_DOMAIN}', password) 103 104 if params is None: 105 params = [] 106 107 if HAVE_JEA: 108 try: 109 # translate pywinrm transport -> pypsrp auth/ssl/port 110 _auth_map = { 111 'kerberos': 'kerberos', 112 'negotiate': 'negotiate', 113 'ntlm': 'negotiate', # NTLM is negotiated under "negotiate" 114 'credssp': 'credssp', 115 'basic': 'basic', 116 'plaintext': 'basic', # basic over HTTP 117 'ssl': 'basic', # basic over HTTPS 118 } 119 _psrp_auth = _auth_map.get(_transport, 'negotiate') 120 _use_ssl = (_transport == 'ssl') 121 _port = 5986 if _use_ssl else 5985 122 123 # create PSRP client 124 session = Client( 125 server=args.WINRM_HOSTNAME, 126 username=auth[0], 127 password=auth[1], 128 auth=_psrp_auth, 129 ssl=_use_ssl, 130 port=_port, 131 cert_validation=True, 132 ) 133 134 # run native command (not PowerShell script) 135 stdout, stderr, rc = session.execute_cmd(cmd, params) 136 137 return { 138 'retc': rc, 139 'stdout': txt.to_text(stdout), 140 'stderr': txt.to_text(stderr), 141 } 142 except Exception as e: 143 return { 144 'retc': 1, 145 'stdout': '', 146 'stderr': txt.exception2text(e), 147 } 148 149 if HAVE_WINRM: 150 try: 151 session = winrm.Session( 152 args.WINRM_HOSTNAME, 153 auth=auth, 154 transport=args.WINRM_TRANSPORT, 155 ) 156 157 result = session.run_cmd(cmd, params) 158 return { 159 'retc': result.status_code, 160 'stdout': txt.to_text(result.std_out), 161 'stderr': txt.to_text(result.std_err), 162 } 163 except Exception as e: 164 return { 165 'retc': 1, 166 'stdout': '', 167 'stderr': txt.exception2text(e), 168 } 169 170 # Neither pypsrp nor pywinrm is available 171 return { 172 'retc': 1, 173 'stdout': '', 174 'stderr': 'No compatible remoting library available (pypsrp or pywinrm).', 175 }
Run a native command on a remote Windows host via WinRM/PSRP and return a normalized result dictionary.
Prefers pypsrp (PSRP) if available (for JEA/PowerShell Remoting
compatibility); otherwise falls back to pywinrm. Authentication,
transport and SSL/port selection are derived from the provided args.
Parameters
- args: An object (e.g.,
argparse.Namespace) that provides at least:WINRM_HOSTNAME(str): Target host or IP.WINRM_USERNAME(str, optional): Username. IfNoneor empty when using Kerberos transport, will use existing Kerberos credentials from credential cache (e.g., obtained viakinit).WINRM_PASSWORD(str, optional): Password. IfNoneor empty when using Kerberos transport, will use existing Kerberos credentials from credential cache.WINRM_TRANSPORT(str, optional): Transport (e.g.,'negotiate','kerberos','ntlm','credssp','basic','ssl'). Defaults to'negotiate'if unset.WINRM_DOMAIN(str, optional): If set, username is sent asuser@domain. (Additional fields may be honored by the underlying libraries if present.)
- cmd (
str): The executable/command to run remotely (native command, not a PowerShell script block). - params (
list[str], optional): Positional arguments passed to the command. Defaults to[].
Returns
- dict: A normalized result with:
retc(int): Process return code (0on success).stdout(str): Captured standard output (text).stderr(str): Captured standard error (text).
Behavior
- If pypsrp is available, maps
WINRM_TRANSPORTto an appropriate PSRP auth and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command viaClient.execute_cmd(). - If pypsrp is unavailable but pywinrm is installed, executes via
Session.run_cmd(). - For Kerberos authentication: if
WINRM_USERNAMEandWINRM_PASSWORDare not provided (or are empty/None), the function will attempt to use existing Kerberos credentials from the credential cache (obtained viakinit). - On any exception, returns
{'retc': 1, 'stdout': '', 'stderr': <exception text>}. - If neither backend is present, returns an error indicating that no compatible remoting library is available.
Example
>>> # With explicit credentials:
>>> run_cmd(args, "ipconfig", ["/all"])
{'retc': 0, 'stdout': 'Windows IP Configuration\r\n...','stderr': ''}
>>> # With Kerberos using kinit credentials (username/password can be None):
>>> run_cmd(args, "ipconfig", ["/all"])
{'retc': 0, 'stdout': 'Windows IP Configuration\r\n...','stderr': ''}
def
run_ps(args, cmd):
178def run_ps(args, cmd): 179 """ 180 Run a PowerShell script/string on a remote Windows host via WinRM/PSRP and 181 return a normalized result dictionary. 182 183 Prefers **pypsrp (PSRP)** if available (best for JEA/PowerShell Remoting); 184 otherwise falls back to **pywinrm**. Authentication, transport, and SSL/port 185 are derived from the provided `args`. 186 187 ### Parameters 188 - **args**: An object (e.g., `argparse.Namespace`) that provides at least: 189 - `WINRM_HOSTNAME` (`str`): Target host or IP. 190 - `WINRM_USERNAME` (`str`, optional): Username. If `None` or empty when using 191 Kerberos transport, will use existing Kerberos credentials from credential cache 192 (e.g., obtained via `kinit`). 193 - `WINRM_PASSWORD` (`str`, optional): Password. If `None` or empty when using 194 Kerberos transport, will use existing Kerberos credentials from credential cache. 195 - `WINRM_TRANSPORT` (`str`, optional): Transport (`'negotiate'`, `'kerberos'`, 196 `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`, etc.). Defaults to `'negotiate'` 197 if unset. 198 - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`. 199 (Additional attributes may be honored by the underlying libraries if present.) 200 - **cmd** (`str`): PowerShell scriptblock/string to execute remotely. 201 202 ### Returns 203 - **dict**: A normalized result with: 204 - `retc` (`int`): Return code (`0` if no PowerShell errors were reported). 205 - `stdout` (`str`): Captured standard output/text from the script. 206 - `stderr` (`str`): Aggregated error/diagnostic output. 207 - For **PSRP**: collects entries from the PowerShell *Error* stream 208 (human-readable via `to_string()` when available). 209 - For **pywinrm**: uses `std_err`; if `retc == 0` and stderr begins with 210 `#< CLIXML`, it is suppressed as benign progress noise. 211 212 ### Behavior 213 - Maps `WINRM_TRANSPORT` to PSRP auth (`kerberos`, `negotiate`, `credssp`, `basic`) 214 and decides SSL/port (5986 for SSL, 5985 otherwise) when using **pypsrp**, 215 then executes via `Client.execute_ps()`. 216 - Falls back to **pywinrm** and executes via `Session.run_ps()` if pypsrp is not available. 217 - For Kerberos authentication: if `WINRM_USERNAME` and `WINRM_PASSWORD` are not provided 218 (or are empty/None), the function will attempt to use existing Kerberos credentials 219 from the credential cache (obtained via `kinit`). 220 - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`. 221 - If neither backend is installed, returns an error indicating that no compatible 222 remoting library is available. 223 224 ### Example 225 >>> # With explicit credentials: 226 >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize") 227 {'retc': 0, 'stdout': 'Name Id\\r\\n---- --\\r\\n...\\r\\n', 'stderr': ''} 228 >>> # With Kerberos using kinit credentials (username/password can be None): 229 >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize") 230 {'retc': 0, 'stdout': 'Name Id\\r\\n---- --\\r\\n...\\r\\n', 'stderr': ''} 231 """ 232 # Determine authentication credentials 233 # For Kerberos, allow using existing credentials from kinit 234 username = getattr(args, 'WINRM_USERNAME', None) 235 password = getattr(args, 'WINRM_PASSWORD', None) 236 237 # Check if we should use Kerberos with existing credentials 238 _transport = (getattr(args, 'WINRM_TRANSPORT', None) or '').lower() 239 use_kerberos_cache = (_transport in ['kerberos', 'negotiate']) and (not username or not password) 240 241 if use_kerberos_cache: 242 # Use None for username/password to let Kerberos use credential cache 243 auth = (None, None) 244 else: 245 # Use provided credentials 246 auth = (username, password) 247 if getattr(args, 'WINRM_DOMAIN', None): 248 auth = (f'{username}@{args.WINRM_DOMAIN}', password) 249 250 if HAVE_JEA: 251 try: 252 # translate pywinrm transport -> pypsrp auth/ssl/port 253 _auth_map = { 254 'kerberos': 'kerberos', 255 'negotiate': 'negotiate', 256 'ntlm': 'negotiate', # NTLM is negotiated under "negotiate" 257 'credssp': 'credssp', 258 'basic': 'basic', 259 'plaintext': 'basic', # basic over HTTP 260 'ssl': 'basic', # basic over HTTPS 261 } 262 _psrp_auth = _auth_map.get(_transport, 'negotiate') 263 _use_ssl = (_transport == 'ssl') 264 _port = 5986 if _use_ssl else 5985 265 266 # create PSRP client (like in winrm.Session) 267 session = Client( 268 server=args.WINRM_HOSTNAME, 269 username=auth[0], 270 password=auth[1], 271 auth=_psrp_auth, 272 ssl=_use_ssl, 273 port=_port, 274 cert_validation=True, 275 ) 276 277 # run PowerShell 278 stdout, streams, had_errors = session.execute_ps(cmd) 279 280 # stdout is already a string; stderr from PSRP error stream(s) 281 stderr_lines = [] 282 for err in getattr(streams, 'error', []): 283 # err.to_string() gives a readable message with category/position if available 284 try: 285 stderr_lines.append(err.to_string()) 286 except Exception: 287 # fallback to message text 288 msg = getattr(err, 'message', None) or str(err) 289 stderr_lines.append(str(msg)) 290 stderr = '\n'.join(stderr_lines) 291 292 result = { 293 'retc': 0 if not had_errors else 1, 294 'stdout': txt.to_text(stdout), 295 'stderr': txt.to_text(stderr), 296 } 297 return result 298 except Exception as e: 299 return { 300 'retc': 1, 301 'stdout': '', 302 'stderr': txt.exception2text(e), 303 } 304 305 if HAVE_WINRM: 306 try: 307 session = winrm.Session( 308 args.WINRM_HOSTNAME, 309 auth=auth, 310 transport=args.WINRM_TRANSPORT, 311 ) 312 313 # run PowerShell 314 result = session.run_ps(cmd) 315 316 result = { 317 'retc': result.status_code, 318 'stdout': txt.to_text(result.std_out), 319 'stderr': txt.to_text(result.std_err), 320 } 321 # if `result.status_code == 0`, ignore stderr that starts with `#< CLIXML` 322 # (it's just progress noise) 323 if result['retc'] == 0 and result['stderr'].startswith('#< CLIXML'): 324 result['stderr'] = '' 325 return result 326 except Exception as e: 327 return { 328 'retc': 1, 329 'stdout': '', 330 'stderr': txt.exception2text(e), 331 } 332 333 # Neither pypsrp nor pywinrm is available 334 return { 335 'retc': 1, 336 'stdout': '', 337 'stderr': 'No compatible remoting library available (pypsrp or pywinrm).', 338 }
Run a PowerShell script/string on a remote Windows host via WinRM/PSRP and return a normalized result dictionary.
Prefers pypsrp (PSRP) if available (best for JEA/PowerShell Remoting);
otherwise falls back to pywinrm. Authentication, transport, and SSL/port
are derived from the provided args.
Parameters
- args: An object (e.g.,
argparse.Namespace) that provides at least:WINRM_HOSTNAME(str): Target host or IP.WINRM_USERNAME(str, optional): Username. IfNoneor empty when using Kerberos transport, will use existing Kerberos credentials from credential cache (e.g., obtained viakinit).WINRM_PASSWORD(str, optional): Password. IfNoneor empty when using Kerberos transport, will use existing Kerberos credentials from credential cache.WINRM_TRANSPORT(str, optional): Transport ('negotiate','kerberos','ntlm','credssp','basic','ssl', etc.). Defaults to'negotiate'if unset.WINRM_DOMAIN(str, optional): If set, username is sent asuser@domain. (Additional attributes may be honored by the underlying libraries if present.)
- cmd (
str): PowerShell scriptblock/string to execute remotely.
Returns
- dict: A normalized result with:
retc(int): Return code (0if no PowerShell errors were reported).stdout(str): Captured standard output/text from the script.stderr(str): Aggregated error/diagnostic output.- For PSRP: collects entries from the PowerShell Error stream
(human-readable via
to_string()when available). - For pywinrm: uses
std_err; ifretc == 0and stderr begins with#< CLIXML, it is suppressed as benign progress noise.
- For PSRP: collects entries from the PowerShell Error stream
(human-readable via
Behavior
- Maps
WINRM_TRANSPORTto PSRP auth (kerberos,negotiate,credssp,basic) and decides SSL/port (5986 for SSL, 5985 otherwise) when using pypsrp, then executes viaClient.execute_ps(). - Falls back to pywinrm and executes via
Session.run_ps()if pypsrp is not available. - For Kerberos authentication: if
WINRM_USERNAMEandWINRM_PASSWORDare not provided (or are empty/None), the function will attempt to use existing Kerberos credentials from the credential cache (obtained viakinit). - On any exception, returns
{'retc': 1, 'stdout': '', 'stderr': <exception text>}. - If neither backend is installed, returns an error indicating that no compatible remoting library is available.
Example
>>> # With explicit credentials:
>>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize")
{'retc': 0, 'stdout': 'Name Id\r\n---- --\r\n...\r\n', 'stderr': ''}
>>> # With Kerberos using kinit credentials (username/password can be None):
>>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize")
{'retc': 0, 'stdout': 'Name Id\r\n---- --\r\n...\r\n', 'stderr': ''}