In my previous blog, I walked through how I automated Linux server setup using Ansible — SSH hardening, roles, and playbooks. If you haven't read that yet, check out Part 1 here.
In this post, I'll focus entirely on the Windows side — how I configured WinRM, built a reusable Windows role, and tied everything together into one master playbook that manages both Linux and Windows servers.
Windows Automation Feels Different at First
When I first tried to automate Windows servers with Ansible, it didn't feel anything like Linux. On Linux, Ansible just connects over SSH and you're off. Windows doesn't work that way.
A few things that caught my attention early on:
- Windows uses WinRM instead of SSH — that's how Ansible communicates with it
- Fresh Windows servers don't have WinRM enabled — I had to manually turn it on the first time
- The modules are completely different — no apt, no service — everything goes through the ansible.windows collection
- Once I got my head around these differences, the rest came together pretty smoothly.
**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
Adding Windows Hosts to the Inventory
I added the Windows servers into the same inventory file I was already using for Linux. The connection settings are different but the structure stays clean:
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 This
Before this setup, configuring a new Windows server meant RDP-ing in, clicking through settings, and hoping I didn't miss anything. Now I just add the host to the inventory and run the playbook. Same result every time, no matter how many servers I'm dealing with.
Combined with Part 1, I now have a single automation setup managing both Linux and Windows from one place — and it's honestly one of the most satisfying things I've built so far in my DevOps journey.
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)