In my previous blog, I covered how I automated Linux server provisioning using Ansible — things like SSH hardening, reusable roles, and playbooks. That setup worked really well, so the next thing I wanted to tackle was Windows.
This post is about the Windows side of the setup — enabling WinRM, creating a reusable Windows role, and finally combining both Linux and Windows automation into a single Ansible workflow..
Windows Automation Feels Different at First
I’ll be honest — moving from Linux automation to Windows automation was a little frustrating in the beginning.
With Linux, Ansible over SSH just works. Most tutorials online assume that setup, so things feel straightforward pretty quickly. Windows was different.
The first thing I learned was that Ansible talks to Windows through WinRM instead of SSH. That meant extra configuration before I could even run my first playbook.
A few things tripped me up early on:
WinRM isn’t enabled by default on fresh Windows servers
The modules are completely different from Linux
Even simple tasks use different syntax and collections
Some errors from WinRM are vague and take time to troubleshoot
At one point I thought my firewall rules were wrong, but it turned out I had messed up the WinRM transport settings in the inventory file.
Once I got past those initial issues though, the setup became much smoother.
Note: Port 5985 should be allowed in firewall.
**First Thing — Bootstrap WinRM (Just Once)
Before Ansible can do anything on a Windows server, WinRM needs to be enabled. I ran this PowerShell script once on each new Windows machine — after that, Ansible handles everything:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$url = "https://raw.githubusercontent.com/ansible/ansible/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
The first successful win_ping honestly felt like progress because I’d already spent a while debugging connection issues before getting there.
Adding Windows Hosts to the Inventory
I kept windows and linux in the same inventory file because I wanted one central place to manage everything.
The structure stayed mostly the same, but the connection settings were obviously different:
all:
children:
linux_servers:
hosts:
linux-01:
ansible_host: 10.0.1.10
ansible_user: ec2-user
ansible_ssh_private_key_file: ~/.ssh/id_rsa
windows_servers:
hosts:
win-01:
ansible_host: 10.0.2.10
ansible_user: Administrator
ansible_password: "{{ vault_win_password }}"
ansible_connection: winrm
ansible_winrm_transport: ntlm
ansible_port: 5985
The Windows password is vaulted using ansible-vault — I never put credentials in plain text. That's just a habit I've built early on and I'd recommend everyone do the same.
Building the Windows Role
I kept the same role-based structure I used for Linux. Here's how the Windows role looks:
roles/
windows_setup/
├── tasks/main.yml
└── defaults/main.yml
roles/windows_setup/defaults/main.yml
name: Ensure WinRM service is running and set to auto start
ansible.windows.win_service:
name: WinRM
state: started
start_mode: autoname: Disable unencrypted WinRM traffic
ansible.windows.win_shell: |
winrm set winrm/config/service '@{AllowUnencrypted="false"}'name: Configure Windows Firewall to allow WinRM
ansible.windows.win_firewall_rule:
name: WinRM HTTP
localport: "{{ winrm_port }}"
action: allow
direction: in
protocol: tcp
state: present
enabled: true-
name: Check for available Windows Security Updates
ansible.windows.win_updates:
category_names:- SecurityUpdates state: searched register: update_result
name: Display available updates
ansible.builtin.debug:
msg: "{{ update_result.updates | length }} security update(s) available"
The Windows Playbook
- name: Configure Windows Servers
hosts: windows_servers
roles:
- windows_setup
One thing I noticed here — there's no become: true like I used on Linux. Windows doesn't use sudo. The Administrator account takes care of privilege escalation directly.
Bringing It All Together — site.yml
This is the part I enjoyed the most. One playbook, one command, both Linux and Windows configured together:
- import_playbook: playbooks/linux_setup.yml
- import_playbook: playbooks/windows_setup.yml
And to run everything:
ansible-playbook site.yml -i inventory/hosts.yml --ask-vault-pass
That's it. Ansible runs through Linux first, then Windows — clean and consistent every single time.
ansible windows_servers -i inventory/hosts.yml -m ansible.windows.win_ping
If I get pong back, I know I'm good to go.
WinRM transport depends on your environment. I used ntlm since my servers weren't in a domain. If you're working in an Active Directory setup, kerberos is the better and more secure option.
Don't mix Linux and Windows modules. Early on I made the mistake of trying to use a Linux module on a Windows host — it fails and the error isn't always obvious. Stick to ansible.windows.* for everything Windows-related.
What Changed After Automating This
Before automation, setting up a Windows server usually meant opening RDP, clicking through configuration screens, enabling services manually, and hoping I didn’t forget something important.
Now the process is much simpler:
- add the server to inventory,
- run the playbook,
- wait a few minutes,
- done.
That consistency alone made the effort worth it.
Combined with the Linux setup from Part 1, I now have a single Ansible environment managing both Linux and Windows servers from one place. Getting both platforms working together took more troubleshooting than I expected, but finishing it felt genuinely rewarding.
Coming Up in Part 3
I'm planning to cover:
User management across Linux and Windows
Scheduling automated patching
Plugging Ansible into a CI/CD pipeline
Drop your questions or thoughts in the comments — always happy to discuss!
— Sireesha
Top comments (0)