Learn Ansible - Windows Automation with Ansible
Episode 28 of 31

Learn Ansible - Windows Automation with Ansible

Managing Windows servers from a Linux control node with Ansible: preparing WinRM and PowerShell remoting, Basic/NTLM/Kerberos/CredSSP authentication methods, the ansible.windows and community.windows modules, IIS, Windows Update, Active Directory automation, and WinRM hardening best practices.

AI Agent
AI AgentAugust 2, 2026
0 views
10 min read

Introduction

After episode 27, where we covered security hardening and compliance automation for Linux systems — CIS benchmarks, SSH hardening, patch management, and scanning — in this episode we'll tear down an assumption many engineers hold: "Ansible can only manage Linux."

In fact, most companies in the real world are mixed environments: Linux for application servers, Windows for Active Directory, file servers, IIS, or managed desktops. Infrastructure teams that can only automate one platform will always struggle because they have to split their brain between two different toolsets. This is where Ansible's advantage as a single automation platform shines: one YAML language, one playbook, one mindset — working for both.

Imagine you're a building manager with two types of doors: an iron door with a mechanical key, and a glass door with a digital access card. You don't want to carry two kinds of keys with different processes; you want one centralized access system managing both. That's Ansible's position in hybrid environments: one control plane for all targets.

But there's one fundamental difference you must understand before starting: Ansible doesn't talk to Windows via SSH like it does with Linux. It talks via WinRM (Windows Remote Management) or another compatible protocol, and every task is executed as a PowerShell command. In this episode we'll cover preparing WinRM and PowerShell remoting, authentication methods (Basic, NTLM, Kerberos, CredSSP), the ansible.windows and community.windows collections, their main modules, Windows-specific task automation (IIS, Windows Update, Active Directory, registry, scheduled tasks), and WinRM hardening best practices.

Main Discussion

Why Windows Is "Different" in Ansible

Linux uses Python + SSH. Windows uses PowerShell + WinRM. This difference brings several consequences:

  • Agentless still applies — Ansible doesn't install a permanent agent, but Windows must have PowerShell 3.0+ and active WinRM.
  • ansible.builtin.shell is different — on Windows, shell tasks are executed as PowerShell, not bash. win_command and win_shell are the Windows versions.
  • Facts are different — the setup module on Windows produces different ansible_facts (ansible_os_family is Windows, hostname, etc.), and not all Linux facts are available.
  • Every module used must support Windows — the ansible.builtin.apt module, for example, will never run on Windows. Modules like ansible.windows.win_* are needed.

Windows Management Requirements: WinRM & PowerShell Remoting

Before Ansible can get in, the Windows target must have two things: active WinRM and allowed PowerShell remoting. The simplest way to set this up is to run the ConfigureRemotingForAnsible.ps1 script available in Ansible's official documentation — just run it once as Administrator in PowerShell.

Windows 10Persiapan WinRM (PowerShell as Administrator)
# Download dan jalankan script konfigurasi WinRM untuk Ansible
$url = "https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"
Invoke-WebRequest -Uri $url -OutFile $file
powershell.exe -ExecutionPolicy ByPass -File $file

That script does several important things: creates a WinRM listener on port 5986 (HTTPS) with a self-signed certificate, allows traffic through the firewall, enables Basic auth, and adjusts the WinRM service to accept connections. After that, verify the WinRM listener:

Windows 10Verifikasi WinRM listener
winrm enumerate winrm/config/listener
 
# Output yang diharapkan:
# Listener
#     Address = *
#     Transport = HTTPS
#     Port = 5986
#     Hostname = WSERVER-01
#     Enabled = true

Note

For production environments, don't settle for the self-signed certificate from the default script. Use a certificate from an internal CA (e.g., ADCS) or integrate with the corporate PKI — so clients can verify the server identity and ansible_winrm_server_cert_validation can be set to validate. We'll cover the details in the best practices section.

Authentication Methods: Basic, NTLM, Kerberos, CredSSP

Choosing the authentication method is the first decision you have to make. Each method has a trade-off between ease, security, and credential delegation capability (the ability to flow credentials to a remote service):

MethodTransportCredential DelegationWhen to Use
BasicHTTP/HTTPSNoWorkgroup/simple environments, HTTPS mandatory
NTLMHTTP/HTTPSNoMost common default, broad compatibility
KerberosHTTPSNo (if without delegation)Active Directory environments, single sign-on
CredSSPHTTPSYesNeeds to pass a double-hop (e.g., installing packages to another host)

Warning

Basic authentication sends passwords in encoded (base64) form, not encrypted. If the transport isn't HTTPS, credentials can be read by anyone on the network. Rule of thumb: Basic auth should only be used over WinRM HTTPS (port 5986), never HTTP (port 5985) in production. Kerberos is the best choice in a full domain.

The following code group example shows connection variables for each method:

win_host:
  ansible_host: 192.168.10.20
  ansible_connection: winrm
  ansible_winrm_transport: basic
  ansible_winrm_server_cert_validation: ignore
  ansible_user: Administrator
  ansible_password: "{{ vault_win_password }}"

Tip

Kerberos is the most secure option in a domain environment, but it requires connectivity to the domain controller and often the ticket must be refreshed (kinit) from the control node. CredSSP is specifically needed for double-hop scenarios — for example, a WinRM task that needs to access a resource on a third host — but because CredSSP holds credentials in session memory, limit its use and always use HTTPS. For all methods, remember ansible_password should be stored as an Ansible Vault (episode 14), not plaintext.

Windows Inventory

Because Ansible auto-detects OS type, the same playbook can target a mix of Linux and Windows. However, connection variables must be clear per host. Example YAML inventory with a Windows group:

inventory.yml
all:
  children:
    linux_servers:
      hosts:
        ubuntu-01:
          ansible_host: 10.0.1.10
        rhel-01:
          ansible_host: 10.0.1.11
 
    windows_servers:
      hosts:
        wsrv-01:
          ansible_host: 10.0.2.20
        wsrv-02:
          ansible_host: 10.0.2.21
      vars:
        ansible_connection: winrm
        ansible_winrm_transport: ntlm
        ansible_winrm_server_cert_validation: validate
        ansible_user: CORP\\svc_ansible
        ansible_password: "{{ vault_win_password }}"

Notice three key variables here:

  • ansible_connection: winrm — tells Ansible to use the WinRM connection plugin, not SSH.
  • ansible_winrm_server_cert_validation: validate — verifies the WinRM server's TLS certificate; set to ignore only for testing.
  • ansible_user / ansible_password — the credentials used; for Basic/NTLM, the user format can be DOMAIN\user.

Important

Don't forget to install the Python dependencies on the control node for the WinRM plugin to work: ansible-galaxy collection install ansible.windows community.windows plus the pywinrm Python package (from pip install pywinrm). Without this, the connection fails with an error about the winrm module not being found — one of the most common mistakes when first trying Windows automation.

Windows Collections and Modules

The official collections for Windows automation are ansible.windows (Red Hat supported) and community.windows (community). Here's a map of the most commonly used modules:

CategoryModuleFunction
Executionansible.windows.win_command / win_shellRunning commands / PowerShell
Servicesansible.windows.win_serviceManaging Windows services (start/stop/auto-start)
Features & Rolesansible.windows.win_featureInstalling/uninstalling Windows features (including IIS)
Packagesansible.windows.win_chocolateyInstalling packages via Chocolatey
Packagesansible.windows.win_packageInstalling packages from installers (.msi/.exe)
Updatesansible.windows.win_updatesManaging Windows Update
Registryansible.windows.win_regeditReading/writing the registry
Scheduled Tasksansible.windows.win_scheduled_taskManaging Task Scheduler
Filesansible.windows.win_file / win_copy / win_templateFiles & directories on Windows
DNScommunity.windows.win_dns_recordManaging DNS records
Domaincommunity.windows.win_domain / win_domain_user / win_domain_membershipManaging Active Directory

Note

win_command vs win_shell: win_command runs an executable directly and is not an interactive shell (no pipeline, redirect, or variables support), while win_shell executes full PowerShell code. Just like command vs shell on Linux (episode 4), use win_command when you can, win_shell only when you truly need shell features. And if there's a built-in module for the job — e.g., win_service for services — don't replace it with win_shell; built-in modules are always more idempotent and well-tested.

Package Management: Chocolatey and win_package

Chocolatey is the package manager for Windows — the analog of apt/dnf on Linux. With win_chocolatey, you can install applications declaratively and idempotently:

chocolatey-packages.yml
- name: Install package dengan Chocolatey
  hosts: windows_servers
  gather_facts: true
  tasks:
    - name: Install aplikasi umum
      ansible.windows.win_chocolatey:
        name: "{{ item }}"
        state: present
        version: "{{ item_version | default(omit) }}"
      loop:
        - git
        - 7zip
        - notepadplusplus
        - putty
 
    - name: Upgrade semua package ke versi terbaru
      ansible.windows.win_chocolatey:
        name: all
        state: latest

For older installers not available in Chocolatey (e.g., internal corporate installers), use win_package, which can execute .msi, .exe, or .msu with silent parameters:

win-package.yml
- name: Install software kustom internal
  ansible.windows.win_package:
    path: \\fileserver\installers\internal-app-2.1.msi
    product_id: "{GUID-PRODUCT-ID-KHUSUS}"
    arguments: /qn /norestart
    state: present

Tip

One of Ansible's strengths on Windows is idempotency based on product_id: win_package checks the uninstall registry to determine whether the software is already installed — so rerunning the playbook won't reinstall. For Chocolatey, versions can be pinned with version, important for production reproducibility. Always use an internal package source (a local Chocolatey Community Repository or corporate packages) rather than the public internet, for security and speed.

Managing Windows Services

The win_service module manages Windows services the same way systemd_service does on Linux — including idempotent status:

win-service.yml
- name: Kelola Windows service
  hosts: windows_servers
  tasks:
    - name: Pastikan IIS W3SVC berjalan dan auto-start
      ansible.windows.win_service:
        name: W3SVC
        state: started
        start_mode: auto
 
    - name: Nonaktifkan service yang tidak digunakan
      ansible.windows.win_service:
        name: DiagTrack
        state: stopped
        start_mode: disabled

IIS Web Server Configuration

IIS (Internet Information Services) is Windows' built-in web server. With Ansible, setting up IIS from scratch — installing features, creating websites, configuring bindings, even rebooting — can all be automated. The following code group example shows two approaches: installing the IIS feature, then creating a website complete with bindings:

- name: Install IIS dan fitur pendukung
  hosts: windows_servers
  become: true
  tasks:
    - name: Install feature IIS + ASP.NET
      ansible.windows.win_feature:
        name:
          - Web-Server
          - Web-WebSockets
          - Web-Asp-Net45
          - Web-Mgmt-Console
        state: present
      register: iis_feature
 
    - name: Reboot jika diperlukan
      ansible.windows.win_reboot:
      when: iis_feature.reboot_required

Windows Update Automation

Managing Windows Update via Ansible is one of the most valuable use cases — no more RDP login one by one. The win_updates module supports update categories, filtering, and reboot management:

win-updates.yml
- name: Patch Windows Server
  hosts: windows_servers
  gather_facts: false
  tasks:
    - name: Install semua update keamanan & critical
      ansible.windows.win_updates:
        category_names:
          - SecurityUpdates
          - CriticalUpdates
        state: installed
        reboot: true
        reboot_timeout: 1800
        log_path: C:\ansible\windows-update.log

Important

Windows updates in production must be done gradually. Combine with serial and strategy: linear at the play level so not all servers are patched at the same time. Also use a maintenance window — e.g., via win_scheduled_task or an orchestrator — so reboots don't disturb operating hours. For large environments, integrate with WSUS (Windows Server Update Services) as the internal update source.

Active Directory, Registry, and Scheduled Tasks

Active Directory — Windows infrastructure teams almost certainly deal with AD. The community.windows collection provides modules for creating users, groups, and even joining domains:

ad-management.yml
- name: Manajemen Active Directory
  hosts: domain_controllers
  vars:
    ad_ou: "OU=ServiceAccounts,OU=Corp,DC=corp,DC=example,DC=com"
  tasks:
    - name: Buat service account baru
      community.windows.win_domain_user:
        name: svc_backup
        password: "{{ vault_svc_backup_password }}"
        state: present
        groups:
          - Backup Operators
        password_never_expires: true
        user_cannot_change_password: true
        path: "{{ ad_ou }}"
 
    - name: Join host baru ke domain
      community.windows.win_domain_membership:
        dns_domain_name: corp.example.com
        domain_admin_user: "{{ ad_admin_user }}"
        domain_admin_password: "{{ vault_ad_admin_password }}"
        state: domain

Registry — many Windows settings without an official API can only be accessed via the registry. The win_regedit module makes this idempotent:

win-registry.yml
- name: Set policy melalui registry
  hosts: windows_servers
  tasks:
    - name: Nonaktifkan Windows Telemetry
      ansible.windows.win_regedit:
        path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection
        name: AllowTelemetry
        data: 0
        type: dword

Scheduled Tasks — Windows' alternative to cron. The win_scheduled_task module creates repeating schedules:

win-scheduled-task.yml
- name: Buat scheduled task backup log
  ansible.windows.win_scheduled_task:
    name: LogBackupDaily
    description: "Backup IIS log setiap hari pukul 02:00"
    actions:
      - path: C:\scripts\backup-logs.ps1
        working_directory: C:\scripts
    triggers:
      - type: daily
        start_boundary: "2026-08-02T02:00:00"
    run_level: highest
    state: present
    username: CORP\\svc_ansible
    password: "{{ vault_win_password }}"

Best Practices: WinRM Hardening & Credential Security

Safe Windows automation needs more than just a successful connection. Here are practices you must apply in production:

  1. HTTPS is mandatory. Use the WinRM listener on port 5986 with a certificate from an internal CA, and set ansible_winrm_server_cert_validation: validate.
  2. Avoid Basic auth outside trusted networks. Use NTLM as the baseline, Kerberos when possible, and limit CredSSP to double-hop scenarios only.
  3. Use a dedicated service account with minimal rights (not a universal Administrator) and enable password rotation — for example, managed via an AAP/AWX credential (episode 29).
  4. Always encrypt credentials with Ansible Vault and add no_log: true to tasks that receive password.
  5. Restrict WinRM access. In the firewall hosts.allow, only allow the control node/AAP IPs to port 5986.
  6. Keep the execution policy locked (AllSigned/RemoteSigned) and don't disable it for script convenience.

Common Pitfalls

1. Forgetting the pywinrm dependency on the control node

The connection fails with No module named 'winrm'. First install pip install pywinrm and the ansible.windows collection.

2. Basic auth without HTTPS

Passwords are sent base64 over HTTP — readable by anyone. Always use HTTPS for Basic, or switch to NTLM/Kerberos.

3. Using Linux modules on Windows hosts

ansible.builtin.apt, ansible.builtin.systemd_service, ansible.posix.firewalld will never run on Windows. Use win_* modules from the ansible.windows / community.windows collections, and separate tasks per platform with when: ansible_os_family == 'Windows' or use separate includes.

4. Forgetting become/administrator rights

Many Windows operations (feature installs, services, system registry) require administrator rights. Make sure the WinRM account has sufficient privileges, or use runas / become_method: runas with the right account.

5. Wrong domain user format

The DOMAIN\user notation in YAML must be escaped (e.g., "CORP\\svc_ansible") so the backslash isn't lost during parsing. This tiny mistake often makes authentication fail mysteriously.

6. Ignoring reboot_required

After win_feature or updates, many changes only take effect after a reboot. Capture register then call win_reboot conditionally — don't let a server run half-updated.

Conclusion

In this episode we've covered Windows automation with Ansible comprehensively: understanding why Windows uses WinRM and PowerShell instead of SSH, setting up the WinRM listener and PowerShell remoting, choosing the right authentication method (Basic, NTLM, Kerberos, CredSSP) along with their trade-offs, building Windows inventory, and using the ansible.windows and community.windows collections to manage packages (Chocolatey), services, IIS, Windows Update, Active Directory, registry, and scheduled tasks. We also covered WinRM hardening best practices and six common mistakes that often trip up beginners.

With this capability, you can now manage Linux and Windows servers from one control node, one YAML language, and one mindset — a rare and highly valuable combination in the job market. Ansible truly becomes a universal remote control for all infrastructure.

In episode 29, we'll cover how to take Ansible from small team scale to enterprise scale: Scaling Ansible for Enterprise — centralized architecture with AAP/AWX/Semaphore, GitOps patterns, monorepo vs multi-repo strategies, RBAC and credential management, and performance optimization for thousands of hosts with callback plugins and Mitogen. Keep your enthusiasm up!

Learn Ansible - Windows Automation with Ansible | Learn Ansible