# pywinrm: talking WinRM from Python, and the transport that decides what you can log into

> pywinrm is a Python client for the Windows Remote Management service, used by Ansible for its Windows support, with a high level Session API and a low level Protocol API underneath. This looks at how the transports differ in practice, how the two encryption layers work, and what the release history says about depending on it.

**diyan/pywinrm** — Python library for Windows Remote Management (WinRM)

- Repository: https://github.com/diyan/pywinrm
- Stars: 1,142 · Forks: 274
- Language: Python
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/diyan-pywinrm

## Run a command on a Windows box from any machine running Python

pywinrm is a Python client for the Windows Remote Management service. It allows you to invoke commands on target Windows machines from any machine that can run Python, and the point of it is the second half of that sentence: the machine you run it from does not have to be Windows.

The management tasks it supports are listed in the project documentation: running batch scripts, PowerShell scripts, and fetching WMI variables. WMI is the Windows Management Instrumentation interface, so an inventory tool that wants CPU, memory or disk facts from a remote host gets them through the same channel it uses to run a command.

The audience is system administrators and the tool builders around them. The proof of that is in the project's own description of where it is used: Ansible uses it for Windows support. If your organisation already runs Ansible against Windows hosts, pywinrm is not an alternative to that, it is the layer underneath it, and adopting it directly means reimplementing the parts of Ansible you would have got for free.

The library is MIT licensed, written in Python, and the package metadata declares `requires-python = ">=3.8"` with classifiers covering Python 3.8 through 3.12 and PyPy. The last push to the repository was on 2026-08-04.

## Session for the common case, Protocol for everything else

The API has two layers, and picking the right one is the first decision.

The high level is `winrm.Session`. You construct it with a target and an authentication tuple, then call `run_cmd` for a command and `run_ps` for a PowerShell script:

```python
import winrm

s = winrm.Session('windows-host.example.com', auth=('john.smith', 'secret'))
r = s.run_cmd('ipconfig', ['/all'])
```

The result object carries `status_code`, `std_out` and `std_err`, which is the shape any automation wants, since a non-zero status code is a command failure rather than an exception.

The low level is `winrm.protocol.Protocol`, where you manage the shell yourself:

```python
from winrm.protocol import Protocol

p = Protocol(
    endpoint='https://windows-host:5986/wsman',
    transport='ntlm',
    username=r'somedomain\someuser',
    password='secret',
    server_cert_validation='ignore')
shell_id = p.open_shell()
command_id = p.run_command(shell_id, 'ipconfig', ['/all'])
std_out, std_err, status_code = p.get_command_output(shell_id, command_id)
p.cleanup_command(shell_id, command_id)
p.close_shell(shell_id)
```

The difference is lifetime and control. A Session manages the shell for you, while the Protocol API lets you keep a shell open across several commands, which is what you want for a sequence of related steps on a slow link. The `cleanup_command` and `close_shell` calls in the example are not decoration, because a WinRM host has a limit on open shells and a leaked one will block the next connection.

One convenience worth knowing: the library guesses the endpoint URL from the target string, mapping a bare hostname to port 5985, and honouring an explicit port, scheme or `/wsman` path if you give one.

## Installing with pip, and the two extras that need system packages

The base install is a single command, and it brings basic, certificate and NTLM authentication with it:

```bash
$ pip install pywinrm
```

Kerberos needs system libraries as well as the Python package, and the documentation gives the package sets per distribution:

```bash
# for Debian/Ubuntu/etc:
$ sudo apt-get install gcc python3-dev libkrb5-dev
$ pip install pywinrm[kerberos]
```

```bash
# for RHEL/CentOS/etc:
$ sudo dnf install gcc krb5-devel krb5-workstation python3-devel
$ pip install pywinrm[kerberos]
```

CredSSP is simpler, since it needs only the Python side:

```bash
$ pip install pywinrm[credssp]
```

That mirrors the manifest. The base dependencies are `requests >= 2.9.1`, `requests_ntlm >= 1.1.0` and `xmltodict`, with `credssp` pulling in `requests-credssp >= 1.0.0` and `kerberos` pulling in `pykerberos` on non-Windows platforms or `winkerberos` on Windows. The Kerberos extra being platform dependent is a detail worth noting if you deploy the same code on both.

The requirements list in the documentation also states the runtime prerequisites: Linux, Mac OS X or Windows, and CPython 3.8+ or PyPy3.

## The transport parameter is the real configuration decision

The `transport` parameter accepts several values, and the differences are not cosmetic. They determine which accounts you can authenticate as and over which connection.

`basic` works only for local Windows accounts, not domain accounts, and the credentials are base64 encoded when sent. `plaintext` is described as the same as basic. `certificate` authenticates with a certificate that is mapped to a local Windows account on the server. `ssl` uses a certificate when combined with `cert_pem` and `cert_key_pem`, and otherwise reverts to basic auth over HTTPS.

`ntlm` works for both domain and local accounts, and `kerberos` works for domain accounts but only when the client is in the same domain as the server and the required dependencies are installed. The documentation adds that a Kerberos ticket has to be initialised outside pywinrm using the `kinit` command, which is a genuine operational wrinkle: your code cannot obtain the ticket itself.

`credssp` works for both domain and local accounts and allows double hop authentication, which is the capability that matters when your command has to authenticate onward to another Windows machine. Its constraint is explicit: it only works over an HTTPS endpoint and not HTTP.

The low level example in the documentation shows the domain account form, where the username is written as a raw string with a backslash, `r'somedomain\someuser'`, because a normal Python string would interpret the backslash as an escape.

## Two encryption layers, and a server that refuses plaintext by default

The security model has two independent layers, and conflating them is how people end up with a connection they thought was encrypted and was not.

By default, WinRM will not accept unencrypted communication with a client. There are two ways to enable encrypted communication with pywinrm. The first is to use an HTTPS endpoint instead of HTTP, which the documentation recommends, because it encrypts all the data sent to the server including all headers, works securely with all auth types, and can properly verify remote host identity when used with certificates signed by a verifiable certificate authority.

The second is to use NTLM, Kerberos or CredSSP as the transport auth and set the `message_encryption` argument to `auto` or `always`. This uses the authentication GSS-API Wrap and Unwrap methods to encrypt the message contents, and the documentation is precise that this form of encryption is independent of the transport layer, with the strength varying by authentication type, NTLM generally being the weakest and CredSSP the strongest.

The `message_encryption` argument has three values. `auto` is the default and uses message encryption only if it is available for the auth method and HTTPS is not used. `never` will never use message encryption even when not over HTTPS. `always` will always use it even over HTTPS, and fails if the selected auth method cannot support it. If you set `always` with a transport that does not support message encryption, such as basic auth or an old pykerberos without encryption support, pywinrm will raise an exception.

And the last line of defence is on the server. A default-configured WinRM server will automatically reject requests from pywinrm unless one of those two routes is in place.

## Where pywinrm is the wrong tool

The limits are concrete and they are visible in the transport list.

The account model is the first. Basic auth reaches only local Windows accounts. Kerberos reaches only domain accounts, only from inside the same domain, and only with a ticket you obtain separately. If your automation runs from a Linux jump host in a different domain, neither of those will work for you, and the library will not paper over it. CredSSP covers the widest ground, including double hop, but only over HTTPS, so it demands a properly configured listener with a certificate.

The second limit is that pywinrm is a protocol client, not a management tool. It runs commands and returns their output. It does not interpret the output, does not do idempotency, does not retry a failed step, and does not keep an inventory of what it changed. If your requirement is a declarative description of a machine's desired state, you want the layer above this one, which is what Ansible provides and what this library serves.

The third limit is the release gap. The tags show v0.5.0 published on 2024-07-16, v0.4.3 in April 2022 with Python 3.11 compatibility, and v0.4.2 in July 2021 with Python 3.10 compatibility. The last push to the repository was on 2026-08-04, so the source is being worked on while the published release is more than two years old. A client library talking to a Windows service is not as volatile as a UI framework, but a project that pins `pywinrm` should pin the version and read `CHANGELOG.md` before bumping.

## MIT terms, a beta classifier, and a small dependency set

The licence is MIT, with the `LICENSE` file at the repository root and the classifier `License:: OSI Approved:: MIT License` in the package metadata. For a library that will sit in the dependency tree of most Ansible installations, that is the least significant decision on the page.

The package metadata declares itself as `Development Status:: 4 - Beta`, which is worth noticing. A beta status on a library that Ansible depends on for Windows support is a statement about the project's own confidence, and it is a reason to pin the version rather than track the branch.

The dependency set is small and worth reading. `requests` for the HTTP layer, `requests_ntlm` for the NTLM authentication handshake, and `xmltodict` to turn the WS-Management XML into something you can work with. There is no framework layer and no logging configuration to fight with.

The build system is modern even if the release cadence is slow: `pyproject.toml` with `setuptools >= 61.0.0` as the build requirement, a `MANIFEST.in` for package data, a `requirements-test.txt` for the test extras, a `.coveragerc` for coverage configuration, and a CI workflow running the test suite with a Coveralls badge for coverage reporting.

## Conclusion

pywinrm fits a Python tool that has to run commands, batch files or PowerShell scripts on Windows hosts, and it fits a team already running Ansible, where it is the transport underneath Windows support. It does not fit a cross-domain setup that needs Kerberos without a kinit ticket, and it is the wrong choice for CredSSP over plain HTTP, which the library will not do. Before you adopt it, decide which transport matches your account type, since basic only works for local Windows accounts and Kerberos only works inside the same domain, and check the version you pin, because the newest published release is v0.5.0 from 2024 while the repository itself was pushed to on 2026-08-04.

## FAQ

### How do I install pywinrm and use it to run a command on a Windows host?

Install it with `pip install pywinrm`, then create a session and run a command: `s = winrm.Session('windows-host.example.com', auth=('john.smith', 'secret'))` followed by `r = s.run_cmd('ipconfig', ['/all'])`, reading `r.status_code`, `r.std_out` and `r.std_err`.

### Which WinRM transports does pywinrm support?

The transport parameter accepts basic, plaintext, certificate, ssl, kerberos, ntlm and credssp. Basic works only for local Windows accounts, kerberos only for domain accounts and only from the same domain, and credssp allows double hop but only over an HTTPS endpoint.

### What extra packages do I need for Kerberos or CredSSP?

For Kerberos, install the system libraries, `libkrb5-dev` on Debian and Ubuntu or `krb5-devel` with `krb5-workstation` on RHEL and CentOS, and then `pip install pywinrm[kerberos]`. For CredSSP, `pip install pywinrm[credssp]` is enough, since it only adds requests-credssp.

### Why does my pywinrm request get rejected by the server?

A default-configured WinRM server will automatically reject requests from pywinrm if the communication is unencrypted. Either use an HTTPS endpoint, or use NTLM, Kerberos or CredSSP with the message_encryption argument set to auto or always.

### Which Python versions does pywinrm support?

The package metadata requires Python 3.8 or newer, with classifiers for Python 3.8 through 3.12 and PyPy, and the project documentation lists CPython 3.8+ or PyPy3 as the runtime requirement. The client itself runs on Linux, Mac OS X or Windows.

## Sources

- [diyan/pywinrm on GitHub](https://github.com/diyan/pywinrm)
- [Issues](https://github.com/diyan/pywinrm/issues)
- [License: MIT](https://github.com/diyan/pywinrm/blob/master/LICENSE)
- [README](https://github.com/diyan/pywinrm/blob/master/README.md)
- [Releases](https://github.com/diyan/pywinrm/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/diyan-pywinrm
