DEV Community

Sirisharaju Kamparaju
Sirisharaju Kamparaju

Posted on

Automating Windows Server Setup with Ansible: My DevOps Journey (Part 2)

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: auto

  • name: 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)