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.

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.
Linux uses Python + SSH. Windows uses PowerShell + WinRM. This difference brings several consequences:
ansible.builtin.shell is different — on Windows, shell tasks are executed as PowerShell, not bash. win_command and win_shell are the Windows versions.setup module on Windows produces different ansible_facts (ansible_os_family is Windows, hostname, etc.), and not all Linux facts are available.ansible.builtin.apt module, for example, will never run on Windows. Modules like ansible.windows.win_* are needed.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.
# 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 $fileThat 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:
winrm enumerate winrm/config/listener
# Output yang diharapkan:
# Listener
# Address = *
# Transport = HTTPS
# Port = 5986
# Hostname = WSERVER-01
# Enabled = trueNote
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.
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):
| Method | Transport | Credential Delegation | When to Use |
|---|---|---|---|
| Basic | HTTP/HTTPS | No | Workgroup/simple environments, HTTPS mandatory |
| NTLM | HTTP/HTTPS | No | Most common default, broad compatibility |
| Kerberos | HTTPS | No (if without delegation) | Active Directory environments, single sign-on |
| CredSSP | HTTPS | Yes | Needs 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.
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:
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.
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:
| Category | Module | Function |
|---|---|---|
| Execution | ansible.windows.win_command / win_shell | Running commands / PowerShell |
| Services | ansible.windows.win_service | Managing Windows services (start/stop/auto-start) |
| Features & Roles | ansible.windows.win_feature | Installing/uninstalling Windows features (including IIS) |
| Packages | ansible.windows.win_chocolatey | Installing packages via Chocolatey |
| Packages | ansible.windows.win_package | Installing packages from installers (.msi/.exe) |
| Updates | ansible.windows.win_updates | Managing Windows Update |
| Registry | ansible.windows.win_regedit | Reading/writing the registry |
| Scheduled Tasks | ansible.windows.win_scheduled_task | Managing Task Scheduler |
| Files | ansible.windows.win_file / win_copy / win_template | Files & directories on Windows |
| DNS | community.windows.win_dns_record | Managing DNS records |
| Domain | community.windows.win_domain / win_domain_user / win_domain_membership | Managing 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.
Chocolatey is the package manager for Windows — the analog of apt/dnf on Linux. With win_chocolatey, you can install applications declaratively and idempotently:
- 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: latestFor older installers not available in Chocolatey (e.g., internal corporate installers), use win_package, which can execute .msi, .exe, or .msu with silent parameters:
- 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: presentTip
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.
The win_service module manages Windows services the same way systemd_service does on Linux — including idempotent status:
- 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: disabledIIS (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_requiredManaging 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:
- 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.logImportant
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 — Windows infrastructure teams almost certainly deal with AD. The community.windows collection provides modules for creating users, groups, and even joining domains:
- 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: domainRegistry — many Windows settings without an official API can only be accessed via the registry. The win_regedit module makes this idempotent:
- 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: dwordScheduled Tasks — Windows' alternative to cron. The win_scheduled_task module creates repeating schedules:
- 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 }}"Safe Windows automation needs more than just a successful connection. Here are practices you must apply in production:
ansible_winrm_server_cert_validation: validate.Administrator) and enable password rotation — for example, managed via an AAP/AWX credential (episode 29).no_log: true to tasks that receive password.hosts.allow, only allow the control node/AAP IPs to port 5986.AllSigned/RemoteSigned) and don't disable it for script convenience.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.
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!