Linux engineers often inherit Windows fleets they did not ask for: a few hundred file servers, a forest of domain controllers, IIS hosts, SQL boxes, and the inevitable jump servers. The good news is that Ansible — yes, the same Ansible you use for Linux — drives Windows extremely well. The bad news is that getting from “I can SSH to my Linux box” to “I can run a playbook against a domain-joined Windows Server 2022 with Kerberos auth and CredSSP delegation” involves real concepts: WinRM as the wire protocol, Kerberos as the identity layer, PowerShell as the execution engine, and DSC as the optional idempotence backend.
This lesson covers all of it the way EX374 and senior Windows-on-Ansible interviews actually grade it: WinRM listeners and authentication mechanisms, Kerberos vs NTLM vs Basic, the ansible.windows and community.windows module families, win_dsc as a generic bridge to any DSC resource, hardening WinRM-over-HTTPS with proper certificates, and patterns for credential management (gMSA, become, runas) on Windows from a Linux control node.
Learning Objectives
By the end you will be able to:
- Configure a fresh Windows Server 2022 host as a managed node with WinRM-over-HTTPS, a real certificate, and Kerberos auth from a Linux control node.
- Choose between WinRM authentication mechanisms (Basic, NTLM, Kerberos, CredSSP, Certificate) based on domain membership and double-hop requirements.
- Use the
ansible.windowscollection (win_copy,win_template,win_service,win_feature,win_regedit,win_acl,win_user,win_group,win_updates,win_command,win_shell,win_powershell) for routine Windows administration. - Use
community.windowsfor higher-level tasks (win_scheduled_task,win_iis_*,win_chocolatey,win_domain_*,win_psmodule,win_xml). - Bridge any Microsoft DSC resource into Ansible idempotence using
ansible.windows.win_dsc— includingxWebAdministration,SqlServerDsc, and custom DSC modules. - Harden WinRM: HTTPS-only, signed listener cert, AllowUnencrypted=false, MaxConcurrentOperations, IPv4 firewall scoping, audit logging.
- Use gMSA (group Managed Service Accounts) for unattended Windows automation that survives password rotation.
- Diagnose the four classic Windows-on-Ansible failures: WinRM authentication failed, Kerberos clock skew, double-hop / second-hop credentials, and SSL handshake failures.
Prerequisites
- Solid Ansible playbook fluency from Tiers 1–3.
- A Linux control node (RHEL 9 / Ubuntu 22.04) with Python 3.11+ and
pywinrmpluspykrb5installed. - At least one Windows Server 2019 or 2022 lab host with admin rights.
- Optional but recommended: a small Active Directory forest (one DC + one member server) — the lab section walks through building one for free in a Hyper-V or VirtualBox VM.
Mental Model: How Ansible Talks to Windows
Five concepts unlock everything else.
1. WinRM is “SSH for Windows” — but it’s HTTP
WinRM (Windows Remote Management) is Microsoft’s implementation of WS-Management. From Ansible’s perspective it serves the same role as SSH: a remote shell, file transfer, and a stable transport for executing modules. But it is HTTP under the hood — listening on TCP/5985 (HTTP) or TCP/5986 (HTTPS) — and it speaks SOAP-over-HTTP to a Windows server’s WSMan listener. The control node uses the Python pywinrm library to speak this protocol.
2. Authentication is the hard part — pick once, configure forever
WinRM supports five auth mechanisms: Basic (cleartext password, dev-only), NTLM (works for local accounts and workgroup machines), Kerberos (the only sane choice for domain-joined fleets), CredSSP (required for the “double-hop” problem when your playbook needs to access a fileshare or another server from inside a target host), and Certificate (mTLS with a client cert, useful for fully unattended automation). Pick one per inventory group and configure it consistently — mixing auth modes per host is the #1 cause of “it worked yesterday” outages.
3. PowerShell is the execution engine — modules ship as .ps1
When you run ansible win01 -m ansible.windows.win_service -a "name=Spooler state=stopped", Ansible serializes a small bootstrap PowerShell script, ships it via WinRM, and executes it inside powershell.exe on the target. Each Windows module is a .ps1 file that returns JSON (just like Linux modules return JSON from Python). This is why ansible_python_interpreter doesn’t matter on Windows — the target doesn’t need Python; it needs PowerShell 5.1 or 7.x.
4. win_dsc is the universal idempotence escape hatch
Microsoft DSC (Desired State Configuration) ships with hundreds of resources covering everything from IIS to SQL Server to Active Directory. Rather than write an Ansible module for each of these, the ansible.windows.win_dsc module wraps any DSC resource as an Ansible task. If something is missing from ansible.windows or community.windows, it is almost certainly available as a DSC resource — and win_dsc brings it into your playbook with full idempotence and check-mode support.
5. The Linux control node never installs anything on Windows
Unlike Group Policy or SCCM, Ansible doesn’t deploy an agent on Windows. The only requirement on the target is that WinRM is configured and PowerShell 5.1+ is present (every supported Windows Server has both out of the box). This means you can manage 5,000 Windows servers from a single RHEL VM with no agent fleet to maintain — but it also means every authentication problem is a configuration problem on the target’s WinRM, not an “agent not phoning home” problem.
Setting Up the Linux Control Node
The control node needs three Python libraries beyond Ansible itself:
# Install ansible-core and Windows-specific dependencies
python3 -m pip install --user 'ansible-core>=2.16' 'pywinrm[kerberos]' 'pykrb5'
# Install the two Windows collections
ansible-galaxy collection install ansible.windows community.windows
pywinrm is the Python WinRM client. The [kerberos] extra pulls in pykrb5 and the GSSAPI bindings needed to do Kerberos auth. On RHEL 9 you also need krb5-workstation and krb5-devel for the system Kerberos client.
# RHEL 9 / Rocky / Alma
sudo dnf install -y krb5-workstation krb5-devel python3-devel gcc
# Ubuntu 22.04
sudo apt-get install -y krb5-user libkrb5-dev python3-dev gcc
If you plan to use Kerberos auth (you should, for domain-joined targets) edit /etc/krb5.conf and add your AD realm:
[libdefaults]
default_realm = CORP.EXAMPLE.COM
dns_lookup_realm = false
dns_lookup_kdc = true
[realms]
CORP.EXAMPLE.COM = {
kdc = dc01.corp.example.com
admin_server = dc01.corp.example.com
}
[domain_realm]
.corp.example.com = CORP.EXAMPLE.COM
corp.example.com = CORP.EXAMPLE.COM
Test the Kerberos config with kinit administrator@CORP.EXAMPLE.COM — if you get a TGT back without errors, your control node can talk to AD.
Setting Up a Windows Target — WinRM-over-HTTPS
The default WinRM listener (HTTP/5985) accepts only NTLM/Kerberos and is acceptable for domain-joined hosts behind a firewall. For everything else, configure HTTPS/5986 with a real certificate.
Option A: Quick start with self-signed (lab only)
Microsoft maintains a ConfigureRemotingForAnsible.ps1 script that creates a self-signed cert and an HTTPS listener:
# Run on the Windows target, as Administrator
$url = "https://raw.githubusercontent.com/ansible/ansible-documentation/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:TEMP\ConfigureRemotingForAnsible.ps1"
Invoke-WebRequest -Uri $url -OutFile $file
powershell.exe -ExecutionPolicy ByPass -File $file -CertValidityDays 3650 -ForceNewSSLCert -EnableCredSSP
This sets up WinRM on 5986 with a self-signed cert. Adequate for a lab, never for production — the control node will have to set ansible_winrm_server_cert_validation: ignore, which defeats the purpose of HTTPS.
Option B: Production-grade with a real certificate
In production, use a certificate issued by your enterprise CA (or ACME via win-acme). The cert’s Subject CN or SAN must match the FQDN your inventory uses.
# On the target, after you've imported a real cert into LocalMachine\My
$thumbprint = (Get-ChildItem Cert:\LocalMachine\My |
Where-Object {$_.Subject -like "*CN=win01.corp.example.com*"}).Thumbprint
# Create the HTTPS listener
winrm create winrm/config/Listener?Address=*+Transport=HTTPS `
"@{Hostname=`"win01.corp.example.com`";CertificateThumbprint=`"$thumbprint`"}"
# Disable HTTP listener
winrm delete winrm/config/Listener?Address=*+Transport=HTTP
# Disable unencrypted (defense-in-depth)
winrm set winrm/config/service '@{AllowUnencrypted="false"}'
# Open Windows Firewall for 5986
New-NetFirewallRule -DisplayName "WinRM HTTPS" -Direction Inbound -Protocol TCP `
-LocalPort 5986 -Action Allow -RemoteAddress 10.0.0.0/8
The -RemoteAddress scoping is critical — only allow your control nodes’ subnet, never 0.0.0.0/0.
Inventory & Connection Variables
A Windows-only inventory:
# inventories/prod/hosts.yml
all:
children:
windows_servers:
hosts:
win01.corp.example.com:
win02.corp.example.com:
win03.corp.example.com:
vars:
ansible_connection: winrm
ansible_port: 5986
ansible_winrm_transport: kerberos
ansible_winrm_scheme: https
ansible_winrm_server_cert_validation: validate
ansible_user: "ansible-svc@CORP.EXAMPLE.COM"
Note ansible_user uses UPN format (user@REALM) for Kerberos. NTLM would use DOMAIN\user.
If you must use NTLM (workgroup hosts, no AD), it looks like:
ansible_winrm_transport: ntlm
ansible_user: "Administrator"
ansible_password: "{{ vault_admin_password }}"
The ansible.windows Collection — Module-by-Module
ansible.windows is the supported core Windows collection. It covers ~50 modules. The most-used ones, grouped by purpose:
File and template management
| Module | Purpose | Key parameters |
|---|---|---|
win_copy |
Copy files from controller to Windows | src, dest, force, backup |
win_file |
Manage paths, dirs, attributes | path, state (file/directory/absent/touch) |
win_template |
Render Jinja2 templates to Windows | src, dest, backup, newline_sequence |
win_acl |
NTFS / registry / share ACLs | path, user, rights, type, state |
win_owner |
Take ownership of a file/dir | path, user, recurse |
win_share |
Manage SMB shares | name, path, full, read, state |
win_template defaults to newline_sequence: '\r\n' (Windows line endings) — leave that alone unless you’re rendering a file that must have Unix endings.
Services and Scheduled Tasks
| Module | Purpose | Key parameters |
|---|---|---|
win_service |
Manage Windows services | name, state, start_mode, username, password |
win_service_info |
Read service config (gather facts) | name (optional, returns all if omitted) |
win_scheduled_task (community) |
Manage scheduled tasks | name, actions, triggers, username, state |
- name: Ensure print spooler is disabled (CIS hardening)
ansible.windows.win_service:
name: Spooler
state: stopped
start_mode: disabled
Packages and Features
| Module | Purpose | Key parameters |
|---|---|---|
win_feature |
Install Windows roles/features | name, state, include_management_tools |
win_package |
Install MSI/EXE installers | path, product_id, state, arguments |
win_chocolatey (community) |
Chocolatey package manager | name, state, version, source |
win_psmodule (community) |
PowerShell Gallery modules | name, state, repository |
win_updates |
Windows Update | category_names, state, reboot |
- name: Install IIS with management tools
ansible.windows.win_feature:
name:
- Web-Server
- Web-Mgmt-Tools
- Web-Asp-Net45
state: present
include_management_tools: true
register: iis_install
- name: Reboot if required
ansible.windows.win_reboot:
when: iis_install.reboot_required
Users, Groups, and Local Security
| Module | Purpose | Key parameters |
|---|---|---|
win_user |
Local user accounts | name, password, state, groups, password_never_expires |
win_group |
Local groups | name, description, state |
win_group_membership |
Add/remove members in a group | name, members, state |
win_user_right |
Local user rights (sec policy) | name (e.g. SeServiceLogonRight), users, action |
win_security_policy |
Local security policy editor | section, key, value |
Registry, Environment, and System Tweaks
| Module | Purpose | Key parameters |
|---|---|---|
win_regedit |
Manage registry keys/values | path, name, data, type, state |
win_environment |
Environment variables | name, value, level (machine/user) |
win_path |
Manage PATH entries | elements, state, scope |
win_hosts |
Manage hosts file |
state, ip_address, canonical_name, aliases |
win_dns_client |
DNS client settings | adapter_names, dns_servers, log_path |
- name: Disable SMBv1 (CVE remediation)
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
name: SMB1
data: 0
type: dword
state: present
Command Execution
| Module | Purpose | Key parameters |
|---|---|---|
win_command |
Run a single .exe (no shell parsing) | cmd (or free-form), chdir, creates, removes |
win_shell |
Run via PowerShell (pipes, redirection) | cmd, executable, chdir, creates |
win_powershell |
First-class PowerShell with structured I/O | script, parameters, executable_type |
win_dsc |
Run any DSC resource | resource_name, plus DSC resource params |
win_powershell (added in ansible.windows 1.5+) is the modern way to run PowerShell — it returns structured output, host_out, error, verbose, etc., separately:
- name: Get top 5 processes by working set
ansible.windows.win_powershell:
script: |
param([int]$Top = 5)
Get-Process | Sort-Object -Property WS -Descending |
Select-Object -First $Top Name, Id, WS |
ConvertTo-Json
parameters:
Top: 10
register: procs
- debug:
var: procs.output
The community.windows Collection — Higher-level Modules
community.windows extends ansible.windows with hundreds of modules for IIS, AD, Hyper-V, Chocolatey, scheduled tasks, certificate management, DSC, and more. The most-used ones:
| Module | Purpose |
|---|---|
win_scheduled_task |
Create/update scheduled tasks (most-used module in this collection) |
win_iis_website, win_iis_webbinding, win_iis_webapplication, win_iis_webapppool, win_iis_virtualdirectory |
Full IIS lifecycle |
win_chocolatey, win_chocolatey_source, win_chocolatey_config |
Chocolatey package management |
win_domain, win_domain_controller, win_domain_user, win_domain_group, win_domain_membership, win_domain_object_info, win_domain_ou |
Active Directory |
win_certificate_store, win_certificate_info |
Certificate store management |
win_xml, win_lineinfile, win_inifile |
Text-file manipulation |
win_hyperv_* |
Hyper-V VM lifecycle (less common since Azure took over) |
win_firewall, win_firewall_rule |
Windows Firewall config |
win_pssession_configuration |
Custom PowerShell session configurations |
- name: Create a daily backup scheduled task
community.windows.win_scheduled_task:
name: "Daily DB Backup"
description: "Backs up the local SQL DB to a fileshare"
actions:
- path: powershell.exe
arguments: "-NoProfile -File C:\\scripts\\backup.ps1"
triggers:
- type: daily
start_boundary: "2025-01-01T02:00:00"
username: "CORP\\sql-backup-svc"
logon_type: password
password: "{{ vault_sql_backup_password }}"
state: present
win_dsc — The Universal Bridge
DSC ships hundreds of community modules: xWebAdministration, xActiveDirectory, SqlServerDsc, NetworkingDsc, ComputerManagementDsc, plus your own custom DSC resources. ansible.windows.win_dsc lets you call any of them as a regular Ansible task with full idempotence and check-mode support.
- name: Ensure xWebAdministration is installed
community.windows.win_psmodule:
name: xWebAdministration
state: present
- name: Configure an IIS site via DSC
ansible.windows.win_dsc:
resource_name: xWebSite
Name: "Default Web Site"
PhysicalPath: "C:\\inetpub\\wwwroot"
State: Started
BindingInfo:
- Protocol: https
Port: 443
CertificateThumbprint: "{{ iis_cert_thumbprint }}"
CertificateStoreName: "MY"
The shape of win_dsc is mechanical: resource_name is the DSC resource (capitalization matters), and every other key is a property of that DSC resource. Get-DscResource -Name xWebSite -Syntax on the target will print the full schema.
When win_dsc shows changed: false, it ran DSC’s Test-TargetResource and the resource is in the desired state. When it shows changed: true, it ran Set-TargetResource to converge. This is true idempotence — no “did the task run? maybe?” guessing.
Hands-on Free Lab: WinRM-over-HTTPS with Kerberos
This lab takes you from “fresh Windows Server 2022 evaluation VM” to “production-grade Ansible-managed host” without spending money. Microsoft’s evaluation Windows Server 2022 ISO is free for 180 days.
# On your Linux control node
mkdir -p ~/ansible-windows-lab && cd ~/ansible-windows-lab
# Create inventory
cat > inventory.yml <<'EOF'
all:
children:
windows_lab:
hosts:
win01.lab.local:
vars:
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm # NTLM for the lab; switch to kerberos after AD setup
ansible_user: "Administrator"
ansible_password: "{{ lab_admin_password }}"
ansible_winrm_server_cert_validation: ignore
EOF
# Create the playbook
cat > site.yml <<'EOF'
---
- hosts: windows_lab
gather_facts: true
tasks:
- name: Install Web Server role with mgmt tools
ansible.windows.win_feature:
name:
- Web-Server
- Web-Mgmt-Console
- Web-Asp-Net45
state: present
include_management_tools: true
register: iis
- name: Reboot if Web Server install requires it
ansible.windows.win_reboot:
when: iis.reboot_required
- name: Render an IIS welcome page from a template
ansible.windows.win_template:
src: welcome.html.j2
dest: 'C:\inetpub\wwwroot\index.html'
- name: Open firewall for HTTP
community.windows.win_firewall_rule:
name: "HTTP-In"
localport: 80
action: allow
direction: in
protocol: tcp
state: present
enabled: true
- name: Disable SMBv1 (defense-in-depth)
ansible.windows.win_regedit:
path: HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters
name: SMB1
data: 0
type: dword
state: present
- name: Create a service account for app deployments
ansible.windows.win_user:
name: app-deploy-svc
password: "{{ vault_deploy_svc_password }}"
password_never_expires: true
state: present
groups:
- Administrators
- name: Grant the deploy account 'log on as service' right
ansible.windows.win_user_right:
name: SeServiceLogonRight
users:
- app-deploy-svc
action: add
EOF
# Create the template
cat > welcome.html.j2 <<'EOF'
<!DOCTYPE html>
<html>
<head><title>{{ ansible_hostname }}</title></head>
<body>
<h1>Welcome to {{ ansible_hostname }}</h1>
<p>OS: {{ ansible_distribution }} {{ ansible_distribution_version }}</p>
<p>Provisioned by Ansible at {{ ansible_date_time.iso8601 }}</p>
</body>
</html>
EOF
# Run the playbook
ansible-playbook -i inventory.yml site.yml \
-e "lab_admin_password=YourLabPassw0rd! vault_deploy_svc_password=YourDeployPassw0rd!"
# Verify
curl -s http://win01.lab.local/ | grep "Welcome"
After this works, switch to Kerberos auth:
- Promote
win01to a Domain Controller (or join an existing AD). - Create a service account
ansible-svc@LAB.LOCALand grant it admin on the target. - Update inventory:
ansible_winrm_transport: kerberos
ansible_user: "ansible-svc@LAB.LOCAL"
# remove ansible_password — kinit handles credentials
- On the control node:
kinit ansible-svc@LAB.LOCAL - Re-run the playbook — same playbook, no password in inventory, ticket-based auth.
Common Mistakes & Troubleshooting
1. “WinRM authentication failed” on a domain-joined host with NTLM
Group Policy may have disabled NTLM for inbound auth. Switch to Kerberos (the right answer anyway) or check Computer Configuration\Policies\Windows Settings\Security Settings\Local Policies\Security Options\Network security: Restrict NTLM.
2. “Server not found in Kerberos database”
Either the host’s SPN is missing or your /etc/krb5.conf realm is wrong. Run setspn -L <hostname> on the DC — there must be HTTP/win01.corp.example.com registered. If missing: setspn -A HTTP/win01.corp.example.com win01.
3. “Clock skew too great”
Kerberos rejects tickets if client and KDC clocks differ by more than 5 minutes. Run chronyc sources -v (Linux) and w32tm /query /status (Windows) to verify both are syncing to the same NTP source.
4. The “double-hop” problem
Your playbook accesses \\fileserver\share from win01 and gets “access denied” even though the user has rights. This is by design — NTLM/Kerberos won’t forward credentials past one hop. Solution: enable CredSSP (ansible_winrm_transport: credssp) which performs delegation, OR use a UNC-aware tool like Copy-Item -ToSession, OR avoid the hop entirely by copying via win_copy (which uses the WinRM session, not the user’s identity).
5. “Certificate verify failed” with HTTPS listener
The cert is self-signed or its chain isn’t trusted on the control node. Either set ansible_winrm_server_cert_validation: ignore (lab only), or copy the CA cert to /etc/pki/ca-trust/source/anchors/ and run update-ca-trust.
6. win_updates runs forever
win_updates waits for the entire update cycle including reboots and re-runs of pending updates. Set category_names: ['SecurityUpdates'] to scope it, and state: searched to just enumerate without installing.
7. win_dsc says “resource not found”
The DSC module isn’t installed on the target. community.windows.win_psmodule: name=xWebAdministration state=present first, then call win_dsc.
Best Practices
- Always use Kerberos for domain-joined hosts. NTLM is a fallback for workgroup machines or break-glass scenarios.
- Always use HTTPS (5986) in production. HTTP (5985) is acceptable only inside a fully isolated management VLAN.
- Use a dedicated service account, not Domain Admin. Grant it local Administrators on the target hosts only — Tier 0 separation.
- Use gMSA when feasible. Group Managed Service Accounts auto-rotate their password every 30 days, removing a long-lived secret from your vault.
- Pin collection versions in
requirements.yml:ansible.windows: 2.5.0,community.windows: 2.3.0. New versions occasionally rename module parameters. - Run
gather_facts: trueonce per play, not per task. Windows fact gathering is more expensive than Linux because it’s WMI-backed. - Use
win_powershelloverwin_shellwhen you need structured output.win_shellis for one-line commands. - Reboot explicitly with
win_reboot, not implicitly.win_updatesandwin_featuresetreboot_required: true— your playbook decides whether to act on it. - Test in check mode (
--check) regularly. Mostansible.windowsmodules support check mode;win_dscsupports it for any DSC resource that implementsTest-TargetResource.
Security Notes
- WinRM-over-HTTP transmits credentials in cleartext for Basic auth — never use Basic auth without HTTPS, and disable Basic globally in production:
winrm set winrm/config/service/auth '@{Basic="false"}'. - Kerberos delegation (CredSSP) is powerful and dangerous. Enable it only on hosts that need the second hop, scope it to specific target hosts via
Enable-WSManCredSSP -Role Server -DelegateComputer, and never enable “Allow delegation to any computer.” - Disable SMBv1, NTLMv1, and LM hashes on every Windows server. The
ansible.windows.win_regeditrecipes for each are well-documented in CIS benchmarks. - Audit WinRM access via Windows Event Log. WinRM writes to
Microsoft-Windows-WinRM/Operational— ship those events to your SIEM. - Use a dedicated jump host for Ansible, not engineers’ laptops. The jump host’s identity (Kerberos keytab or gMSA) is the single thing that needs to be tightly controlled.
- Encrypt secrets in Ansible Vault, never inline. Especially passwords for
win_user, service account passwords, and certificate private keys.
Q&A — 14 Questions
Q1. Why does Ansible use WinRM instead of SSH for Windows?
Windows ships WinRM out of the box on every Server SKU since 2008 R2. SSH for Windows (OpenSSH on Server 2019+) is supported by Ansible too (ansible_connection: ssh works on Windows), but WinRM has years of operational maturity, deeper Kerberos integration, and richer error semantics for PowerShell-driven modules.
Q2. What is the [kerberos] extra in pywinrm[kerberos]?
It pulls in pykrb5 and the GSSAPI Python bindings so the control node can do Kerberos auth. Without it, ansible_winrm_transport: kerberos fails with “kerberos extension not available.”
Q3. Why does kinit work but Ansible still fails with auth errors?
Probably an SPN mismatch. Run klist after kinit, then check the service principal — it must be HTTP/<fqdn>@REALM. If the SPN registered in AD is HTTP/<short-hostname> instead, Kerberos rejects the ticket.
Q4. What’s the difference between win_command, win_shell, and win_powershell?
win_command runs an executable directly with no shell — no pipes, no redirection (use this for safety). win_shell runs via PowerShell, allowing pipes and redirection. win_powershell is the modern, structured-output equivalent of win_shell — returns separate output, error, verbose, host_out channels.
Q5. Can I manage Windows from a Linux control node without any AD/Kerberos?
Yes — use NTLM auth with local accounts. ansible_winrm_transport: ntlm, ansible_user: "Administrator", ansible_password: "...". Works fine for workgroup machines or break-glass scenarios.
Q6. What is CredSSP and when do I need it? CredSSP (Credential Security Support Provider) is a Windows auth mechanism that performs credential delegation. You need it when your playbook on host A needs to authenticate to host B as the original user — e.g., a script that copies from a fileshare. NTLM and Kerberos won’t forward creds past the first hop; CredSSP will.
Q7. Why does win_updates take so long?
Because it runs Windows Update synchronously: search, download, install, reboot if required, re-search for pending updates. A fresh Server 2022 with 6 months of pending updates can run for 90+ minutes. Scope it with category_names: ['SecurityUpdates', 'CriticalUpdates'] and consider using a separate WSUS server.
Q8. What does win_dsc give me that the regular Windows modules don’t?
Coverage. ansible.windows and community.windows have ~250 modules combined; DSC has thousands. If you need to manage IIS request filtering, SQL Server replication, or AD recycle bin — there’s a DSC resource for it, and win_dsc brings it into Ansible.
Q9. How do gMSAs work with Ansible?
gMSAs (group Managed Service Accounts) are AD accounts whose password rotates automatically every 30 days. To use one with Ansible: install the gMSA on the host (Install-ADServiceAccount), grant it admin rights, then set ansible_user: "gmsa-name$@CORP.EXAMPLE.COM" (note the $ suffix) and let Kerberos handle the keytab. No password ever in the vault.
Q10. Should I use SSH on Windows instead of WinRM? For greenfield environments, SSH is a viable choice — Microsoft’s OpenSSH is solid. But the Windows module ecosystem assumes WinRM, and SSH-on-Windows has subtle differences in how environment variables and console encodings work. Stick with WinRM unless you have a strong reason.
Q11. What’s the difference between ansible.windows.win_feature and community.windows.win_chocolatey?
win_feature installs Windows roles and features that ship with Windows itself (IIS, AD-DS, Hyper-V, .NET 3.5). win_chocolatey installs third-party software via the Chocolatey package manager (Notepad++, 7-Zip, Git, JDK). Use the right tool for the right thing — don’t try to install IIS via Chocolatey.
Q12. Can I run Ansible on a Windows control node?
Officially no — Ansible requires a Unix-like control node. Use WSL2 if you must run from a Windows engineer’s laptop (ansible-core runs fine in WSL2 Ubuntu and can drive Windows targets via WinRM).
Q13. How do I rotate the WinRM listener cert without downtime?
Issue the new cert into LocalMachine\My, then winrm set winrm/config/Listener?Address=*+Transport=HTTPS '@{CertificateThumbprint="<new-thumbprint>"}'. WinRM picks up the new cert on the next connection — no service restart needed.
Q14. Why doesn’t my playbook see Windows facts (ansible_distribution, ansible_hostname)?
You probably set gather_facts: false at the play level, or the connection is failing before facts run. Set gather_facts: true and verify the auth works with ansible win01 -m ansible.windows.win_ping before debugging fact-related issues.
Quick Check
- Which auth transport is the only sane choice for a domain-joined fleet?
- What port does WinRM-over-HTTPS listen on by default?
- What does
ansible.windows.win_dscdo? - How do you grant the SeServiceLogonRight to a local account?
- What’s the difference between
ansible_user: user@REALMandansible_user: DOMAIN\user? - Why must clocks be in sync within 5 minutes for Kerberos auth?
- Which collection contains
win_scheduled_task—ansible.windowsorcommunity.windows? - What’s the canonical way to check if a
win_featureinstall requires reboot?
Exercise
Build a complete role windows_iis_baseline that:
- Installs IIS with management tools (use
win_feature). - Installs the
xWebAdministrationDSC module (usewin_psmodule). - Renders a custom
applicationHost.configsnippet viawin_template. - Configures a default site via
win_dsc+xWebSite. - Creates a dedicated app pool service account (gMSA preferred, fallback to local user).
- Opens the firewall for HTTPS only (drop HTTP).
- Includes a
validate.ymltask list that useswin_urito confirm the site responds with HTTP 200.
Test it with both NTLM (workgroup target) and Kerberos (domain-joined target) and confirm both code paths work without changing the role.
Cert Mapping
- EX374 — Windows automation is one of the seven domain blocks. Expect a hands-on task to bootstrap a Windows host, configure WinRM, run a playbook, and harden the listener.
- MS-100/MS-101 / Azure AD — gMSAs and AD service accounts overlap with Microsoft 365 fundamentals.
- CKAD/CKA — not directly applicable, but the credential-rotation discipline (gMSA, Kerberos keytabs) maps onto the same problem in K8s ServiceAccount tokens.
Glossary
- WinRM — Windows Remote Management, Microsoft’s WS-Management implementation. The wire protocol between Ansible and Windows.
- WSMan — The listener service inside Windows that accepts WinRM requests.
- NTLM — NT LAN Manager, the legacy Windows challenge-response auth protocol.
- Kerberos — Ticket-based auth protocol used by Active Directory.
- CredSSP — Credential Security Support Provider, the auth mechanism that performs credential delegation (solves “double-hop”).
- DSC — Desired State Configuration, Microsoft’s declarative config-as-code framework. PowerShell-based.
- gMSA — group Managed Service Account, an AD account whose password auto-rotates.
- SPN — Service Principal Name, the Kerberos identifier for a service running on a host (e.g.
HTTP/win01.corp.example.com). - TGT — Ticket Granting Ticket, the initial Kerberos credential issued by
kinit.
Next Steps
You can now bring Windows fleets into the same playbook ecosystem as your Linux fleet, with proper Kerberos auth and DSC-backed idempotence. The next lesson covers Ansible for Kubernetes — the kubernetes.core collection, k8s module, manifest templating, Helm chart lifecycle, and the patterns that let you treat clusters as configuration-managed targets rather than imperative scripts.