Windows Automation with Ansible
Overview
Ansible can manage Windows systems using WinRM (Windows Remote Management) instead of SSH. This allows you to automate Windows servers and desktops alongside your Linux infrastructure.
Requirements:
- Windows 7/Server 2008 R2 or later
- PowerShell 3.0 or later (5.1+ recommended)
- WinRM configured and enabled
pywinrmPython package on control node
Setting Up Windows for Ansible
1. Install pywinrm on Control Node
pip install pywinrm
# For Kerberos authentication
pip install pywinrm[kerberos]
2. Configure WinRM on Windows
Run this PowerShell script on the Windows host:
# Download and run Ansible's WinRM setup script
$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
3. Verify WinRM Configuration
winrm enumerate winrm/config/Listener
Inventory Configuration
Windows hosts require special variables in inventory:
[windows]
win-server1 ansible_host=192.168.1.100
[windows:vars]
ansible_user=Administrator
ansible_password=SecurePassword123
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore
ansible_port=5986
Security Warning:
Never store passwords in plain text! Use Ansible Vault to encrypt sensitive data or use certificate-based authentication.
Authentication Methods
Basic Authentication
ansible_winrm_transport=basic
ansible_port=5985
NTLM (Recommended for Domain)
ansible_winrm_transport=ntlm
ansible_port=5985
Kerberos (Most Secure)
ansible_winrm_transport=kerberos
ansible_user=user@DOMAIN.COM
Certificate-Based
ansible_winrm_transport=certificate
ansible_winrm_cert_pem=/path/to/cert.pem
ansible_winrm_cert_key_pem=/path/to/key.pem
Common Windows Modules
win_ping - Test Connection
---
- name: Test Windows connectivity
hosts: windows
tasks:
- name: Ping Windows host
win_ping:
win_feature - Manage Windows Features
---
- name: Install IIS Web Server
hosts: windows
tasks:
- name: Install IIS
win_feature:
name: Web-Server
state: present
include_management_tools: yes
- name: Install ASP.NET
win_feature:
name: Web-Asp-Net45
state: present
win_package - Install Software
---
- name: Install software
hosts: windows
tasks:
- name: Install 7-Zip
win_package:
path: https://www.7-zip.org/a/7z1900-x64.msi
product_id: '{23170F69-40C1-2702-1900-000001000000}'
state: present
- name: Install Chrome
win_chocolatey:
name: googlechrome
state: present
win_service - Manage Services
---
- name: Manage Windows services
hosts: windows
tasks:
- name: Ensure W3SVC is running
win_service:
name: W3SVC
state: started
start_mode: auto
- name: Stop and disable a service
win_service:
name: Spooler
state: stopped
start_mode: disabled
win_file - File Operations
---
- name: File management
hosts: windows
tasks:
- name: Create directory
win_file:
path: C:\Temp\MyApp
state: directory
- name: Remove file
win_file:
path: C:\Temp\oldfile.txt
state: absent
win_copy - Copy Files
---
- name: Copy files to Windows
hosts: windows
tasks:
- name: Copy configuration file
win_copy:
src: /local/path/config.ini
dest: C:\App\config.ini
- name: Copy with backup
win_copy:
src: important.txt
dest: C:\Data\important.txt
backup: yes
win_shell & win_command - Execute Commands
---
- name: Execute commands
hosts: windows
tasks:
- name: Run PowerShell command
win_shell: |
Get-Service | Where-Object {$_.Status -eq "Running"}
- name: Execute command
win_command: ipconfig /all
win_regedit - Registry Management
---
- name: Manage Windows Registry
hosts: windows
tasks:
- name: Set registry value
win_regedit:
path: HKLM:\Software\MyApp
name: Version
data: 1.0.0
type: string
- name: Delete registry key
win_regedit:
path: HKLM:\Software\OldApp
state: absent
win_updates - Windows Updates
---
- name: Install Windows updates
hosts: windows
tasks:
- name: Install all security updates
win_updates:
category_names:
- SecurityUpdates
- CriticalUpdates
reboot: yes
Complete Windows Playbook Example
---
- name: Configure Windows Web Server
hosts: windows
gather_facts: yes
tasks:
- name: Install IIS
win_feature:
name: Web-Server
state: present
include_management_tools: yes
- name: Create website directory
win_file:
path: C:\inetpub\mysite
state: directory
- name: Copy website files
win_copy:
src: ./website/
dest: C:\inetpub\mysite\
- name: Configure IIS site
win_iis_website:
name: MySite
state: started
port: 80
physical_path: C:\inetpub\mysite
- name: Open firewall for HTTP
win_firewall_rule:
name: HTTP
localport: 80
action: allow
direction: in
protocol: tcp
state: present
enabled: yes
- name: Ensure service is running
win_service:
name: W3SVC
state: started
start_mode: auto
Troubleshooting
Test WinRM Connection
ansible windows -m win_ping
Check WinRM from Control Node
python3 -c "import winrm; s=winrm.Session('192.168.1.100', auth=('user', 'pass')); print(s.run_cmd('ipconfig'))"
Common Issues
- SSL Certificate Errors: Set
ansible_winrm_server_cert_validation=ignore(development only) - Authentication Failures: Verify username/password and try different transports
- Timeout Errors: Check firewall rules allow ports 5985 (HTTP) and 5986 (HTTPS)
- Memory Errors: Increase WinRM memory:
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'
Active Directory Management
Domain Controller Setup
---
- name: Configure Active Directory Domain Controller
hosts: dc_servers
gather_facts: yes
tasks:
- name: Install AD-Domain-Services feature
win_feature:
name: AD-Domain-Services
include_management_tools: yes
include_sub_features: yes
state: present
- name: Promote server to Domain Controller
win_domain:
dns_domain_name: example.com
safe_mode_password: P@ssw0rd123!
domain_netbios_name: EXAMPLE
register: dc_promotion
- name: Reboot after DC promotion
win_reboot:
when: dc_promotion.reboot_required
- name: Wait for Active Directory services
win_wait_for:
port: 389
timeout: 600
Domain User Management
---
- name: Manage Active Directory Users
hosts: domain_controllers
gather_facts: no
vars:
domain_users:
- name: john.doe
firstname: John
surname: Doe
password: TempPass123!
ou: "OU=Users,DC=example,DC=com"
groups: ['Domain Users', 'IT Staff']
- name: jane.smith
firstname: Jane
surname: Smith
password: TempPass456!
ou: "OU=Users,DC=example,DC=com"
groups: ['Domain Users', 'Managers']
tasks:
- name: Create domain users
win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
password: "{{ item.password }}"
path: "{{ item.ou }}"
state: present
enabled: yes
password_never_expires: no
user_cannot_change_password: no
change_password_at_logon: yes
loop: "{{ domain_users }}"
- name: Add users to groups
win_domain_group_membership:
name: "{{ item.1 }}"
members:
- "{{ item.0.name }}"
state: present
loop: "{{ domain_users | subelements('groups') }}"
- name: Disable inactive user
win_domain_user:
name: old.user
enabled: no
state: present
Organizational Units (OUs)
---
- name: Manage Organizational Units
hosts: domain_controllers
tasks:
- name: Create OU structure
win_domain_ou:
name: "{{ item.name }}"
path: "{{ item.path }}"
state: present
protected: yes
loop:
- { name: 'IT', path: 'DC=example,DC=com' }
- { name: 'Servers', path: 'OU=IT,DC=example,DC=com' }
- { name: 'Workstations', path: 'OU=IT,DC=example,DC=com' }
- { name: 'ServiceAccounts', path: 'OU=IT,DC=example,DC=com' }
- name: Move computer to OU
win_domain_computer:
name: WEB01
dns_hostname: web01.example.com
ou: "OU=Servers,OU=IT,DC=example,DC=com"
state: present
Group Policy Management
---
- name: Manage Group Policy
hosts: domain_controllers
tasks:
- name: Create Group Policy Object
win_shell: |
Import-Module GroupPolicy
New-GPO -Name "Security Baseline" -Comment "Standard security settings"
args:
creates: 'C:\Windows\SYSVOL\domain\Policies\{GPO-GUID}'
- name: Configure password policy
win_domain_group_policy:
name: "Default Domain Policy"
settings:
- name: MinimumPasswordLength
value: 14
- name: PasswordComplexity
value: 1
- name: MaximumPasswordAge
value: 90
- name: Link GPO to OU
win_shell: |
Import-Module GroupPolicy
New-GPLink -Name "Security Baseline" -Target "OU=Servers,DC=example,DC=com"
PowerShell DSC with Ansible
What is PowerShell DSC?
Desired State Configuration (DSC) is a PowerShell management platform for configuring Windows systems. Ansible can invoke DSC resources for advanced Windows automation.
Using DSC from Ansible
---
- name: Configure Windows with DSC
hosts: windows
tasks:
- name: Ensure a Windows feature via DSC
win_dsc:
resource_name: WindowsFeature
Name: Web-Server
Ensure: Present
IncludeAllSubFeature: true
- name: Configure registry via DSC
win_dsc:
resource_name: Registry
Key: 'HKLM:\Software\MyCompany'
ValueName: Version
ValueData: '1.0.0'
ValueType: String
Ensure: Present
- name: Create local user via DSC
win_dsc:
resource_name: User
UserName: ServiceAccount
Password: "{{ service_account_password }}"
Ensure: Present
PasswordNeverExpires: true
PasswordChangeNotAllowed: true
Complex DSC Configuration
---
- name: Advanced DSC Configuration
hosts: windows
tasks:
- name: Configure IIS with DSC
win_dsc:
resource_name: xWebsite
Name: MyWebSite
PhysicalPath: 'C:\inetpub\wwwroot\mysite'
State: Started
BindingInfo:
- Protocol: http
Port: 80
IPAddress: '*'
- Protocol: https
Port: 443
IPAddress: '*'
CertificateThumbprint: "{{ ssl_cert_thumbprint }}"
- name: Configure SQL Server with DSC
win_dsc:
resource_name: SqlServerConfiguration
InstanceName: MSSQLSERVER
OptionName: max server memory (MB)
OptionValue: 8192
RestartService: false
- name: Configure Windows Firewall with DSC
win_dsc:
resource_name: xFirewall
Name: Allow-HTTP
DisplayName: "Allow HTTP Traffic"
Ensure: Present
Enabled: True
Direction: Inbound
LocalPort: 80
Protocol: TCP
Action: Allow
Domain Join Operations
---
- name: Join Windows computers to domain
hosts: workstations
vars:
domain_name: example.com
domain_admin: Administrator
domain_password: "{{ vault_domain_password }}"
target_ou: "OU=Workstations,DC=example,DC=com"
tasks:
- name: Set DNS server to Domain Controller
win_dns_client:
adapter_names: '*'
ipv4_addresses:
- 192.168.1.10
- 192.168.1.11
- name: Join computer to domain
win_domain_membership:
dns_domain_name: "{{ domain_name }}"
domain_admin_user: "{{ domain_admin }}@{{ domain_name }}"
domain_admin_password: "{{ domain_password }}"
domain_ou_path: "{{ target_ou }}"
state: domain
register: domain_join
- name: Reboot after domain join
win_reboot:
msg: "Rebooting after domain join"
when: domain_join.reboot_required
- name: Verify domain membership
win_shell: (Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
register: domain_check
failed_when: domain_check.stdout | trim != 'True'
Windows Server Roles and Features
File Server Configuration
---
- name: Configure File Server
hosts: file_servers
become_method: runas
tasks:
- name: Install File Server role
win_feature:
name:
- FS-FileServer
- FS-DFS-Namespace
- FS-DFS-Replication
- FS-Resource-Manager
state: present
include_management_tools: yes
- name: Create shared directories
win_file:
path: "{{ item }}"
state: directory
loop:
- D:\Shares\Public
- D:\Shares\Departments
- D:\Shares\Projects
- name: Configure SMB shares
win_share:
name: "{{ item.name }}"
path: "{{ item.path }}"
full: "{{ item.full }}"
change: "{{ item.change }}"
read: "{{ item.read }}"
state: present
loop:
- name: Public
path: D:\Shares\Public
full: Domain Admins
change: Domain Users
read: Everyone
- name: Departments
path: D:\Shares\Departments
full: Domain Admins
change: Department Heads
read: Domain Users
- name: Set NTFS permissions
win_acl:
path: "{{ item.path }}"
user: "{{ item.user }}"
rights: "{{ item.rights }}"
type: allow
state: present
inherit: ContainerInherit, ObjectInherit
propagation: None
loop:
- { path: 'D:\Shares\Public', user: 'Domain Users', rights: 'Modify' }
- { path: 'D:\Shares\Departments', user: 'Department Heads', rights: 'FullControl' }
DHCP Server Configuration
---
- name: Configure DHCP Server
hosts: dhcp_servers
tasks:
- name: Install DHCP Server role
win_feature:
name: DHCP
include_management_tools: yes
state: present
- name: Configure DHCP scope
win_shell: |
Add-DhcpServerv4Scope -Name "Corporate Network" `
-StartRange 192.168.1.100 `
-EndRange 192.168.1.200 `
-SubnetMask 255.255.255.0 `
-State Active
- name: Set DHCP server options
win_shell: |
Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 `
-Router 192.168.1.1 `
-DnsServer 192.168.1.10,192.168.1.11 `
-DnsDomain example.com
- name: Authorize DHCP in Active Directory
win_shell: |
Add-DhcpServerInDC -DnsName dhcp01.example.com -IPAddress 192.168.1.50
Certificate Services
---
- name: Manage Windows Certificates
hosts: windows
tasks:
- name: Install certificate from file
win_certificate_store:
path: C:\Certs\server.pfx
password: CertPassword123
state: present
store_location: LocalMachine
store_name: My
- name: Export certificate
win_certificate_store:
thumbprint: "{{ cert_thumbprint }}"
state: exported
file_type: pfx
path: C:\Backup\cert.pfx
password: BackupPass123
store_location: LocalMachine
store_name: My
- name: Request certificate from CA
win_shell: |
$cert = Get-Certificate -Template WebServer `
-DnsName web01.example.com `
-CertStoreLocation Cert:\LocalMachine\My
$cert.Certificate.Thumbprint
register: new_cert
- name: Remove expired certificates
win_shell: |
Get-ChildItem Cert:\LocalMachine\My |
Where-Object { $_.NotAfter -lt (Get-Date) } |
Remove-Item
Windows Clustering
---
- name: Configure Windows Failover Cluster
hosts: cluster_nodes
tasks:
- name: Install Failover Clustering feature
win_feature:
name: Failover-Clustering
include_management_tools: yes
state: present
- name: Create failover cluster
win_shell: |
New-Cluster -Name AppCluster `
-Node {{ groups['cluster_nodes'] | join(',') }} `
-StaticAddress 192.168.1.100 `
-NoStorage
when: inventory_hostname == groups['cluster_nodes'][0]
run_once: true
- name: Add cluster quorum
win_shell: |
Set-ClusterQuorum -Cluster AppCluster `
-FileShareWitness \\fileserver\quorum\AppCluster
when: inventory_hostname == groups['cluster_nodes'][0]
- name: Configure cluster network
win_shell: |
(Get-ClusterNetwork -Cluster AppCluster |
Where-Object {$_.Address -eq "192.168.1.0"}).Name = "Production"
Windows Automation Best Practices
Handling Reboots
---
- name: Proper reboot handling
hosts: windows
tasks:
- name: Install updates requiring reboot
win_updates:
category_names:
- SecurityUpdates
- CriticalUpdates
reboot: no
register: update_result
- name: Reboot if required
win_reboot:
reboot_timeout: 600
post_reboot_delay: 120
when: update_result.reboot_required
- name: Wait for system to be ready
win_wait_for_process:
process_name_exact: TrustedInstaller
state: absent
timeout: 600
when: update_result.reboot_required
Error Handling
---
- name: Windows error handling
hosts: windows
tasks:
- name: Attempt risky operation
block:
- name: Stop critical service
win_service:
name: MyAppService
state: stopped
- name: Update application
win_copy:
src: newapp.exe
dest: C:\App\app.exe
- name: Start service
win_service:
name: MyAppService
state: started
rescue:
- name: Rollback on failure
win_copy:
src: C:\App\Backup\app.exe
dest: C:\App\app.exe
remote_src: yes
- name: Start service with old version
win_service:
name: MyAppService
state: started
- name: Send alert
win_shell: |
Send-MailMessage -To "ops@example.com" `
-Subject "Deployment Failed" `
-Body "Application update failed, rolled back to previous version"
always:
- name: Log deployment attempt
win_eventlog:
source: AnsibleDeployment
event_id: 1000
message: "Deployment attempt completed"
Performance Optimization
---
- name: Optimize Windows Automation Performance
hosts: windows
vars:
ansible_winrm_operation_timeout_sec: 60
ansible_winrm_read_timeout_sec: 70
tasks:
- name: Increase WinRM memory limit
win_shell: |
Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024
Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 1024
- name: Enable PowerShell remoting optimizations
win_shell: |
Set-Item WSMan:\localhost\MaxConcurrentOperationsPerUser 100
Set-Item WSMan:\localhost\Service\MaxConcurrentOperations 200
- name: Use async for long-running tasks
win_updates:
category_names:
- Updates
state: installed
async: 3600
poll: 0
register: update_task
- name: Continue with other work
win_feature:
name: Telnet-Client
state: present
- name: Check async task status
async_status:
jid: "{{ update_task.ansible_job_id }}"
register: job_result
until: job_result.finished
retries: 360
delay: 10
Hybrid Cloud Windows Management
---
- name: Manage Windows across On-Prem and Azure
hosts: localhost
gather_facts: no
tasks:
- name: Get Azure Windows VMs
azure_rm_virtualmachine_info:
resource_group: ProductionRG
tags:
- Environment:Production
- OS:Windows
register: azure_vms
- name: Add Azure VMs to inventory
add_host:
name: "{{ item.name }}"
ansible_host: "{{ item.properties.networkProfile.networkInterfaces[0].properties.ipConfigurations[0].properties.privateIPAddress }}"
groups: windows,azure_windows
loop: "{{ azure_vms.vms }}"
- name: Configure all Windows servers (on-prem + cloud)
include_role:
name: windows_baseline
delegate_to: "{{ item }}"
loop: "{{ groups['windows'] }}"
Security Hardening
---
- name: Windows Security Hardening
hosts: windows
tasks:
- name: Disable unnecessary services
win_service:
name: "{{ item }}"
state: stopped
start_mode: disabled
loop:
- RemoteRegistry
- SSDPSRV
- upnphost
- WMPNetworkSvc
- name: Configure Windows Firewall
win_firewall:
state: enabled
profiles:
- Domain
- Private
- Public
- name: Enable audit policy
win_audit_policy_system:
subcategory: Logon
audit_type: success, failure
- name: Configure account lockout policy
win_security_policy:
section: System Access
key: LockoutBadCount
value: 5
- name: Disable SMBv1
win_optional_feature:
name: SMB1Protocol
state: absent
- name: Enable Windows Defender
win_shell: |
Set-MpPreference -DisableRealtimeMonitoring $false
Update-MpSignature
Best Practices
- Use HTTPS (port 5986) in production environments
- Implement certificate-based or Kerberos authentication for security
- Always store credentials in Ansible Vault, never in plain text
- Use
win_modules specifically designed for Windows - Test playbooks with
--checkmode before production runs - Handle reboots properly using
win_rebootmodule - Use async tasks for long-running Windows operations
- Increase WinRM timeouts for slow operations
- Enable detailed logging for troubleshooting
- Use DSC for complex configurations when appropriate
- Implement proper error handling with block/rescue
- Test domain operations in non-production first
- Document OU paths and naming conventions
- Use service accounts with minimum required privileges
- Monitor WinRM memory usage for large-scale deployments
Next Steps
- Learn about Ansible Vault for securing Windows credentials
- Explore Roles for organizing Windows automation
- Try Windows examples in the Playground
- Explore Cloud Automation for Azure Windows VMs