Introduction
Enterprise Windows environments are complex: Active Directory forests with dozens of OUs, Group Policy Objects layered across sites, IIS farms serving internal applications, SQL Server clusters, and thousands of endpoints needing patching. Ansible manages all of it without installing agents — using WinRM or SSH to connect, PowerShell under the hood, and the same playbook/role/inventory patterns as Linux automation.
Setup
WinRM Configuration on Windows Targets
# Run on Windows targets (as Administrator)
# Option 1: Quick setup script from Ansible
$url = "https://raw.githubusercontent.com/ansible/ansible-documentation/main/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)
powershell.exe -ExecutionPolicy ByPass -File $file
# Option 2: Manual HTTPS setup
winrm quickconfig -transport:https
winrm set winrm/config/service '@{AllowUnencrypted="false"}'
winrm set winrm/config/service/auth '@{Basic="true"}'
winrm set winrm/config/service/auth '@{CredSSP="true"}'
Ansible Control Node Configuration
pip install pywinrm requests-credssp
# group_vars/windows.yml
ansible_connection: winrm
ansible_winrm_transport: credssp
ansible_winrm_server_cert_validation: validate
ansible_port: 5986
ansible_user: "{{ vault_windows_admin }}"
ansible_password: "{{ vault_windows_password }}"
Collections
ansible-galaxy collection install ansible.windows
ansible-galaxy collection install community.windows
ansible-galaxy collection install microsoft.ad
Pattern 1: Active Directory Management
Create OUs and Users
---
- name: Manage Active Directory
hosts: domain_controllers
vars:
ad_domain: example.com
base_dn: "DC=example,DC=com"
tasks:
- name: Create organizational units
microsoft.ad.ou:
name: "{{ item.name }}"
path: "{{ item.path }}"
state: present
protected: true
loop:
- { name: "IT Department", path: "{{ base_dn }}" }
- { name: "Users", path: "OU=IT Department,{{ base_dn }}" }
- { name: "Groups", path: "OU=IT Department,{{ base_dn }}" }
- { name: "Service Accounts", path: "OU=IT Department,{{ base_dn }}" }
- { name: "Servers", path: "OU=IT Department,{{ base_dn }}" }
- name: Create AD security groups
microsoft.ad.group:
name: "{{ item.name }}"
path: "OU=Groups,OU=IT Department,{{ base_dn }}"
scope: global
category: security
description: "{{ item.description }}"
state: present
loop:
- { name: "IT-Admins", description: "IT administrators" }
- { name: "IT-Developers", description: "Development team" }
- { name: "IT-Operations", description: "Operations team" }
- { name: "SVC-Ansible", description: "Ansible service accounts" }
- name: Create user accounts
microsoft.ad.user:
name: "{{ item.name }}"
firstname: "{{ item.first }}"
surname: "{{ item.last }}"
password: "{{ item.password }}"
state: present
path: "OU=Users,OU=IT Department,{{ base_dn }}"
email: "{{ item.email }}"
upn: "{{ item.email }}"
enabled: true
password_never_expires: false
user_cannot_change_password: false
groups:
add: "{{ item.groups }}"
update_password: on_create
loop: "{{ ad_users }}"
no_log: true
- name: Create Ansible service account
microsoft.ad.user:
name: svc-ansible
firstname: Ansible
surname: Service
password: "{{ vault_ansible_svc_password }}"
state: present
path: "OU=Service Accounts,OU=IT Department,{{ base_dn }}"
password_never_expires: true
user_cannot_change_password: true
groups:
add:
- SVC-Ansible
- Domain Admins
no_log: true
Bulk User Import from CSV
- name: Import users from CSV
hosts: domain_controllers
tasks:
- name: Read CSV file
ansible.builtin.read_csv:
path: files/new_users.csv
register: user_csv
delegate_to: localhost
- name: Create users from CSV
microsoft.ad.user:
name: "{{ item.samAccountName }}"
firstname: "{{ item.FirstName }}"
surname: "{{ item.LastName }}"
password: "{{ item.Password }}"
path: "{{ item.OU }}"
email: "{{ item.Email }}"
enabled: true
update_password: on_create
state: present
loop: "{{ user_csv.list }}"
no_log: true
Pattern 2: Windows Update Management
---
- name: Patch Windows servers — rolling update
hosts: windows_servers
serial: "25%"
max_fail_percentage: 10
tasks:
- name: Create pre-patch snapshot (VMware)
community.vmware.vmware_guest_snapshot:
hostname: "{{ vcenter_hostname }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
name: "{{ inventory_hostname }}"
snapshot_name: "pre-patch-{{ ansible_date_time.date }}"
state: present
delegate_to: localhost
- name: Install critical and security updates
ansible.windows.win_updates:
category_names:
- CriticalUpdates
- SecurityUpdates
- UpdateRollups
reboot: true
reboot_timeout: 3600
state: installed
register: update_result
- name: Display update results
ansible.builtin.debug:
msg: |
Updates installed: {{ update_result.installed_update_count }}
Reboot required: {{ update_result.reboot_required }}
Updates: {{ update_result.filtered_updates | map(attribute='title') | list }}
- name: Verify services after patching
ansible.windows.win_service_info:
name: "{{ item }}"
register: service_check
loop:
- W3SVC
- MSSQLSERVER
- WinRM
failed_when: service_check.services[0].state != "running"
- name: Run post-patch health check
ansible.windows.win_shell: |
$services = @('W3SVC', 'MSSQLSERVER', 'WinRM')
$failed = @()
foreach ($svc in $services) {
$s = Get-Service -Name $svc -ErrorAction SilentlyContinue
if ($s -and $s.Status -ne 'Running') { $failed += $svc }
}
if ($failed.Count -gt 0) { throw "Failed services: $($failed -join ', ')" }
Write-Output "All services healthy"
register: health_check
Pattern 3: IIS Web Server Management
---
- name: Configure IIS web farm
hosts: iis_servers
tasks:
- name: Install IIS with features
ansible.windows.win_feature:
name:
- Web-Server
- Web-Asp-Net45
- Web-ISAPI-Ext
- Web-ISAPI-Filter
- Web-Mgmt-Console
- Web-Scripting-Tools
- Web-Filtering
- Web-Stat-Compression
- Web-Dyn-Compression
- Web-Url-Auth
state: present
include_management_tools: true
register: iis_install
- name: Reboot if features required restart
ansible.windows.win_reboot:
when: iis_install.reboot_required
- name: Create application pool
community.windows.win_iis_webapppool:
name: MyApp-Pool
state: started
attributes:
managedRuntimeVersion: v4.0
managedPipelineMode: Integrated
startMode: AlwaysRunning
processModel.idleTimeout: "00:00:00"
processModel.identityType: SpecificUser
processModel.userName: "EXAMPLE\\svc-myapp"
processModel.password: "{{ vault_svc_myapp_password }}"
recycling.periodicRestart.time: "00:00:00"
- name: Create IIS website
community.windows.win_iis_website:
name: MyApp
state: started
port: 443
ssl: true
physical_path: C:\inetpub\myapp
application_pool: MyApp-Pool
parameters: |
bindings.[{protocol:'https',bindingInformation:'*:443:app.example.com'}]
- name: Deploy application files
ansible.windows.win_copy:
src: files/myapp/
dest: C:\inetpub\myapp\
notify: Recycle app pool
- name: Deploy web.config
ansible.windows.win_template:
src: templates/web.config.j2
dest: C:\inetpub\myapp\web.config
notify: Recycle app pool
handlers:
- name: Recycle app pool
community.windows.win_iis_webapppool:
name: MyApp-Pool
state: restarted
Pattern 4: Registry and Group Policy
---
- name: Configure Windows security baseline via registry
hosts: windows_servers
tasks:
- name: Disable SMBv1
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
name: SMB1
data: 0
type: dword
- name: Enable Windows Firewall for all profiles
ansible.windows.win_shell: |
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
- name: Configure audit policies
ansible.windows.win_shell: |
auditpol /set /subcategory:"Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Account Lockout" /success:enable /failure:enable
auditpol /set /subcategory:"Special Logon" /success:enable /failure:enable
auditpol /set /subcategory:"Security Group Management" /success:enable
auditpol /set /subcategory:"User Account Management" /success:enable /failure:enable
- name: Set password policy
ansible.windows.win_shell: |
net accounts /minpwlen:15 /maxpwage:365 /minpwage:1 /uniquepw:24 /lockoutthreshold:5
- name: Configure Windows Update settings
ansible.windows.win_regedit:
path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU
name: "{{ item.name }}"
data: "{{ item.value }}"
type: dword
loop:
- { name: "NoAutoUpdate", value: 0 }
- { name: "AUOptions", value: 3 }
- { name: "ScheduledInstallDay", value: 0 }
- { name: "ScheduledInstallTime", value: 3 }
- name: Disable unnecessary services
ansible.windows.win_service:
name: "{{ item }}"
start_mode: disabled
state: stopped
loop:
- RemoteRegistry
- Spooler
- TapiSrv
- SNMPTRAP
failed_when: false
Pattern 5: Windows Fleet Inventory
---
- name: Collect Windows fleet inventory
hosts: windows_servers
tasks:
- name: Gather Windows facts
ansible.builtin.setup:
- name: Get installed software
ansible.windows.win_shell: |
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher |
ConvertTo-Json
register: installed_software
- name: Get disk information
ansible.windows.win_shell: |
Get-WmiObject Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID, @{N='SizeGB';E={[math]::Round($_.Size/1GB,2)}},
@{N='FreeGB';E={[math]::Round($_.FreeSpace/1GB,2)}},
@{N='UsedPct';E={[math]::Round(($_.Size-$_.FreeSpace)/$_.Size*100,1)}} |
ConvertTo-Json
register: disk_info
- name: Get pending updates
ansible.windows.win_updates:
state: searched
category_names:
- CriticalUpdates
- SecurityUpdates
register: pending_updates
- name: Build inventory report
ansible.builtin.template:
src: windows-inventory-report.html.j2
dest: "reports/{{ inventory_hostname }}_inventory.html"
delegate_to: localhost
vars:
os_version: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
software_list: "{{ installed_software.stdout | from_json }}"
disks: "{{ disk_info.stdout | from_json }}"
updates_pending: "{{ pending_updates.found_update_count }}"
Troubleshooting
WinRM Connection Failed
# Test WinRM from control node
python3 -c "
import winrm
s = winrm.Session('https://server:5986', auth=('user', 'pass'), transport='credssp', server_cert_validation='validate')
print(s.run_cmd('hostname'))
"
# Check WinRM listeners on Windows
winrm enumerate winrm/config/listener
# Verify HTTPS listener exists
winrm get winrm/config/service
CredSSP Authentication Errors
# On Windows target
Enable-WSManCredSSP -Role Server -Force
# On control node (if Windows)
Enable-WSManCredSSP -Role Client -DelegateComputer *.example.com -Force
PowerShell Execution Policy
# Set in ansible.cfg or group_vars
ansible_winrm_operation_timeout_sec: 120
ansible_winrm_read_timeout_sec: 150
Related Articles
- Configure Windows for Ansible
- Test Windows: win_ping
- Install Software with Chocolatey
- Windows Registry Management
- Copy Files to Windows
Conclusion
Enterprise Windows automation with Ansible covers the full stack: Active Directory management with microsoft.ad, Windows Updates with rolling patch windows, IIS deployment with app pool configuration, registry-based security hardening, and fleet-wide inventory collection. Use CredSSP for domain-joined authentication, serial for rolling updates that don't take down the whole farm, and AAP surveys for self-service AD user creation. The same Ansible patterns that work for Linux — roles, inventory groups, handlers, Vault — work identically for Windows at enterprise scale.