Ansible can manage Windows servers just as effectively as Linux. Instead of SSH, Ansible communicates with Windows hosts over WinRM (Windows Remote Management) – a built-in Windows protocol that accepts remote commands. This opens the door to automating software installs, service management, Active Directory tasks, Windows Updates, and full server provisioning from a single Linux control node.
This guide walks through configuring WinRM on Windows Server, setting up Ansible inventory for Windows hosts, and running practical playbooks that cover the most common Windows administration tasks with Ansible. Every example uses tested Ansible Windows modules that work on Windows Server 2019, 2022, and 2025.
Prerequisites
Before starting, make sure you have these in place:
- A Linux control node (Ubuntu 24.04, Rocky Linux 10, or any supported distro) with Ansible 2.15+ installed
- One or more Windows Server hosts (2019, 2022, or 2025) with administrator access
- Network connectivity between the control node and Windows hosts on port 5986 (HTTPS WinRM)
- Python
pywinrmlibrary installed on the control node - PowerShell 5.1 or later on the Windows hosts (included by default in Server 2019+)
Install the required Python library and Ansible Windows collection on your control node:
pip install pywinrm
ansible-galaxy collection install ansible.windows
Step 1: Configure WinRM on Windows Server
WinRM must be enabled and configured on every Windows host that Ansible will manage. Open an elevated PowerShell prompt on the Windows server and run the following script. This enables WinRM over HTTPS with a self-signed certificate, creates a firewall rule for port 5986, and sets basic authentication.
$url = "https://raw.githubusercontent.com/ansible/ansible-documentation/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file
This is the official Ansible remoting script that handles all the WinRM configuration. After it finishes, verify WinRM is listening on HTTPS:
winrm enumerate winrm/config/Listener
You should see a listener entry with Transport = HTTPS and Port = 5986:
Listener
Address = *
Transport = HTTPS
Port = 5986
Hostname
Enabled = true
URLPrefix = wsman
CertificateThumbprint = 8A2B4C6D8E0F...
If you prefer manual configuration instead of the script, run these PowerShell commands individually:
Enable-PSRemoting -Force
Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true
Set-Item -Path WSMan:\localhost\Service\AllowUnencrypted -Value $false
$cert = New-SelfSignedCertificate -DnsName $(hostname) -CertStoreLocation Cert:\LocalMachine\My
New-Item -Path WSMan:\localhost\Listener -Transport HTTPS -Address * -CertificateThumbprint $cert.Thumbprint -Force
New-NetFirewallRule -Name "WinRM-HTTPS" -DisplayName "WinRM over HTTPS" -Enabled True -Direction Inbound -Protocol TCP -LocalPort 5986 -Action Allow
Step 2: Configure Ansible Inventory for Windows Hosts
Ansible needs specific connection variables to talk to Windows hosts. Create an inventory file that defines your Windows servers and the WinRM connection parameters.
Create the inventory directory and hosts file:
mkdir -p ~/ansible-windows/inventory
Open the inventory file:
vi ~/ansible-windows/inventory/hosts.yml
Add the following configuration, replacing the IP addresses and credentials with your actual values:
all:
children:
windows:
hosts:
win-server01:
ansible_host: 192.168.1.50
win-server02:
ansible_host: 192.168.1.51
vars:
ansible_user: Administrator
ansible_password: "YourSecurePassword123!"
ansible_connection: winrm
ansible_winrm_transport: basic
ansible_winrm_server_cert_validation: ignore
ansible_port: 5986
ansible_winrm_scheme: https
For production environments, store the password in Ansible Vault instead of plain text. Use Kerberos or CredSSP transport instead of basic auth when the hosts are domain-joined.
Step 3: Test Ansible Connection to Windows with win_ping
Before running any playbooks, confirm that Ansible can reach the Windows hosts. The win_ping module is the Windows equivalent of the standard ping module – it connects over WinRM and returns a success response.
ansible windows -i ~/ansible-windows/inventory/hosts.yml -m ansible.windows.win_ping
A successful connection returns a pong response for each host:
win-server01 | SUCCESS => {
"changed": false,
"ping": "pong"
}
win-server02 | SUCCESS => {
"changed": false,
"ping": "pong"
}
If the connection fails, check these common issues: WinRM service not running on the Windows host, firewall blocking port 5986, wrong credentials, or certificate validation problems. Run with -vvvv for detailed debug output.
Step 4: Install Software with win_chocolatey
Chocolatey is the standard package manager for Windows, and the win_chocolatey module lets you install, update, and remove software packages. Ansible installs Chocolatey automatically on the first run if it is not present.
Create a playbook to install common tools:
vi ~/ansible-windows/install-software.yml
Add the following playbook content:
---
- name: Install software on Windows servers
hosts: windows
tasks:
- name: Install Chocolatey packages
ansible.windows.win_chocolatey:
name: "{{ item }}"
state: present
loop:
- googlechrome
- notepadplusplus
- 7zip
- git
- vscode
- name: Install a specific version of Python
ansible.windows.win_chocolatey:
name: python3
version: "3.12.4"
state: present
- name: Update all installed Chocolatey packages
ansible.windows.win_chocolatey:
name: all
state: latest
Run the playbook:
ansible-playbook -i ~/ansible-windows/inventory/hosts.yml ~/ansible-windows/install-software.yml
Ansible reports each package installation with changed status. Packages already installed show ok with no changes made.
Step 5: Manage Windows Services with Ansible
The win_service module controls Windows services – start, stop, restart, and configure startup type. This is essential for managing IIS, SQL Server, or any custom service across multiple servers.
Create the service management playbook:
vi ~/ansible-windows/manage-services.yml
Add the following tasks:
---
- name: Manage Windows services
hosts: windows
tasks:
- name: Ensure Windows Update service is running
ansible.windows.win_service:
name: wuauserv
start_mode: auto
state: started
- name: Stop and disable Print Spooler on servers
ansible.windows.win_service:
name: Spooler
start_mode: disabled
state: stopped
- name: Restart IIS service
ansible.windows.win_service:
name: W3SVC
state: restarted
when: "'webservers' in group_names"
- name: Get service info
ansible.windows.win_service_info:
name: wuauserv
register: svc_info
- name: Display service status
ansible.builtin.debug:
msg: "Windows Update service is {{ svc_info.services[0].state }}"
The win_service_info module is useful for gathering service state before taking action. Combine it with conditionals to build playbooks that only restart services when configuration files change.
Step 6: Manage Windows Features and Roles
Windows Server roles and features – like IIS, DNS Server, DHCP, or Hyper-V – are managed with the win_feature module. This replaces the manual Server Manager workflow with repeatable automation.
Create the features playbook:
vi ~/ansible-windows/manage-features.yml
Add tasks to install and remove server features:
---
- name: Manage Windows Server features and roles
hosts: windows
tasks:
- name: Install IIS web server with management tools
ansible.windows.win_feature:
name:
- Web-Server
- Web-Mgmt-Tools
- Web-Mgmt-Console
state: present
include_sub_features: true
include_management_tools: true
register: iis_install
- name: Reboot if IIS installation requires it
ansible.windows.win_reboot:
reboot_timeout: 300
when: iis_install.reboot_required
- name: Install DNS Server role
ansible.windows.win_feature:
name: DNS
state: present
include_management_tools: true
- name: Install Telnet Client for diagnostics
ansible.windows.win_feature:
name: Telnet-Client
state: present
- name: Remove Windows Fax and Scan
ansible.windows.win_feature:
name: Fax
state: absent
The win_reboot module handles graceful reboots when a feature installation requires it. Ansible waits for the host to come back online before continuing with the next task.
Step 7: Manage Files and Registry on Windows
File operations and registry edits are two of the most common admin tasks on Windows. Ansible provides win_copy, win_template, and win_regedit modules to handle these declaratively.
Create the file and registry management playbook:
vi ~/ansible-windows/files-registry.yml
Add the following tasks:
---
- name: Manage files and registry on Windows
hosts: windows
tasks:
- name: Create a directory structure
ansible.windows.win_file:
path: C:\Apps\Configs
state: directory
- name: Copy configuration file to Windows host
ansible.windows.win_copy:
src: files/app-config.xml
dest: C:\Apps\Configs\app-config.xml
- name: Download a file from URL
ansible.windows.win_get_url:
url: https://download.sysinternals.com/files/SysinternalsSuite.zip
dest: C:\Temp\SysinternalsSuite.zip
- name: Set registry value to disable Server Manager at logon
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\ServerManager
name: DoNotOpenServerManagerAtLogon
data: 1
type: dword
- name: Configure RDP security settings via registry
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp
name: SecurityLayer
data: 2
type: dword
- name: Set environment variable system-wide
ansible.windows.win_environment:
name: APP_ENV
value: production
level: machine
state: present
The win_regedit module is idempotent – it only makes changes when the current registry value differs from the desired state. Always use the full registry path format with the hive prefix (HKLM, HKCU, etc.).
Step 8: Manage Active Directory Users with Ansible
For domain-joined environments, Ansible can create and manage Active Directory users, groups, and organizational units using the microsoft.ad collection. Install it first on your control node:
ansible-galaxy collection install microsoft.ad
Create the AD management playbook:
vi ~/ansible-windows/manage-ad-users.yml
Add tasks to manage users, groups, and OUs:
---
- name: Manage Active Directory users and groups
hosts: windows
tasks:
- name: Create an Organizational Unit
microsoft.ad.ou:
name: DevOps
path: "DC=example,DC=com"
state: present
description: "DevOps team accounts"
- name: Create AD user
microsoft.ad.user:
name: jdoe
firstname: John
surname: Doe
password: "Str0ngP@ssword!"
state: present
path: "OU=DevOps,DC=example,DC=com"
email: [email protected]
enabled: true
password_never_expires: false
user_cannot_change_password: false
update_password: on_create
- name: Create AD security group
microsoft.ad.group:
name: DevOps-Admins
scope: global
path: "OU=DevOps,DC=example,DC=com"
state: present
description: "DevOps administrators group"
- name: Add user to group
microsoft.ad.group:
name: DevOps-Admins
members:
add:
- jdoe
state: present
- name: Disable a terminated user account
microsoft.ad.user:
name: former_employee
state: present
enabled: false
The update_password: on_create setting prevents Ansible from resetting the password on subsequent runs. This is important for user accounts where the person has already changed their password.
Step 9: Automate Windows Updates with Ansible
Patching Windows servers manually is time-consuming and error-prone. The win_updates module downloads and installs Windows updates with full control over categories, reboot behavior, and blacklisted KBs.
Create the Windows Update playbook:
vi ~/ansible-windows/windows-updates.yml
Add the update tasks:
---
- name: Apply Windows Updates
hosts: windows
tasks:
- name: Install all critical and security updates
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
state: installed
reboot: true
reboot_timeout: 600
register: update_result
- name: Display update results
ansible.builtin.debug:
msg: >
Installed {{ update_result.installed_update_count }} updates.
Reboot required: {{ update_result.reboot_required }}
- name: Install all updates except specific KBs
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
- UpdateRollups
reject_list:
- KB5001234
- KB5005678
state: installed
reboot: true
- name: Search for available updates without installing
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
state: searched
register: available_updates
- name: Show pending updates
ansible.builtin.debug:
msg: "{{ available_updates.found_update_count }} updates available"
The state: searched option is valuable for audit playbooks – it checks what updates are available without installing anything. Use reject_list to exclude known problematic KBs from automated patching.
Step 10: Complete Windows Server Setup Playbook
Combining everything into a single playbook gives you a repeatable server provisioning workflow. This playbook takes a fresh Windows Server and configures it to your standard – hostname, features, software, security settings, and monitoring agent.
Create the master setup playbook:
vi ~/ansible-windows/full-server-setup.yml
Add the complete server provisioning tasks:
---
- name: Complete Windows Server setup
hosts: windows
vars:
server_timezone: "Eastern Standard Time"
ntp_server: "time.windows.com"
admin_users:
- name: sysadmin
firstname: System
surname: Admin
password: "S3cureP@ss!"
required_features:
- Telnet-Client
- RSAT-AD-Tools
- Web-Server
required_packages:
- googlechrome
- notepadplusplus
- 7zip
- git
tasks:
- name: Set timezone
community.windows.win_timezone:
timezone: "{{ server_timezone }}"
- name: Set NTP server
ansible.windows.win_shell: |
w32tm /config /manualpeerlist:"{{ ntp_server }}" /syncfromflags:manual /reliable:YES /update
Restart-Service w32time
w32tm /resync
- name: Disable unnecessary services
ansible.windows.win_service:
name: "{{ item }}"
start_mode: disabled
state: stopped
loop:
- Spooler
- Fax
- XblGameSave
- XblAuthManager
ignore_errors: true
- name: Install required Windows features
ansible.windows.win_feature:
name: "{{ required_features }}"
state: present
include_management_tools: true
register: features_result
- name: Install required software via Chocolatey
ansible.windows.win_chocolatey:
name: "{{ item }}"
state: present
loop: "{{ required_packages }}"
- name: Configure Windows Firewall - allow RDP
community.windows.win_firewall_rule:
name: "Allow RDP"
localport: 3389
action: allow
direction: in
protocol: tcp
state: present
enabled: true
- name: Configure Windows Firewall - allow WinRM HTTPS
community.windows.win_firewall_rule:
name: "Allow WinRM HTTPS"
localport: 5986
action: allow
direction: in
protocol: tcp
state: present
enabled: true
- name: Disable Server Manager at logon
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Microsoft\ServerManager
name: DoNotOpenServerManagerAtLogon
data: 1
type: dword
- name: Enable RDP
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server
name: fDenyTSConnections
data: 0
type: dword
- name: Set power plan to High Performance
ansible.windows.win_shell: powercfg /setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
- name: Install all security updates
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
state: installed
reboot: true
reboot_timeout: 900
register: updates
- name: Final reboot if features require it
ansible.windows.win_reboot:
reboot_timeout: 300
when: features_result.reboot_required
- name: Verify server configuration
ansible.windows.win_shell: |
$info = @{
Hostname = hostname
OS = (Get-CimInstance Win32_OperatingSystem).Caption
Uptime = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
Updates = "{{ updates.installed_update_count | default(0) }} installed"
}
$info | Format-Table -AutoSize
register: verify
- name: Display server status
ansible.builtin.debug:
var: verify.stdout_lines
Run the full setup playbook against new servers:
ansible-playbook -i ~/ansible-windows/inventory/hosts.yml ~/ansible-windows/full-server-setup.yml
This playbook is idempotent – running it again on an already-configured server makes zero changes. That makes it safe for both initial provisioning and ongoing compliance checks.
Reference: Common Ansible Windows Modules
The Ansible Windows documentation covers the full module list. Here are the modules you will use most often for Windows server administration:
| Module | Purpose |
|---|---|
win_ping | Test WinRM connectivity to Windows hosts |
win_copy | Copy files from control node to Windows host |
win_file | Create, delete, and manage files and directories |
win_template | Deploy Jinja2 templates to Windows hosts |
win_service | Manage Windows services (start, stop, configure) |
win_service_info | Gather information about Windows services |
win_feature | Install or remove Windows Server roles and features |
win_chocolatey | Install, update, and remove Chocolatey packages |
win_regedit | Add, modify, and remove registry keys and values |
win_updates | Search for and install Windows Updates |
win_reboot | Reboot Windows host and wait for it to come back |
win_shell | Execute PowerShell commands on Windows hosts |
win_command | Execute commands without PowerShell shell processing |
win_get_url | Download files from HTTP/HTTPS/FTP URLs |
win_environment | Manage environment variables at user or system level |
win_scheduled_task | Create and manage Windows Scheduled Tasks |
win_user | Manage local Windows user accounts |
win_group | Manage local Windows groups |
win_firewall_rule | Create and manage Windows Firewall rules |
win_dsc | Run PowerShell DSC resources on Windows hosts |
Conclusion
Ansible gives you a single automation platform for both Linux and Windows infrastructure. With WinRM configured and the right inventory variables set, every Windows administration task – from user management to patching to full server provisioning – becomes a repeatable playbook.
For production deployments, use Kerberos authentication instead of basic auth, store credentials in Ansible Vault, and set up Ansible AWX for centralized job scheduling and audit logging. Keep your playbooks in Git and test changes in a staging environment before rolling out to production servers.