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

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5168

    #1

    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/an...ForAnsible.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




    More...
Working...