Overview of WinRM and Its Role in Ansible Management

Windows Remote Management (WinRM) is Microsoft’s implementation of the WS-Management protocol, a DMTF standard for remote management of hardware and software. WinRM enables remote PowerShell sessions, remote command execution, and is the primary transport mechanism that Ansible uses when managing Windows hosts. Unlike Linux targets where Ansible uses SSH, Windows targets use WinRM by default — though SSH is now also supported on Windows (covered later). Understanding WinRM’s architecture is essential for setting up reliable Ansible automation against Windows Server 2022.

WinRM operates on two ports: HTTP on port 5985 for unencrypted transport (message-level encryption is still applied by the authentication mechanism in most cases) and HTTPS on port 5986 for transport-level TLS encryption. For production environments, HTTPS is strongly recommended. The WinRM service (Windows Remote Management) must be running on the target Windows Server 2022 machine, and appropriate firewall rules must permit inbound connections on the chosen port.

Enabling WinRM on Windows Server 2022

On Windows Server 2022, WinRM can be enabled quickly using the built-in quickconfig command. Open an elevated PowerShell session on the Windows Server 2022 target and run:

winrm quickconfig

This command starts the WinRM service, sets it to start automatically, creates an HTTP listener on port 5985, and adds a Windows Firewall rule permitting inbound WinRM traffic. Alternatively, use PowerShell’s remoting configuration cmdlet which also configures the necessary session configurations:

Enable-PSRemoting -Force

The -Force switch suppresses confirmation prompts. This is equivalent to winrm quickconfig plus it registers the PowerShell remoting session configurations (Microsoft.PowerShell, Microsoft.PowerShell32, and Microsoft.PowerShell.Workflow). Verify the listener was created successfully:

winrm enumerate winrm/config/listener

The output should show a listener on Transport=HTTP, Port=5985. To view the full WinRM service configuration:

winrm get winrm/config

Configuring WinRM HTTPS Listener

For Ansible in production, configure WinRM to use HTTPS. You need a certificate whose Subject or Subject Alternative Name matches the hostname Ansible will use to connect. First, create a self-signed certificate suitable for WinRM:

$hostname = $env:COMPUTERNAME
$cert = New-SelfSignedCertificate `
    -DnsName $hostname `
    -CertStoreLocation "Cert:LocalMachineMy" `
    -KeyLength 2048 `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears(3)

Write-Host "Certificate thumbprint: $($cert.Thumbprint)"

Create the WinRM HTTPS listener using the certificate thumbprint:

$thumbprint = $cert.Thumbprint
$cmd = "winrm create winrm/config/Listener?Address=*+Transport=HTTPS @{Hostname=`"$hostname`";CertificateThumbprint=`"$thumbprint`"}"
Invoke-Expression $cmd

Alternatively, using PowerShell’s WSMan provider:

New-Item -Path WSMan:LocalhostListener -Transport HTTPS -Address * -CertificateThumbprint $cert.Thumbprint -Force

Add the Windows Firewall rule for port 5986:

New-NetFirewallRule `
    -Name "WinRM-HTTPS-Inbound" `
    -DisplayName "WinRM HTTPS (5986)" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 5986 `
    -Action Allow `
    -Profile Domain, Private

Restart the WinRM service to apply all changes:

Restart-Service WinRM

Installing pywinrm on the Ansible Control Node

Ansible’s WinRM connection plugin requires the pywinrm Python library on the Ansible control node (which runs Linux — Ansible itself does not run on Windows). Install it using pip:

pip install pywinrm
pip install pywinrm[kerberos]   # For Kerberos/AD authentication
pip install pywinrm[credssp]    # For CredSSP authentication

Verify the installation:

python3 -c "import winrm; print(winrm.__version__)"

Creating an Ansible Inventory for Windows Hosts

Create an inventory file that specifies Windows Server 2022 hosts and their connection variables. A typical inventory in INI format:

[windows]
winserver01.contoso.com
winserver02.contoso.com

[windows:vars]
ansible_user=Administrator
ansible_password=YourSecurePassword123!
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_port=5985
ansible_winrm_scheme=http
ansible_winrm_server_cert_validation=ignore

For HTTPS with certificate validation disabled (self-signed cert scenario):

[windows:vars]
ansible_user=Administrator
ansible_password=YourSecurePassword123!
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_port=5986
ansible_winrm_scheme=https
ansible_winrm_server_cert_validation=ignore

For Kerberos authentication against a domain-joined Windows Server 2022 host (recommended for domain environments):

[windows:vars]
ansible_user=CONTOSOansible-svc
ansible_password=ServiceAcctPassword
ansible_connection=winrm
ansible_winrm_transport=kerberos
ansible_winrm_port=5985
ansible_winrm_kerberos_delegation=true

WinRM Authentication Methods Comparison

WinRM supports several authentication mechanisms, each with different security characteristics. Basic authentication sends credentials in Base64 — only acceptable over HTTPS, never plain HTTP. NTLM uses challenge-response and works on both domain and non-domain systems, transmitting a hash rather than the password. Kerberos is the most secure option for domain-joined hosts and avoids passing credentials over the wire entirely — the Ansible control node obtains a Kerberos ticket from the domain controller. CredSSP delegates credentials to the remote host (useful for double-hop scenarios), but requires additional setup and carries higher risk. Certificate-based auth uses a client certificate mapped to a Windows user account — the most secure option for non-domain scenarios.

To configure WinRM on the server to allow specific authentication methods:

# Enable NTLM authentication
Set-Item -Path WSMan:LocalhostServiceAuthNTLM -Value $true

# Enable Kerberos authentication
Set-Item -Path WSMan:LocalhostServiceAuthKerberos -Value $true

# Enable Basic authentication (HTTPS only)
Set-Item -Path WSMan:LocalhostServiceAuthBasic -Value $true

# Allow unencrypted (for HTTP/NTLM — not recommended for production)
Set-Item -Path WSMan:LocalhostServiceAllowUnencrypted -Value $true

Essential Ansible Windows Modules

Ansible provides a rich library of Windows-specific modules. The following are the most commonly used when managing Windows Server 2022:

win_command — runs a command that does not need shell features:

- name: Run ipconfig
  win_command: ipconfig /all
  register: ipconfig_output

win_shell — runs a command through PowerShell, supporting pipes, redirection, and PS syntax:

- name: Get disk usage
  win_shell: Get-PSDrive C | Select-Object Used, Free
  register: disk_info

win_service — manages Windows services:

- name: Ensure W32tm service is running
  win_service:
    name: w32tm
    state: started
    start_mode: auto

win_feature — installs or removes Windows Server features:

- name: Install IIS
  win_feature:
    name: Web-Server
    state: present
    include_management_tools: yes
    include_sub_features: yes

win_package — installs MSI or EXE packages:

- name: Install 7-Zip
  win_package:
    path: C:Installers7z2301-x64.msi
    state: present
    arguments: /quiet /norestart

Running Your First Ansible Playbook Against Windows Server 2022

Create a simple playbook file named setup_windows.yml:

---
- name: Configure Windows Server 2022
  hosts: windows
  gather_facts: yes

  tasks:
    - name: Ensure Windows Firewall is enabled on all profiles
      win_firewall:
        profiles: Domain, Private, Public
        state: enabled
        enabled: yes

    - name: Set timezone to UTC
      win_timezone:
        timezone: UTC

    - name: Ensure Remote Registry service is disabled
      win_service:
        name: RemoteRegistry
        state: stopped
        start_mode: disabled

    - name: Install Windows Updates (security only)
      win_updates:
        category_names:
          - SecurityUpdates
        reboot: no
        state: installed
      register: update_result

    - name: Print update results
      debug:
        var: update_result.found_update_count

Run the playbook with verbosity enabled to see WinRM connection details:

ansible-playbook -i inventory.ini setup_windows.yml -vvv

Test connectivity before running a full playbook using the win_ping module:

ansible windows -i inventory.ini -m win_ping

A successful response returns “pong”, confirming WinRM connectivity, authentication, and that the Ansible Windows modules are functioning correctly on the target Windows Server 2022 host.

WinRM Firewall Rules and Hardening

On Windows Server 2022, restrict WinRM access to specific source IP ranges rather than allowing all connections. Replace the broad firewall rule created by Enable-PSRemoting with a restricted one:

# Remove the broad rule
Remove-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)" -ErrorAction SilentlyContinue

# Create a restricted rule allowing only your Ansible control node
New-NetFirewallRule `
    -Name "WinRM-Ansible-Only" `
    -DisplayName "WinRM Ansible Control Node Only" `
    -Direction Inbound `
    -Protocol TCP `
    -LocalPort 5985 `
    -RemoteAddress 10.10.10.50 `
    -Action Allow `
    -Profile Any

Also consider setting the MaxConcurrentOperationsPerUser and MaxConnections limits in WinRM to prevent resource exhaustion from misconfigured playbooks:

Set-Item -Path WSMan:LocalhostServiceMaxConcurrentOperationsPerUser -Value 50
winrm set winrm/config @{MaxTimeoutms="1800000"}