Windows Automation Architecture
Relevant source files
The following files were used as context for generating this wiki page:- lib/ansible/plugins/connection/winrm.py
- test/support/windows-integration/plugins/modules/win_command.py
- test/support/windows-integration/plugins/modules/win_shell.py
- test/support/windows-integration/plugins/modules/win_stat.py
- test/integration/targets/connection_winrm/tests.yml
- test/lib/ansible_test/_internal/util.py
Ansible manages Windows machines in an agentless manner, similar to how it manages Linux/Unix machines. However, instead of using SSH, Ansible leverages Windows Remote Management (WinRM) as the primary transport for communication. This architecture allows Ansible to execute commands, run PowerShell scripts, and transfer files to remote Windows hosts without requiring any custom agent software to be installed on the target machines.
The core of this architecture is the winrm connection plugin, which handles all communication and execution logic. Most Ansible modules for Windows are implemented as PowerShell scripts (.ps1) that are transferred to the target and executed. The Python files for these modules in the Ansible repository often serve as documentation stubs, defining the module's parameters and return values, while the actual logic resides in the corresponding .ps1 file.
Connection Layer: WinRM
The winrm connection plugin is responsible for establishing and managing the connection to Windows hosts. It uses the pywinrm Python library to communicate over the WinRM protocol, which is Microsoft's implementation of the WS-Management Protocol.
Sources: lib/ansible/plugins/connection/winrm.py:18-24, lib/ansible/plugins/connection/winrm.py:214-216
Connection Configuration
The connection can be configured using various Ansible variables. The plugin supports multiple authentication transports and connection schemes.
| Option | Description | Default | Ansible Variables |
|---|---|---|---|
remote_addr | Address of the Windows machine. | inventory_hostname | inventory_hostname, ansible_host, ansible_winrm_host |
remote_user | The user to log in as. | - | ansible_user, ansible_winrm_user |
remote_password | The user's password. | - | ansible_password, ansible_winrm_pass |
port | The WinRM port to connect to. | 5986 (HTTPS) | ansible_port, ansible_winrm_port |
scheme | URI scheme to use. | https (or http if port is 5985) | ansible_winrm_scheme |
transport | List of WinRM transports to try. | Auto-detected (kerberos for UPN, ssl/plaintext otherwise) | ansible_winrm_transport |
connection_timeout | Sets operation and read timeouts for the connection. | pywinrm default | ansible_winrm_connection_timeout |
Sources: lib/ansible/plugins/connection/winrm.py:26-193
Ansible's test suite validates various connection types, which are defined with specific variable combinations.
| Connection Type | ansible_port | ansible_winrm_scheme | ansible_winrm_transport |
|---|---|---|---|
winrm+http | 5985 | http | ntlm |
winrm+https | 5986 | https | (default) |
psrp+http | 5985 | - | - |
psrp+https | 5986 | - | - |
ssh+key | 22 | - | - |
ssh+password | 22 | - | - |
Sources: test/lib/ansible_test/_internal/util.py:126-160, test/integration/targets/connection_winrm/tests.yml:39-50
Connection and Authentication Flow
The connection process involves selecting a transport, authenticating, and opening a remote shell.
The diagram below illustrates the high-level connection flow within the winrm plugin.
- Initiation: An Ansible task targeting a Windows host triggers the
_connectmethod. - Build Arguments:
_build_winrm_kwargsgathers allansible_winrm_*variables to construct the connection parameters. - Transport Selection: If
ansible_winrm_transportis not set, the plugin auto-selects a transport. It defaults tokerberosif the username is a User Principal Name (UPN, e.g.,user@domain.com), otherwise it usessslfor HTTPS orplaintextfor HTTP. - Kerberos Authentication: If Kerberos is used in "managed" mode, the
_kerb_authmethod is called to obtain a ticket usingkinit. It creates a temporary Kerberos credential cache (KRB5CCNAME) for the connection. - Establish Connection: The
_winrm_connectmethod iterates through the specified transports, attempting to connect to the endpoint (e.g.,https://host:5986/wsman). - Open Shell: Upon a successful connection,
protocol.open_shellis called to create a new remote shell on the target, returning ashell_id. This shell is used for all subsequent command executions for the task. The shell is opened with codepage 65001 (UTF-8).
Sources: lib/ansible/plugins/connection/winrm.py:284-301, lib/ansible/plugins/connection/winrm.py:304-361, lib/ansible/plugins/connection/winrm.py:488-502, [lib/ansible/plugins/connection/winrm.py:532-540]
Module and Command Execution
Ansible executes modules and commands on Windows targets by running PowerShell scripts through the established WinRM connection.
Module Execution Model
Most Windows modules are composed of two parts: a Python file that serves as a documentation and parameter definition stub, and a PowerShell (.ps1) file that contains the actual implementation.
The diagram below shows the sequence of executing an Ansible module on a Windows host.
- The Ansible engine calls
exec_commandon thewinrmconnection plugin. - The plugin encodes the command and prepares the module data to be sent via
stdin. - The
_winrm_execmethod is called, which runs a command on the remote host and optionally streamsstdindata. - Inside
_winrm_exec, a command ID is obtained fromprotocol.run_command. - If there is input data (the module script),
_winrm_write_stdinsends it to the remote command'sstdinstream in chunks. - The
_winrm_get_command_outputmethod polls the remote command forstdout,stderr, and the return code until the command is done. - If the
stderroutput is CLIXML (Common Language Infrastructure XML), it is parsed by_parse_clixmlinto a more readable format. - The final result (return code, stdout, stderr) is returned to the Ansible engine.
Sources: lib/ansible/plugins/connection/winrm.py:734-758, lib/ansible/plugins/connection/winrm.py:642-732, [test/support/windows-integration/plugins/modules/win_stat.py:7-9]
win_command vs. win_shell
Ansible provides two primary modules for running commands on Windows, with a key distinction in how they execute the command.
| Feature | win_command | win_shell |
|---|---|---|
| Execution | Executes the command directly. | Runs the command via a shell (PowerShell by default). |
| Shell Features | Not processed by a shell. Variables ($env:VAR) and operators (>, ` | , ;`) do not work. |
| Security | More secure, as it is not affected by the user's shell environment. | Less predictable, as it can be influenced by the shell environment. |
| Use Case | Running a specific executable with arguments. | Running complex commands, scripts, or using shell features. |
executable param | Not available. | Yes, can change the shell (e.g., cmd). |
Sources: test/support/windows-integration/plugins/modules/win_command.py:13-19, test/support/windows-integration/plugins/modules/win_shell.py:13-16, [test/support/windows-integration/plugins/modules/win_shell.py:38-41]
File Transfer
The winrm plugin implements put_file and fetch_file to transfer files to and from the Windows target. These operations do not use a separate protocol like SFTP; instead, they leverage PowerShell scripts executed over WinRM.
put_file
To upload a file, put_file executes a helper PowerShell script (winrm_put_file.ps1) on the target. The file content is read locally, base64-encoded, and streamed to the script's stdin. The remote script decodes the data and writes it to the destination file.
This diagram shows the put_file process.
After the transfer, the remote script calculates the SHA1 hash of the newly created file and returns it. The put_file method compares this with a locally calculated hash to verify the integrity of the transfer.
Sources: [lib/ansible/plugins/connection/winrm.py:797-832]
fetch_file
To download a file, fetch_file executes the winrm_fetch_file.ps1 helper script. This script reads the requested file on the remote host in chunks, base64-encodes them, and writes them to its stdout. The winrm plugin reads the stdout, decodes the data, and writes it to the local destination file. This process repeats in a loop until the entire file is transferred.
Sources: [lib/ansible/plugins/connection/winrm.py:834-873]
Handling WinRM Quotas
WinRM has a MaxConcurrentOperationsPerUser quota that can be exhausted, especially during tasks that run many commands in a loop. The winrm connection plugin has a mechanism to handle this. If it receives a WSManFaultError with code 0x803381A6 (ERROR_WSMAN_QUOTA_MAX_OPERATIONS), it automatically closes the current shell, opens a new one, and retries the command. This is tested in the integration test suite by lowering the quota and running a looped command.
Sources: lib/ansible/plugins/connection/winrm.py:707-732, [test/integration/targets/connection_winrm/tests.yml:52-67]