In a nutshell
Imagine you already speak fluent Spanish (Linux + SSH) and you inherit a team that only speaks German (Windows). You do not need to learn a second grammar from scratch — you keep the same interpreter (Ansible) you already trust and teach it to route German through a different phone line. That phone line is WinRM instead of SSH, the interpreter shows its ID at the door using Kerberos instead of an SSH key, and once inside it speaks PowerShell instead of Bash. The playbooks, roles, inventory, variables, handlers, and idempotent mindset you already know are identical. Only the transport and the identity handshake change.
So the whole lesson is really four swaps: SSH → WinRM (the wire), SSH keys → Kerberos/NTLM/CredSSP/certs (the identity), Python → PowerShell (the on-host engine), and shell idempotence → DSC-backed idempotence (the optional convergence layer). Nail those four and a Linux engineer can manage 5,000 Windows servers from one RHEL box with no agent installed anywhere.
If you take one thing away: authentication is 90% of the work, and Kerberos is the answer for anything domain-joined. Everything else — copying files, installing IIS, disabling SMBv1 — is just picking the right win_* module and letting Ansible ship a PowerShell script over the WinRM wire.
Level: Advanced · Time: ~45 min · You’ll need: playbook fluency, one Windows Server 2019/2022 lab host, and a Linux control node.
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.
- Elevate and switch identity on Windows with
become/runas, and drive Windows over PSRP or SSH when WinRM is not the right fit. - 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 — plays, roles, variables, handlers, and
becomeas you use it on Linux (see Playbooks, plays, tasks & become). - 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.
Ansible vs Group Policy, SCCM, and standalone DSC
Windows admins already own configuration tools, so before writing a single playbook it is worth being honest about where Ansible fits — because “but we already have GPO” is the first objection you will hear. These tools are not mutually exclusive; the mature answer is usually “GPO for the domain-wide security floor, Ansible for orchestration and everything imperative or cross-platform.”
| Tool | Model | Push / Pull | Idempotent | Best at | Weakness |
|---|---|---|---|---|---|
| Group Policy (GPO) | Declarative, AD-scoped | Pull (gpupdate) |
Yes (reapplies on refresh) | Domain-wide security baselines, user & machine settings | Domain-joined only; slow feedback loop; no cross-host ordering; hard to unit-test |
| SCCM / MECM | Imperative + packaged | Pull (agent) | Partial | Massive patch & software distribution | Heavy agent + site infra; costly; Windows-only |
| PowerShell DSC (standalone) | Declarative | Pull (LCM) or push | Yes | Per-host desired state, drift correction | Drift returns if LCM is off; MOF authoring is clunky; weak multi-host orchestration |
| Ansible | Declarative tasks, imperative ordering | Push (WinRM/PSRP/SSH) | Yes (per module) | Orchestrating mixed Linux+Windows fleets, ad-hoc and repeatable runs, cross-tier workflows | Needs WinRM/SSH reachability; no continuous enforcement between runs |
The key distinction: GPO and DSC continuously enforce state (good for drift, bad for “do this exact sequence right now”), while Ansible runs when you tell it to and orchestrates across a whole fleet in one ordered pass. win_dsc even lets you keep your DSC resources and drive them on Ansible’s schedule — you get DSC’s convergence guarantee with Ansible’s orchestration and reporting.
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 }}"
Running tasks as another user: become and runas on Windows
On Linux, become means sudo. On Windows there is no sudo — the equivalent is runas, and Ansible ships a dedicated become plugin for it (become_method: runas). You reach for become on Windows for three distinct reasons, and none of them are “just to be admin”:
- Escape the WinRM privilege sandbox. A WinRM session runs under a network logon token, which deliberately strips some privileges and, crucially, carries no cached credentials — that is the root of the double-hop wall. Running a task under
becomeperforms a fresh interactive/batch logon with the full privilege set. - Run as a different identity —
SYSTEM, a service account, or a gMSA — without changing the connection user. - Reach the user profile / registry hive that only exists once an interactive logon has occurred.
- name: Run a task as the local SYSTEM account (no password needed)
ansible.windows.win_whoami:
become: true
become_method: runas
become_user: SYSTEM
- name: Run an installer as a domain service account
ansible.windows.win_package:
path: C:\pkgs\app.msi
state: present
become: true
become_method: runas
become_user: "CORP\\deploy-svc"
vars:
ansible_become_password: "{{ vault_deploy_svc_password }}"
- name: Run as a gMSA (AD supplies the password, so you omit it)
ansible.windows.win_shell: whoami /all
become: true
become_method: runas
become_user: "CORP\\web-gmsa$"
become_flags: "logon_type=service"
Three things to internalise:
SYSTEMneeds no password — it is the LocalSystem account, andrunascan assume it directly. Handy for tasks that need machine-level rights without stashing a secret.- A gMSA needs no password either — the
$suffix marks it a group Managed Service Account, and the host pulls its rotating password from AD. Pair it withbecome_flags: "logon_type=service". becomeis itself a double-hop fix. Becauserunasperforms a full logon (not a credential-less network logon), the resulting token carries credentials that can authenticate outbound to a second host — so supplyingansible_become_passwordoften solves the double-hop with no CredSSP delegation risk at all. More on this in Going deeper.
You can set ansible_become_method: runas once at the inventory-group level so every play inherits it.
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.
Beyond WinRM: PSRP and SSH-on-Windows
WinRM is the default, but it is not the only way in. Two alternatives matter once you are past the basics.
PSRP — PowerShell Remoting Protocol
PSRP (ansible_connection: psrp) rides the same WinRM listener and ports (5985/5986) and uses the same auth mechanisms, but speaks Microsoft’s richer PowerShell Remoting Protocol (MS-PSRP) instead of raw WS-Man SOAP. It is backed by the pypsrp Python library rather than pywinrm.
Why switch:
- Faster file copies and lower overhead — PSRP streams objects natively, so
win_copyof large files and fact gathering are often noticeably quicker. - Cleaner large-payload handling — you hit fewer of the
MaxMemoryPerShellMBfragmentation issues that plague classic WinRM at scale.
python3 -m pip install --user pypsrp
windows_servers:
vars:
ansible_connection: psrp
ansible_port: 5986
ansible_psrp_auth: kerberos
ansible_psrp_protocol: https
ansible_psrp_cert_validation: validate
ansible_user: "ansible-svc@CORP.EXAMPLE.COM"
SSH on Windows
Windows Server 2019+ and Windows 10/11 ship Microsoft’s OpenSSH server as an optional feature. Ansible can drive it with the ordinary ssh connection plugin — you simply tell Ansible the remote shell is PowerShell:
windows_ssh:
vars:
ansible_connection: ssh
ansible_shell_type: powershell
ansible_user: ansible-svc
# key-based auth, exactly like Linux
That gives you SSH-key auth and one transport for both Linux and Windows — attractive for greenfield fleets. The caveats are real, though: SSH-on-Windows is younger, environment-variable and console-encoding behaviour differs subtly from WinRM, and there is no CredSSP, so double-hop scenarios still need become or Kerberos delegation. win_dsc and every win_* module work identically over all three transports, because they are just modules — the transport only changes how the payload gets there.
| Transport | Library | Ports | Auth | When to choose |
|---|---|---|---|---|
| WinRM | pywinrm | 5985 / 5986 | Basic / NTLM / Kerberos / CredSSP / Cert | Default; maximum compatibility, richest error semantics |
| PSRP | pypsrp | 5985 / 5986 | Same as WinRM | Large file copies, heavy fact gathering, performance |
| SSH | OpenSSH | 22 | Keys / password | Greenfield, key-based auth, unify Linux+Windows on one transport |
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 become: runas with a password (a full logon whose credentials can reach the second hop), 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 accounts, and certificate private keys (see Vault, secrets & encryption).
Going deeper
The sections above get you running. This one is for the reader who has to run Windows automation at scale, safely, and predictably — where the WinRM wire protocol, quota model, and Kerberos internals stop being trivia and start being the difference between a clean 500-host run and intermittent 3 a.m. failures.
What actually crosses the wire
A single win_service task is not one HTTP request — it is a short WS-Man shell lifecycle. The control node sends a SOAP envelope to create a remote shell (wsman:Shell), a second call to run the bootstrap PowerShell command carrying the module’s Base64-encoded payload, one or more Receive calls to stream stdout/stderr back, and a final call to delete the shell. That is why WinRM feels chattier than SSH: every task pays this create → run → receive → delete tax over HTTP.
For NTLM, Kerberos, and CredSSP the payload is encrypted at the message layer using the negotiated session key, even on port 5985 — so “HTTP” WinRM is not plaintext for those mechanisms. Basic auth is the exception: it has no session key, so the payload (and your password) travels in clear text. That single fact is the entire reason Basic auth must run over HTTPS and Kerberos does not have to.
WinRM quotas bite you at scale
WinRM ships conservative quotas that a busy Ansible run trips into. The classics:
| Setting | Typical default | Symptom when too low |
|---|---|---|
MaxMemoryPerShellMB |
1024 (older builds 150–512) | Modules die with “Out of memory” / truncated output |
MaxConcurrentOperationsPerUser |
1500 | “The WS-Management service cannot process the request” under high forks |
MaxShellsPerUser |
30 (older builds 5) | New connections refused mid-run |
MaxConnections |
300 | Same, at the listener level |
Bump one with winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="2048"}'. If you run Ansible with forks: 50 against Windows, size these deliberately — an undersized MaxShellsPerUser against a high fork count is the #1 cause of intermittent, host-count-dependent failures that look like flakiness but are pure arithmetic.
Kerberos delegation vs CredSSP — the double-hop, precisely
The double-hop wall exists because a WinRM network logon receives a token with no cached credentials — it can prove who you are to the first host, but has nothing to present onward to a second host. Three ways through, from bluntest to safest:
- CredSSP — the target receives your actual plaintext credentials and can reuse them anywhere. Powerful, blunt, and a genuine risk: a compromised target now holds your password. Scope it per-host with
Enable-WSManCredSSP -Role Server -DelegateComputer <fqdn>, never “any computer.” - Kerberos constrained delegation (KCD) — AD is configured so the host may forward tickets only to named SPNs. Far safer than CredSSP; no plaintext ever leaves the control node. Configured on the computer object in AD, not in Ansible.
become: runas— often the cleanest: a full interactive/batch logon caches the become account’s credentials on the target, so outbound auth to a second host “just works” — no CredSSP, no KCD — as long as you supplyansible_become_password.
Idempotence and check mode, under the hood
A well-written Windows module reports changed: true only when it mutated state — win_service diffs the current start mode against the requested one before touching it. win_dsc is the gold standard: it calls the DSC resource’s read-only Test-TargetResource first, reports changed: false if already converged, and only runs Set-TargetResource when it must. Under --check, modules that support check mode report what would change without doing it; win_command/win_shell cannot know their own side effects, so you must guard them (see check mode, diff & debugging):
- name: Only report changed when the script actually did work
ansible.windows.win_shell: C:\scripts\reindex.ps1
register: reindex
changed_when: "'REINDEXED' in reindex.stdout"
check_mode: false # this script has no dry-run; never let --check pretend it ran
Long-running tasks: async, win_updates, and win_reboot
win_updates and win_reboot are special. win_reboot deliberately tears down its own WinRM connection, then polls the listener until the host answers again and a test command succeeds — that is how a reboot in the middle of a play does not break the play. For genuinely long jobs, fire-and-poll with async so you are not holding a shell open for an hour:
- name: Kick off a long patch run without holding the connection open
ansible.windows.win_updates:
category_names: [SecurityUpdates, CriticalUpdates]
reboot: true
async: 3600
poll: 0
register: patch_job
- name: Wait for patching to finish
ansible.builtin.async_status:
jid: "{{ patch_job.ansible_job_id }}"
register: patch_result
until: patch_result.finished
retries: 60
delay: 60
Performance notes
- No pipelining on WinRM. The SSH
pipeliningoptimisation does not apply; each module still ships as a discrete payload. PSRP recovers some of that overhead by streaming objects. - Fact gathering is WMI/CIM-backed and costs real time. Cache it (
gathering: smartplus afact_cachingbackend) or setgather_facts: falseand gather only what you need. - Fork sizing vs WinRM quotas must be co-designed (see the quota table). High forks against low
MaxShellsPerUseris a self-inflicted outage, not bad luck.
Collection & version caveats
- Pin both collections in
requirements.yml; parameter names occasionally change across majors (option renames appeared betweenansible.windows1.x → 2.x). win_powershellneedsansible.windows≥ 1.5.win_dscneeds PowerShell 5.1 on the target — the classic DSC engine is not present in PowerShell 7 by default.- Kerberos auth needs the
[kerberos]extra and a working system krb5 stack — a missingkrb5-develatpip installtime silently drops GSSAPI support, and you find out only when auth fails.
Common beginner mistakes
Distinct from the symptom → fix troubleshooting table above, these are the misconceptions that send beginners down the wrong path — the wrong mental model, not just the wrong command.
- “WinRM is plaintext, so port 5985 leaks my password.” Half-true. Basic auth is plaintext and needs HTTPS — but NTLM/Kerberos/CredSSP encrypt the message payload even on 5985. HTTPS still matters for certificate trust and for Basic, yet “5985 = insecure” is a misconception.
- “I’ll set
ansible_python_interpreterfor my Windows hosts.” No Python runs on the target — modules execute in PowerShell. That variable is inert on Windows. The engine you care about is PowerShell 5.1+ (or 7.x). - “
becomeon Windows is justsudo.” It isrunas, a different logon type, and its real superpower is escaping the WinRM network-logon sandbox (privileges and the double-hop), not merely “run as admin.” - “Kerberos failed, let me fall back to NTLM to get unblocked.” NTLM masks the real problem (usually an SPN mismatch or clock skew) and is frequently disabled by GPO on hardened domains. Fix the Kerberos config; do not paper over it.
- “
win_shellandwin_commandare interchangeable.”win_commandruns an .exe with no shell — no pipes, no$env:, no redirection.win_shellis full PowerShell. Reaching forwin_shell“to be safe” invites command injection and accidental non-idempotence. - “I ran a PowerShell script, so the task is idempotent.” Only if you made it so.
win_shell/win_commandalways reportchanged: trueunless you addcreates/removesorchanged_when. Idempotence is a property you add, not a default you inherit. - “Self-signed cert is fine, I’ll just
ignorevalidation everywhere.” That turns HTTPS into theatre — encryption with zero authentication, wide open to a man-in-the-middle. Fine for a throwaway lab, never a production habit. - “
win_updateshung — kill it.” It almost never did. It is synchronously grinding through search → download → install → reboot. Scope it withcategory_namesand run itasync, or you will think every patch run is frozen.
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 (and so, often, will become: runas).
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 either set ansible_user: "gmsa-name$@CORP.EXAMPLE.COM" (note the $ suffix) or run tasks under become_user: "CORP\\gmsa-name$" with become_flags: "logon_type=service" — 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, and one transport for Linux+Windows is appealing. But the Windows module ecosystem assumes WinRM, SSH-on-Windows has subtle differences in environment variables and console encodings, and there is no CredSSP. 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? - Which become method does Windows use, and why is it not the same as Linux
sudo? - Name two ways to solve the double-hop problem without CredSSP.
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.
Practice challenges
Six graded exercises, beginner → advanced. Try each before opening the solution; each solution ends with a one-line why.
Challenge 1 — Prove the connection (beginner)
Configure an inventory for a single workgroup host using NTLM over HTTP, then confirm Ansible can reach it with a single ad-hoc command — no playbook.
<details><summary>Solution</summary>
# inventory.yml
windows_lab:
hosts:
win01:
vars:
ansible_connection: winrm
ansible_port: 5985
ansible_winrm_transport: ntlm
ansible_winrm_server_cert_validation: ignore
ansible_user: Administrator
ansible_password: "{{ vault_admin_password }}"
ansible win01 -i inventory.yml -m ansible.windows.win_ping
Why: win_ping is the Windows smoke test — it proves transport + auth before you waste time debugging a module.
</details>
Challenge 2 — Idempotent service state (beginner)
Write a task that guarantees the Print Spooler is stopped and disabled, and reason about why it reports changed: false on the second run.
<details><summary>Solution</summary>
- name: Spooler stopped and disabled (CIS)
ansible.windows.win_service:
name: Spooler
state: stopped
start_mode: disabled
Why: win_service diffs current vs desired state and start mode, so a converged host returns changed: false — idempotence you get for free from a real module (unlike win_shell).
</details>
Challenge 3 — Feature install with conditional reboot (intermediate)
Install the DNS Server role including management tools, and reboot only if Windows says a reboot is required.
<details><summary>Solution</summary>
- name: Install DNS role
ansible.windows.win_feature:
name: DNS
state: present
include_management_tools: true
register: dns
- name: Reboot only if required
ansible.windows.win_reboot:
when: dns.reboot_required
Why: win_feature returns reboot_required; gating win_reboot on it keeps the play from bouncing a host that did not need it.
</details>
Challenge 4 — Make a raw script idempotent (intermediate)
You must run C:\scripts\seed-db.ps1, but it should run only once and must not always report changed. Do it two different ways.
<details><summary>Solution</summary>
# Way 1 — marker file: skip the run entirely once seeded
- name: Seed the DB once
ansible.windows.win_shell: C:\scripts\seed-db.ps1
args:
creates: C:\scripts\.seeded
# Way 2 — changed_when: tie change status to the script's own output
- name: Seed the DB and report change honestly
ansible.windows.win_shell: C:\scripts\seed-db.ps1
register: seed
changed_when: "'SEEDED' in seed.stdout"
Why: win_shell always reports changed: true on its own; creates skips the run on repeat, and changed_when ties change status to what the script actually did.
</details>
Challenge 5 — Beat the double-hop without CredSSP (advanced)
A task on win01 must read \\fileserver\deploy\app.zip and fails with “access denied” even though the user has share rights. Fix it using become — not CredSSP.
<details><summary>Solution</summary>
- name: Copy an app package that lives on a remote share
ansible.windows.win_copy:
src: \\fileserver\deploy\app.zip
dest: C:\deploy\app.zip
remote_src: true
become: true
become_method: runas
become_user: "CORP\\deploy-svc"
vars:
ansible_become_password: "{{ vault_deploy_svc_password }}"
Why: the default WinRM network logon carries no credentials to a second hop; runas performs a full logon whose cached credentials can authenticate outbound to the fileshare — solving the double-hop with no CredSSP delegation risk.
</details>
Challenge 6 — Bridge a DSC resource and dry-run it (advanced)
There is no native module for the setting you need, but a DSC resource exists. Install the DSC module, converge the resource with win_dsc, and prove the play is safe by running it in check mode first.
<details><summary>Solution</summary>
- name: Ensure the DSC module is present
community.windows.win_psmodule:
name: NetworkingDsc
state: present
- name: Set the DNS client server addresses via DSC
ansible.windows.win_dsc:
resource_name: DnsServerAddress
AddressFamily: IPv4
InterfaceAlias: Ethernet
Address:
- 10.0.0.10
- 10.0.0.11
ansible-playbook -i inventory.yml dns.yml --check
Why: win_dsc runs the resource’s read-only Test-TargetResource under --check, so you see whether it would converge without mutating the host — genuine dry-run idempotence for any DSC resource that implements Test.
</details>
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.
- WS-Management (WS-Man) — the SOAP-over-HTTP management standard that WinRM implements.
- WSMan — The listener service inside Windows that accepts WinRM requests.
- WinRM listener — the endpoint (HTTP:5985 / HTTPS:5986) that accepts remote-management connections.
- 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”).
- KCD — Kerberos Constrained Delegation, the safe way to forward tickets only to named services (a controlled alternative to CredSSP).
- DSC — Desired State Configuration, Microsoft’s declarative config-as-code framework. PowerShell-based.
- LCM — Local Configuration Manager, the on-host DSC engine that applies compiled configurations.
- MOF — Managed Object Format, the compiled document a DSC configuration produces and the LCM consumes.
- 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. - UPN — User Principal Name, the
user@REALMlogin format Kerberos expects (vs NTLM’sDOMAIN\user). - PSRP — PowerShell Remoting Protocol, an alternative connection over the same WinRM ports, backed by
pypsrp. - pywinrm / pypsrp — the Python client libraries behind the
winrmandpsrpconnection plugins. - runas / become — Windows privilege-and-identity switch; Ansible reaches it via
become_method: runas. - Network logon vs interactive logon — WinRM uses a credential-less network logon (source of the double-hop);
runascreates a fuller logon that can authenticate outward. - Double-hop — the wall that stops first-hop credentials from reaching a second host over WinRM.
- keytab — a file holding Kerberos keys so a service can authenticate without typing a password.
- MaxMemoryPerShellMB — the WinRM per-shell memory quota that, when too low, truncates or kills module output.
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.