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__ = '2025103002'
 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`): Username.
 45        - `WINRM_PASSWORD` (`str`): Password.
 46        - `WINRM_TRANSPORT` (`str`, optional): Transport (e.g., `'negotiate'`, `'kerberos'`,
 47          `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`). Defaults to `'negotiate'` if unset.
 48        - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`.
 49      (Additional fields may be honored by the underlying libraries if present.)
 50    - **cmd** (`str`): The executable/command to run remotely (native command, not a PowerShell
 51       script block).
 52    - **params** (`list[str]`, optional): Positional arguments passed to the command. Defaults
 53       to `[]`.
 54
 55    ### Returns
 56    - **dict**: A normalized result with:
 57        - `retc` (`int`): Process return code (`0` on success).
 58        - `stdout` (`str`): Captured standard output (text).
 59        - `stderr` (`str`): Captured standard error (text).
 60
 61    ### Behavior
 62    - If **pypsrp** is available, maps `WINRM_TRANSPORT` to an appropriate PSRP auth
 63      and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command
 64      via `Client.execute_cmd()`.
 65    - If pypsrp is unavailable but **pywinrm** is installed, executes via
 66      `Session.run_cmd()`.
 67    - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`.
 68    - If neither backend is present, returns an error indicating that no compatible
 69      remoting library is available.
 70
 71    ### Example
 72    >>> # args provides WINRM_* settings (hostname, creds, transport, etc.)
 73    >>> run_cmd(args, "ipconfig", ["/all"])
 74    {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''}
 75    """
 76    auth = (args.WINRM_USERNAME, args.WINRM_PASSWORD)
 77    if getattr(args, 'WINRM_DOMAIN', None):
 78        auth = (f'{args.WINRM_USERNAME}@{args.WINRM_DOMAIN}', args.WINRM_PASSWORD)
 79
 80    if params is None:
 81        params = []
 82
 83    if HAVE_JEA:
 84        try:
 85            # translate pywinrm transport -> pypsrp auth/ssl/port
 86            _transport = (args.WINRM_TRANSPORT or '').lower()
 87            _auth_map = {
 88                'kerberos': 'kerberos',
 89                'negotiate': 'negotiate',
 90                'ntlm': 'negotiate',   # NTLM is negotiated under "negotiate"
 91                'credssp': 'credssp',
 92                'basic': 'basic',
 93                'plaintext': 'basic',  # basic over HTTP
 94                'ssl': 'basic',        # basic over HTTPS
 95            }
 96            _psrp_auth = _auth_map.get(_transport, 'negotiate')
 97            _use_ssl = (_transport == 'ssl')
 98            _port = 5986 if _use_ssl else 5985
 99
100            # create PSRP client
101            session = Client(
102                server=args.WINRM_HOSTNAME,
103                username=auth[0],
104                password=auth[1],
105                auth=_psrp_auth,
106                ssl=_use_ssl,
107                port=_port,
108                cert_validation=True,
109            )
110
111            # run native command (not PowerShell script)
112            stdout, stderr, rc = session.execute_cmd(cmd, params)
113
114            return {
115                'retc': rc,
116                'stdout': txt.to_text(stdout),
117                'stderr': txt.to_text(stderr),
118            }
119        except Exception as e:
120            return {
121                'retc': 1,
122                'stdout': '',
123                'stderr': txt.exception2text(e),
124            }
125
126    if HAVE_WINRM:
127        try:
128            session = winrm.Session(
129                args.WINRM_HOSTNAME,
130                auth=auth,
131                transport=args.WINRM_TRANSPORT,
132            )
133
134            result = session.run_cmd(cmd, params)
135            return {
136                'retc': result.status_code,
137                'stdout': txt.to_text(result.std_out),
138                'stderr': txt.to_text(result.std_err),
139            }
140        except Exception as e:
141            return {
142                'retc': 1,
143                'stdout': '',
144                'stderr': txt.exception2text(e),
145            }
146
147    # Neither pypsrp nor pywinrm is available
148    return {
149        'retc': 1,
150        'stdout': '',
151        'stderr': 'No compatible remoting library available (pypsrp or pywinrm).',
152    }
153
154
155def run_ps(args, cmd):
156    """
157    Run a PowerShell script/string on a remote Windows host via WinRM/PSRP and
158    return a normalized result dictionary.
159
160    Prefers **pypsrp (PSRP)** if available (best for JEA/PowerShell Remoting);
161    otherwise falls back to **pywinrm**. Authentication, transport, and SSL/port
162    are derived from the provided `args`.
163
164    ### Parameters
165    - **args**: An object (e.g., `argparse.Namespace`) that provides at least:
166        - `WINRM_HOSTNAME` (`str`): Target host or IP.
167        - `WINRM_USERNAME` (`str`): Username.
168        - `WINRM_PASSWORD` (`str`): Password.
169        - `WINRM_TRANSPORT` (`str`, optional): Transport (`'negotiate'`, `'kerberos'`,
170          `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`, etc.). Defaults to `'negotiate'`
171          if unset.
172        - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`.
173      (Additional attributes may be honored by the underlying libraries if present.)
174    - **cmd** (`str`): PowerShell scriptblock/string to execute remotely.
175
176    ### Returns
177    - **dict**: A normalized result with:
178        - `retc` (`int`): Return code (`0` if no PowerShell errors were reported).
179        - `stdout` (`str`): Captured standard output/text from the script.
180        - `stderr` (`str`): Aggregated error/diagnostic output.
181          - For **PSRP**: collects entries from the PowerShell *Error* stream
182            (human-readable via `to_string()` when available).
183          - For **pywinrm**: uses `std_err`; if `retc == 0` and stderr begins with
184            `#< CLIXML`, it is suppressed as benign progress noise.
185
186    ### Behavior
187    - Maps `WINRM_TRANSPORT` to PSRP auth (`kerberos`, `negotiate`, `credssp`, `basic`)
188      and decides SSL/port (5986 for SSL, 5985 otherwise) when using **pypsrp**,
189      then executes via `Client.execute_ps()`.
190    - Falls back to **pywinrm** and executes via `Session.run_ps()` if pypsrp is not available.
191    - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`.
192    - If neither backend is installed, returns an error indicating that no compatible
193      remoting library is available.
194
195    ### Example
196    >>> # args must provide WINRM_* settings (hostname, creds, transport, etc.)
197    >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize")
198    {'retc': 0, 'stdout': 'Name    Id\\r\\n----    --\\r\\n...\\r\\n', 'stderr': ''}
199    """
200    auth = (args.WINRM_USERNAME, args.WINRM_PASSWORD)
201    if getattr(args, 'WINRM_DOMAIN', None):
202        auth = (f'{args.WINRM_USERNAME}@{args.WINRM_DOMAIN}', args.WINRM_PASSWORD)
203
204    if HAVE_JEA:
205        try:
206            # translate pywinrm transport -> pypsrp auth/ssl/port
207            _transport = (args.WINRM_TRANSPORT or '').lower()
208            _auth_map = {
209                'kerberos': 'kerberos',
210                'negotiate': 'negotiate',
211                'ntlm': 'negotiate',   # NTLM is negotiated under "negotiate"
212                'credssp': 'credssp',
213                'basic': 'basic',
214                'plaintext': 'basic',  # basic over HTTP
215                'ssl': 'basic',        # basic over HTTPS
216            }
217            _psrp_auth = _auth_map.get(_transport, 'negotiate')
218            _use_ssl = (_transport == 'ssl')
219            _port = 5986 if _use_ssl else 5985
220
221            # create PSRP client (like in winrm.Session)
222            session = Client(
223                server=args.WINRM_HOSTNAME,
224                username=auth[0],
225                password=auth[1],
226                auth=_psrp_auth,
227                ssl=_use_ssl,
228                port=_port,
229                cert_validation=True,
230            )
231
232            # run PowerShell
233            stdout, streams, had_errors = session.execute_ps(cmd)
234
235            # stdout is already a string; stderr from PSRP error stream(s)
236            stderr_lines = []
237            for err in getattr(streams, 'error', []):
238                # err.to_string() gives a readable message with category/position if available
239                try:
240                    stderr_lines.append(err.to_string())
241                except Exception:
242                    # fallback to message text
243                    msg = getattr(err, 'message', None) or str(err)
244                    stderr_lines.append(str(msg))
245            stderr = '\n'.join(stderr_lines)
246
247            result = {
248                'retc': 0 if not had_errors else 1,
249                'stdout': txt.to_text(stdout),
250                'stderr': txt.to_text(stderr),
251            }
252            return result
253        except Exception as e:
254            return {
255                'retc': 1,
256                'stdout': '',
257                'stderr': txt.exception2text(e),
258            }
259
260    if HAVE_WINRM:
261        try:
262            session = winrm.Session(
263                args.WINRM_HOSTNAME,
264                auth=auth,
265                transport=args.WINRM_TRANSPORT,
266            )
267
268            # run PowerShell
269            result = session.run_ps(cmd)
270
271            result = {
272                'retc': result.status_code,
273                'stdout': txt.to_text(result.std_out),
274                'stderr': txt.to_text(result.std_err),
275            }
276            # if `result.status_code == 0`, ignore stderr that starts with `#< CLIXML`
277            # (it's just progress noise)
278            if result['retc'] == 0 and result['stderr'].startswith('#< CLIXML'):
279                result['stderr'] = ''
280            return result
281        except Exception as e:
282            return {
283                'retc': 1,
284                'stdout': '',
285                'stderr': txt.exception2text(e),
286            }
287
288    # Neither pypsrp nor pywinrm is available
289    return {
290        'retc': 1,
291        'stdout': '',
292        'stderr': 'No compatible remoting library available (pypsrp or pywinrm).',
293    }
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`): Username.
 46        - `WINRM_PASSWORD` (`str`): Password.
 47        - `WINRM_TRANSPORT` (`str`, optional): Transport (e.g., `'negotiate'`, `'kerberos'`,
 48          `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`). Defaults to `'negotiate'` if unset.
 49        - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`.
 50      (Additional fields may be honored by the underlying libraries if present.)
 51    - **cmd** (`str`): The executable/command to run remotely (native command, not a PowerShell
 52       script block).
 53    - **params** (`list[str]`, optional): Positional arguments passed to the command. Defaults
 54       to `[]`.
 55
 56    ### Returns
 57    - **dict**: A normalized result with:
 58        - `retc` (`int`): Process return code (`0` on success).
 59        - `stdout` (`str`): Captured standard output (text).
 60        - `stderr` (`str`): Captured standard error (text).
 61
 62    ### Behavior
 63    - If **pypsrp** is available, maps `WINRM_TRANSPORT` to an appropriate PSRP auth
 64      and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command
 65      via `Client.execute_cmd()`.
 66    - If pypsrp is unavailable but **pywinrm** is installed, executes via
 67      `Session.run_cmd()`.
 68    - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`.
 69    - If neither backend is present, returns an error indicating that no compatible
 70      remoting library is available.
 71
 72    ### Example
 73    >>> # args provides WINRM_* settings (hostname, creds, transport, etc.)
 74    >>> run_cmd(args, "ipconfig", ["/all"])
 75    {'retc': 0, 'stdout': 'Windows IP Configuration\\r\\n...','stderr': ''}
 76    """
 77    auth = (args.WINRM_USERNAME, args.WINRM_PASSWORD)
 78    if getattr(args, 'WINRM_DOMAIN', None):
 79        auth = (f'{args.WINRM_USERNAME}@{args.WINRM_DOMAIN}', args.WINRM_PASSWORD)
 80
 81    if params is None:
 82        params = []
 83
 84    if HAVE_JEA:
 85        try:
 86            # translate pywinrm transport -> pypsrp auth/ssl/port
 87            _transport = (args.WINRM_TRANSPORT or '').lower()
 88            _auth_map = {
 89                'kerberos': 'kerberos',
 90                'negotiate': 'negotiate',
 91                'ntlm': 'negotiate',   # NTLM is negotiated under "negotiate"
 92                'credssp': 'credssp',
 93                'basic': 'basic',
 94                'plaintext': 'basic',  # basic over HTTP
 95                'ssl': 'basic',        # basic over HTTPS
 96            }
 97            _psrp_auth = _auth_map.get(_transport, 'negotiate')
 98            _use_ssl = (_transport == 'ssl')
 99            _port = 5986 if _use_ssl else 5985
100
101            # create PSRP client
102            session = Client(
103                server=args.WINRM_HOSTNAME,
104                username=auth[0],
105                password=auth[1],
106                auth=_psrp_auth,
107                ssl=_use_ssl,
108                port=_port,
109                cert_validation=True,
110            )
111
112            # run native command (not PowerShell script)
113            stdout, stderr, rc = session.execute_cmd(cmd, params)
114
115            return {
116                'retc': rc,
117                'stdout': txt.to_text(stdout),
118                'stderr': txt.to_text(stderr),
119            }
120        except Exception as e:
121            return {
122                'retc': 1,
123                'stdout': '',
124                'stderr': txt.exception2text(e),
125            }
126
127    if HAVE_WINRM:
128        try:
129            session = winrm.Session(
130                args.WINRM_HOSTNAME,
131                auth=auth,
132                transport=args.WINRM_TRANSPORT,
133            )
134
135            result = session.run_cmd(cmd, params)
136            return {
137                'retc': result.status_code,
138                'stdout': txt.to_text(result.std_out),
139                'stderr': txt.to_text(result.std_err),
140            }
141        except Exception as e:
142            return {
143                'retc': 1,
144                'stdout': '',
145                'stderr': txt.exception2text(e),
146            }
147
148    # Neither pypsrp nor pywinrm is available
149    return {
150        'retc': 1,
151        'stdout': '',
152        'stderr': 'No compatible remoting library available (pypsrp or pywinrm).',
153    }

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): Username.
    • WINRM_PASSWORD (str): Password.
    • 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 as user@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 (0 on success).
    • stdout (str): Captured standard output (text).
    • stderr (str): Captured standard error (text).

Behavior

  • If pypsrp is available, maps WINRM_TRANSPORT to an appropriate PSRP auth and chooses SSL/port (5986 for SSL, 5985 otherwise), then executes the command via Client.execute_cmd().
  • If pypsrp is unavailable but pywinrm is installed, executes via Session.run_cmd().
  • 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

>>> # args provides WINRM_* settings (hostname, creds, transport, etc.)
>>> run_cmd(args, "ipconfig", ["/all"])
{'retc': 0, 'stdout': 'Windows IP Configuration\r\n...','stderr': ''}
def run_ps(args, cmd):
156def run_ps(args, cmd):
157    """
158    Run a PowerShell script/string on a remote Windows host via WinRM/PSRP and
159    return a normalized result dictionary.
160
161    Prefers **pypsrp (PSRP)** if available (best for JEA/PowerShell Remoting);
162    otherwise falls back to **pywinrm**. Authentication, transport, and SSL/port
163    are derived from the provided `args`.
164
165    ### Parameters
166    - **args**: An object (e.g., `argparse.Namespace`) that provides at least:
167        - `WINRM_HOSTNAME` (`str`): Target host or IP.
168        - `WINRM_USERNAME` (`str`): Username.
169        - `WINRM_PASSWORD` (`str`): Password.
170        - `WINRM_TRANSPORT` (`str`, optional): Transport (`'negotiate'`, `'kerberos'`,
171          `'ntlm'`, `'credssp'`, `'basic'`, `'ssl'`, etc.). Defaults to `'negotiate'`
172          if unset.
173        - `WINRM_DOMAIN` (`str`, optional): If set, username is sent as `user@domain`.
174      (Additional attributes may be honored by the underlying libraries if present.)
175    - **cmd** (`str`): PowerShell scriptblock/string to execute remotely.
176
177    ### Returns
178    - **dict**: A normalized result with:
179        - `retc` (`int`): Return code (`0` if no PowerShell errors were reported).
180        - `stdout` (`str`): Captured standard output/text from the script.
181        - `stderr` (`str`): Aggregated error/diagnostic output.
182          - For **PSRP**: collects entries from the PowerShell *Error* stream
183            (human-readable via `to_string()` when available).
184          - For **pywinrm**: uses `std_err`; if `retc == 0` and stderr begins with
185            `#< CLIXML`, it is suppressed as benign progress noise.
186
187    ### Behavior
188    - Maps `WINRM_TRANSPORT` to PSRP auth (`kerberos`, `negotiate`, `credssp`, `basic`)
189      and decides SSL/port (5986 for SSL, 5985 otherwise) when using **pypsrp**,
190      then executes via `Client.execute_ps()`.
191    - Falls back to **pywinrm** and executes via `Session.run_ps()` if pypsrp is not available.
192    - On any exception, returns `{'retc': 1, 'stdout': '', 'stderr': <exception text>}`.
193    - If neither backend is installed, returns an error indicating that no compatible
194      remoting library is available.
195
196    ### Example
197    >>> # args must provide WINRM_* settings (hostname, creds, transport, etc.)
198    >>> run_ps(args, "Get-Process | Select-Object -First 1 | Format-Table Name,Id -AutoSize")
199    {'retc': 0, 'stdout': 'Name    Id\\r\\n----    --\\r\\n...\\r\\n', 'stderr': ''}
200    """
201    auth = (args.WINRM_USERNAME, args.WINRM_PASSWORD)
202    if getattr(args, 'WINRM_DOMAIN', None):
203        auth = (f'{args.WINRM_USERNAME}@{args.WINRM_DOMAIN}', args.WINRM_PASSWORD)
204
205    if HAVE_JEA:
206        try:
207            # translate pywinrm transport -> pypsrp auth/ssl/port
208            _transport = (args.WINRM_TRANSPORT or '').lower()
209            _auth_map = {
210                'kerberos': 'kerberos',
211                'negotiate': 'negotiate',
212                'ntlm': 'negotiate',   # NTLM is negotiated under "negotiate"
213                'credssp': 'credssp',
214                'basic': 'basic',
215                'plaintext': 'basic',  # basic over HTTP
216                'ssl': 'basic',        # basic over HTTPS
217            }
218            _psrp_auth = _auth_map.get(_transport, 'negotiate')
219            _use_ssl = (_transport == 'ssl')
220            _port = 5986 if _use_ssl else 5985
221
222            # create PSRP client (like in winrm.Session)
223            session = Client(
224                server=args.WINRM_HOSTNAME,
225                username=auth[0],
226                password=auth[1],
227                auth=_psrp_auth,
228                ssl=_use_ssl,
229                port=_port,
230                cert_validation=True,
231            )
232
233            # run PowerShell
234            stdout, streams, had_errors = session.execute_ps(cmd)
235
236            # stdout is already a string; stderr from PSRP error stream(s)
237            stderr_lines = []
238            for err in getattr(streams, 'error', []):
239                # err.to_string() gives a readable message with category/position if available
240                try:
241                    stderr_lines.append(err.to_string())
242                except Exception:
243                    # fallback to message text
244                    msg = getattr(err, 'message', None) or str(err)
245                    stderr_lines.append(str(msg))
246            stderr = '\n'.join(stderr_lines)
247
248            result = {
249                'retc': 0 if not had_errors else 1,
250                'stdout': txt.to_text(stdout),
251                'stderr': txt.to_text(stderr),
252            }
253            return result
254        except Exception as e:
255            return {
256                'retc': 1,
257                'stdout': '',
258                'stderr': txt.exception2text(e),
259            }
260
261    if HAVE_WINRM:
262        try:
263            session = winrm.Session(
264                args.WINRM_HOSTNAME,
265                auth=auth,
266                transport=args.WINRM_TRANSPORT,
267            )
268
269            # run PowerShell
270            result = session.run_ps(cmd)
271
272            result = {
273                'retc': result.status_code,
274                'stdout': txt.to_text(result.std_out),
275                'stderr': txt.to_text(result.std_err),
276            }
277            # if `result.status_code == 0`, ignore stderr that starts with `#< CLIXML`
278            # (it's just progress noise)
279            if result['retc'] == 0 and result['stderr'].startswith('#< CLIXML'):
280                result['stderr'] = ''
281            return result
282        except Exception as e:
283            return {
284                'retc': 1,
285                'stdout': '',
286                'stderr': txt.exception2text(e),
287            }
288
289    # Neither pypsrp nor pywinrm is available
290    return {
291        'retc': 1,
292        'stdout': '',
293        'stderr': 'No compatible remoting library available (pypsrp or pywinrm).',
294    }

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): Username.
    • WINRM_PASSWORD (str): Password.
    • 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 as user@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 (0 if 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; if retc == 0 and stderr begins with #< CLIXML, it is suppressed as benign progress noise.

Behavior

  • Maps WINRM_TRANSPORT to PSRP auth (kerberos, negotiate, credssp, basic) and decides SSL/port (5986 for SSL, 5985 otherwise) when using pypsrp, then executes via Client.execute_ps().
  • Falls back to pywinrm and executes via Session.run_ps() if pypsrp is not available.
  • 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

>>> # args must provide WINRM_* settings (hostname, creds, transport, etc.)
>>> 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': ''}