Windows Automation with Ansible

Overview

Ansible can manage Windows systems using WinRM (Windows Remote Management) instead of SSH. This allows you to automate Windows servers and desktops alongside your Linux infrastructure.

Requirements:
  • Windows 7/Server 2008 R2 or later
  • PowerShell 3.0 or later (5.1+ recommended)
  • WinRM configured and enabled
  • pywinrm Python package on control node

Setting Up Windows for Ansible

1. Install pywinrm on Control Node

pip install pywinrm
# For Kerberos authentication
pip install pywinrm[kerberos]

2. Configure WinRM on Windows

Run this PowerShell script on the Windows host:

# Download and run Ansible's WinRM setup script
$url = "https://raw.githubusercontent.com/ansible/ansible-documentation/devel/examples/scripts/ConfigureRemotingForAnsible.ps1"
$file = "$env:temp\ConfigureRemotingForAnsible.ps1"

(New-Object -TypeName System.Net.WebClient).DownloadFile($url, $file)

powershell.exe -ExecutionPolicy ByPass -File $file

3. Verify WinRM Configuration

winrm enumerate winrm/config/Listener

Inventory Configuration

Windows hosts require special variables in inventory:

[windows]
win-server1 ansible_host=192.168.1.100

[windows:vars]
ansible_user=Administrator
ansible_password=SecurePassword123
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore
ansible_port=5986
Security Warning: Never store passwords in plain text! Use Ansible Vault to encrypt sensitive data or use certificate-based authentication.

Authentication Methods

Basic Authentication

ansible_winrm_transport=basic
ansible_port=5985

NTLM (Recommended for Domain)

ansible_winrm_transport=ntlm
ansible_port=5985

Kerberos (Most Secure)

ansible_winrm_transport=kerberos
ansible_user=user@DOMAIN.COM

Certificate-Based

ansible_winrm_transport=certificate
ansible_winrm_cert_pem=/path/to/cert.pem
ansible_winrm_cert_key_pem=/path/to/key.pem

Common Windows Modules

win_ping - Test Connection

---
- name: Test Windows connectivity
  hosts: windows
  tasks:
    - name: Ping Windows host
      win_ping:

win_feature - Manage Windows Features

---
- name: Install IIS Web Server
  hosts: windows
  tasks:
    - name: Install IIS
      win_feature:
        name: Web-Server
        state: present
        include_management_tools: yes

    - name: Install ASP.NET
      win_feature:
        name: Web-Asp-Net45
        state: present

win_package - Install Software

---
- name: Install software
  hosts: windows
  tasks:
    - name: Install 7-Zip
      win_package:
        path: https://www.7-zip.org/a/7z1900-x64.msi
        product_id: '{23170F69-40C1-2702-1900-000001000000}'
        state: present

    - name: Install Chrome
      win_chocolatey:
        name: googlechrome
        state: present

win_service - Manage Services

---
- name: Manage Windows services
  hosts: windows
  tasks:
    - name: Ensure W3SVC is running
      win_service:
        name: W3SVC
        state: started
        start_mode: auto

    - name: Stop and disable a service
      win_service:
        name: Spooler
        state: stopped
        start_mode: disabled

win_file - File Operations

---
- name: File management
  hosts: windows
  tasks:
    - name: Create directory
      win_file:
        path: C:\Temp\MyApp
        state: directory

    - name: Remove file
      win_file:
        path: C:\Temp\oldfile.txt
        state: absent

win_copy - Copy Files

---
- name: Copy files to Windows
  hosts: windows
  tasks:
    - name: Copy configuration file
      win_copy:
        src: /local/path/config.ini
        dest: C:\App\config.ini

    - name: Copy with backup
      win_copy:
        src: important.txt
        dest: C:\Data\important.txt
        backup: yes

win_shell & win_command - Execute Commands

---
- name: Execute commands
  hosts: windows
  tasks:
    - name: Run PowerShell command
      win_shell: |
        Get-Service | Where-Object {$_.Status -eq "Running"}

    - name: Execute command
      win_command: ipconfig /all

win_regedit - Registry Management

---
- name: Manage Windows Registry
  hosts: windows
  tasks:
    - name: Set registry value
      win_regedit:
        path: HKLM:\Software\MyApp
        name: Version
        data: 1.0.0
        type: string

    - name: Delete registry key
      win_regedit:
        path: HKLM:\Software\OldApp
        state: absent

win_updates - Windows Updates

---
- name: Install Windows updates
  hosts: windows
  tasks:
    - name: Install all security updates
      win_updates:
        category_names:
          - SecurityUpdates
          - CriticalUpdates
        reboot: yes

Complete Windows Playbook Example

---
- name: Configure Windows Web Server
  hosts: windows
  gather_facts: yes

  tasks:
    - name: Install IIS
      win_feature:
        name: Web-Server
        state: present
        include_management_tools: yes

    - name: Create website directory
      win_file:
        path: C:\inetpub\mysite
        state: directory

    - name: Copy website files
      win_copy:
        src: ./website/
        dest: C:\inetpub\mysite\

    - name: Configure IIS site
      win_iis_website:
        name: MySite
        state: started
        port: 80
        physical_path: C:\inetpub\mysite

    - name: Open firewall for HTTP
      win_firewall_rule:
        name: HTTP
        localport: 80
        action: allow
        direction: in
        protocol: tcp
        state: present
        enabled: yes

    - name: Ensure service is running
      win_service:
        name: W3SVC
        state: started
        start_mode: auto

Troubleshooting

Test WinRM Connection

ansible windows -m win_ping

Check WinRM from Control Node

python3 -c "import winrm; s=winrm.Session('192.168.1.100', auth=('user', 'pass')); print(s.run_cmd('ipconfig'))"

Common Issues

  • SSL Certificate Errors: Set ansible_winrm_server_cert_validation=ignore (development only)
  • Authentication Failures: Verify username/password and try different transports
  • Timeout Errors: Check firewall rules allow ports 5985 (HTTP) and 5986 (HTTPS)
  • Memory Errors: Increase WinRM memory: winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}'

Active Directory Management

Domain Controller Setup

---
- name: Configure Active Directory Domain Controller
  hosts: dc_servers
  gather_facts: yes

  tasks:
    - name: Install AD-Domain-Services feature
      win_feature:
        name: AD-Domain-Services
        include_management_tools: yes
        include_sub_features: yes
        state: present

    - name: Promote server to Domain Controller
      win_domain:
        dns_domain_name: example.com
        safe_mode_password: P@ssw0rd123!
        domain_netbios_name: EXAMPLE
      register: dc_promotion

    - name: Reboot after DC promotion
      win_reboot:
      when: dc_promotion.reboot_required

    - name: Wait for Active Directory services
      win_wait_for:
        port: 389
        timeout: 600

Domain User Management

---
- name: Manage Active Directory Users
  hosts: domain_controllers
  gather_facts: no

  vars:
    domain_users:
      - name: john.doe
        firstname: John
        surname: Doe
        password: TempPass123!
        ou: "OU=Users,DC=example,DC=com"
        groups: ['Domain Users', 'IT Staff']
      - name: jane.smith
        firstname: Jane
        surname: Smith
        password: TempPass456!
        ou: "OU=Users,DC=example,DC=com"
        groups: ['Domain Users', 'Managers']

  tasks:
    - name: Create domain users
      win_domain_user:
        name: "{{ item.name }}"
        firstname: "{{ item.firstname }}"
        surname: "{{ item.surname }}"
        password: "{{ item.password }}"
        path: "{{ item.ou }}"
        state: present
        enabled: yes
        password_never_expires: no
        user_cannot_change_password: no
        change_password_at_logon: yes
      loop: "{{ domain_users }}"

    - name: Add users to groups
      win_domain_group_membership:
        name: "{{ item.1 }}"
        members:
          - "{{ item.0.name }}"
        state: present
      loop: "{{ domain_users | subelements('groups') }}"

    - name: Disable inactive user
      win_domain_user:
        name: old.user
        enabled: no
        state: present

Organizational Units (OUs)

---
- name: Manage Organizational Units
  hosts: domain_controllers

  tasks:
    - name: Create OU structure
      win_domain_ou:
        name: "{{ item.name }}"
        path: "{{ item.path }}"
        state: present
        protected: yes
      loop:
        - { name: 'IT', path: 'DC=example,DC=com' }
        - { name: 'Servers', path: 'OU=IT,DC=example,DC=com' }
        - { name: 'Workstations', path: 'OU=IT,DC=example,DC=com' }
        - { name: 'ServiceAccounts', path: 'OU=IT,DC=example,DC=com' }

    - name: Move computer to OU
      win_domain_computer:
        name: WEB01
        dns_hostname: web01.example.com
        ou: "OU=Servers,OU=IT,DC=example,DC=com"
        state: present

Group Policy Management

---
- name: Manage Group Policy
  hosts: domain_controllers

  tasks:
    - name: Create Group Policy Object
      win_shell: |
        Import-Module GroupPolicy
        New-GPO -Name "Security Baseline" -Comment "Standard security settings"
      args:
        creates: 'C:\Windows\SYSVOL\domain\Policies\{GPO-GUID}'

    - name: Configure password policy
      win_domain_group_policy:
        name: "Default Domain Policy"
        settings:
          - name: MinimumPasswordLength
            value: 14
          - name: PasswordComplexity
            value: 1
          - name: MaximumPasswordAge
            value: 90

    - name: Link GPO to OU
      win_shell: |
        Import-Module GroupPolicy
        New-GPLink -Name "Security Baseline" -Target "OU=Servers,DC=example,DC=com"

PowerShell DSC with Ansible

What is PowerShell DSC?

Desired State Configuration (DSC) is a PowerShell management platform for configuring Windows systems. Ansible can invoke DSC resources for advanced Windows automation.

Using DSC from Ansible

---
- name: Configure Windows with DSC
  hosts: windows
  tasks:
    - name: Ensure a Windows feature via DSC
      win_dsc:
        resource_name: WindowsFeature
        Name: Web-Server
        Ensure: Present
        IncludeAllSubFeature: true

    - name: Configure registry via DSC
      win_dsc:
        resource_name: Registry
        Key: 'HKLM:\Software\MyCompany'
        ValueName: Version
        ValueData: '1.0.0'
        ValueType: String
        Ensure: Present

    - name: Create local user via DSC
      win_dsc:
        resource_name: User
        UserName: ServiceAccount
        Password: "{{ service_account_password }}"
        Ensure: Present
        PasswordNeverExpires: true
        PasswordChangeNotAllowed: true

Complex DSC Configuration

---
- name: Advanced DSC Configuration
  hosts: windows

  tasks:
    - name: Configure IIS with DSC
      win_dsc:
        resource_name: xWebsite
        Name: MyWebSite
        PhysicalPath: 'C:\inetpub\wwwroot\mysite'
        State: Started
        BindingInfo:
          - Protocol: http
            Port: 80
            IPAddress: '*'
          - Protocol: https
            Port: 443
            IPAddress: '*'
            CertificateThumbprint: "{{ ssl_cert_thumbprint }}"

    - name: Configure SQL Server with DSC
      win_dsc:
        resource_name: SqlServerConfiguration
        InstanceName: MSSQLSERVER
        OptionName: max server memory (MB)
        OptionValue: 8192
        RestartService: false

    - name: Configure Windows Firewall with DSC
      win_dsc:
        resource_name: xFirewall
        Name: Allow-HTTP
        DisplayName: "Allow HTTP Traffic"
        Ensure: Present
        Enabled: True
        Direction: Inbound
        LocalPort: 80
        Protocol: TCP
        Action: Allow

Domain Join Operations

---
- name: Join Windows computers to domain
  hosts: workstations
  vars:
    domain_name: example.com
    domain_admin: Administrator
    domain_password: "{{ vault_domain_password }}"
    target_ou: "OU=Workstations,DC=example,DC=com"

  tasks:
    - name: Set DNS server to Domain Controller
      win_dns_client:
        adapter_names: '*'
        ipv4_addresses:
          - 192.168.1.10
          - 192.168.1.11

    - name: Join computer to domain
      win_domain_membership:
        dns_domain_name: "{{ domain_name }}"
        domain_admin_user: "{{ domain_admin }}@{{ domain_name }}"
        domain_admin_password: "{{ domain_password }}"
        domain_ou_path: "{{ target_ou }}"
        state: domain
      register: domain_join

    - name: Reboot after domain join
      win_reboot:
        msg: "Rebooting after domain join"
      when: domain_join.reboot_required

    - name: Verify domain membership
      win_shell: (Get-WmiObject -Class Win32_ComputerSystem).PartOfDomain
      register: domain_check
      failed_when: domain_check.stdout | trim != 'True'

Windows Server Roles and Features

File Server Configuration

---
- name: Configure File Server
  hosts: file_servers
  become_method: runas

  tasks:
    - name: Install File Server role
      win_feature:
        name:
          - FS-FileServer
          - FS-DFS-Namespace
          - FS-DFS-Replication
          - FS-Resource-Manager
        state: present
        include_management_tools: yes

    - name: Create shared directories
      win_file:
        path: "{{ item }}"
        state: directory
      loop:
        - D:\Shares\Public
        - D:\Shares\Departments
        - D:\Shares\Projects

    - name: Configure SMB shares
      win_share:
        name: "{{ item.name }}"
        path: "{{ item.path }}"
        full: "{{ item.full }}"
        change: "{{ item.change }}"
        read: "{{ item.read }}"
        state: present
      loop:
        - name: Public
          path: D:\Shares\Public
          full: Domain Admins
          change: Domain Users
          read: Everyone
        - name: Departments
          path: D:\Shares\Departments
          full: Domain Admins
          change: Department Heads
          read: Domain Users

    - name: Set NTFS permissions
      win_acl:
        path: "{{ item.path }}"
        user: "{{ item.user }}"
        rights: "{{ item.rights }}"
        type: allow
        state: present
        inherit: ContainerInherit, ObjectInherit
        propagation: None
      loop:
        - { path: 'D:\Shares\Public', user: 'Domain Users', rights: 'Modify' }
        - { path: 'D:\Shares\Departments', user: 'Department Heads', rights: 'FullControl' }

DHCP Server Configuration

---
- name: Configure DHCP Server
  hosts: dhcp_servers

  tasks:
    - name: Install DHCP Server role
      win_feature:
        name: DHCP
        include_management_tools: yes
        state: present

    - name: Configure DHCP scope
      win_shell: |
        Add-DhcpServerv4Scope -Name "Corporate Network" `
          -StartRange 192.168.1.100 `
          -EndRange 192.168.1.200 `
          -SubnetMask 255.255.255.0 `
          -State Active

    - name: Set DHCP server options
      win_shell: |
        Set-DhcpServerv4OptionValue -ScopeId 192.168.1.0 `
          -Router 192.168.1.1 `
          -DnsServer 192.168.1.10,192.168.1.11 `
          -DnsDomain example.com

    - name: Authorize DHCP in Active Directory
      win_shell: |
        Add-DhcpServerInDC -DnsName dhcp01.example.com -IPAddress 192.168.1.50

Certificate Services

---
- name: Manage Windows Certificates
  hosts: windows

  tasks:
    - name: Install certificate from file
      win_certificate_store:
        path: C:\Certs\server.pfx
        password: CertPassword123
        state: present
        store_location: LocalMachine
        store_name: My

    - name: Export certificate
      win_certificate_store:
        thumbprint: "{{ cert_thumbprint }}"
        state: exported
        file_type: pfx
        path: C:\Backup\cert.pfx
        password: BackupPass123
        store_location: LocalMachine
        store_name: My

    - name: Request certificate from CA
      win_shell: |
        $cert = Get-Certificate -Template WebServer `
          -DnsName web01.example.com `
          -CertStoreLocation Cert:\LocalMachine\My
        $cert.Certificate.Thumbprint
      register: new_cert

    - name: Remove expired certificates
      win_shell: |
        Get-ChildItem Cert:\LocalMachine\My |
        Where-Object { $_.NotAfter -lt (Get-Date) } |
        Remove-Item

Windows Clustering

---
- name: Configure Windows Failover Cluster
  hosts: cluster_nodes

  tasks:
    - name: Install Failover Clustering feature
      win_feature:
        name: Failover-Clustering
        include_management_tools: yes
        state: present

    - name: Create failover cluster
      win_shell: |
        New-Cluster -Name AppCluster `
          -Node {{ groups['cluster_nodes'] | join(',') }} `
          -StaticAddress 192.168.1.100 `
          -NoStorage
      when: inventory_hostname == groups['cluster_nodes'][0]
      run_once: true

    - name: Add cluster quorum
      win_shell: |
        Set-ClusterQuorum -Cluster AppCluster `
          -FileShareWitness \\fileserver\quorum\AppCluster
      when: inventory_hostname == groups['cluster_nodes'][0]

    - name: Configure cluster network
      win_shell: |
        (Get-ClusterNetwork -Cluster AppCluster |
         Where-Object {$_.Address -eq "192.168.1.0"}).Name = "Production"

Windows Automation Best Practices

Handling Reboots

---
- name: Proper reboot handling
  hosts: windows

  tasks:
    - name: Install updates requiring reboot
      win_updates:
        category_names:
          - SecurityUpdates
          - CriticalUpdates
        reboot: no
      register: update_result

    - name: Reboot if required
      win_reboot:
        reboot_timeout: 600
        post_reboot_delay: 120
      when: update_result.reboot_required

    - name: Wait for system to be ready
      win_wait_for_process:
        process_name_exact: TrustedInstaller
        state: absent
        timeout: 600
      when: update_result.reboot_required

Error Handling

---
- name: Windows error handling
  hosts: windows

  tasks:
    - name: Attempt risky operation
      block:
        - name: Stop critical service
          win_service:
            name: MyAppService
            state: stopped

        - name: Update application
          win_copy:
            src: newapp.exe
            dest: C:\App\app.exe

        - name: Start service
          win_service:
            name: MyAppService
            state: started

      rescue:
        - name: Rollback on failure
          win_copy:
            src: C:\App\Backup\app.exe
            dest: C:\App\app.exe
            remote_src: yes

        - name: Start service with old version
          win_service:
            name: MyAppService
            state: started

        - name: Send alert
          win_shell: |
            Send-MailMessage -To "ops@example.com" `
              -Subject "Deployment Failed" `
              -Body "Application update failed, rolled back to previous version"

      always:
        - name: Log deployment attempt
          win_eventlog:
            source: AnsibleDeployment
            event_id: 1000
            message: "Deployment attempt completed"

Performance Optimization

---
- name: Optimize Windows Automation Performance
  hosts: windows

  vars:
    ansible_winrm_operation_timeout_sec: 60
    ansible_winrm_read_timeout_sec: 70

  tasks:
    - name: Increase WinRM memory limit
      win_shell: |
        Set-Item WSMan:\localhost\Shell\MaxMemoryPerShellMB 1024
        Set-Item WSMan:\localhost\Plugin\Microsoft.PowerShell\Quotas\MaxMemoryPerShellMB 1024

    - name: Enable PowerShell remoting optimizations
      win_shell: |
        Set-Item WSMan:\localhost\MaxConcurrentOperationsPerUser 100
        Set-Item WSMan:\localhost\Service\MaxConcurrentOperations 200

    - name: Use async for long-running tasks
      win_updates:
        category_names:
          - Updates
        state: installed
      async: 3600
      poll: 0
      register: update_task

    - name: Continue with other work
      win_feature:
        name: Telnet-Client
        state: present

    - name: Check async task status
      async_status:
        jid: "{{ update_task.ansible_job_id }}"
      register: job_result
      until: job_result.finished
      retries: 360
      delay: 10

Hybrid Cloud Windows Management

---
- name: Manage Windows across On-Prem and Azure
  hosts: localhost
  gather_facts: no

  tasks:
    - name: Get Azure Windows VMs
      azure_rm_virtualmachine_info:
        resource_group: ProductionRG
        tags:
          - Environment:Production
          - OS:Windows
      register: azure_vms

    - name: Add Azure VMs to inventory
      add_host:
        name: "{{ item.name }}"
        ansible_host: "{{ item.properties.networkProfile.networkInterfaces[0].properties.ipConfigurations[0].properties.privateIPAddress }}"
        groups: windows,azure_windows
      loop: "{{ azure_vms.vms }}"

    - name: Configure all Windows servers (on-prem + cloud)
      include_role:
        name: windows_baseline
      delegate_to: "{{ item }}"
      loop: "{{ groups['windows'] }}"

Security Hardening

---
- name: Windows Security Hardening
  hosts: windows

  tasks:
    - name: Disable unnecessary services
      win_service:
        name: "{{ item }}"
        state: stopped
        start_mode: disabled
      loop:
        - RemoteRegistry
        - SSDPSRV
        - upnphost
        - WMPNetworkSvc

    - name: Configure Windows Firewall
      win_firewall:
        state: enabled
        profiles:
          - Domain
          - Private
          - Public

    - name: Enable audit policy
      win_audit_policy_system:
        subcategory: Logon
        audit_type: success, failure

    - name: Configure account lockout policy
      win_security_policy:
        section: System Access
        key: LockoutBadCount
        value: 5

    - name: Disable SMBv1
      win_optional_feature:
        name: SMB1Protocol
        state: absent

    - name: Enable Windows Defender
      win_shell: |
        Set-MpPreference -DisableRealtimeMonitoring $false
        Update-MpSignature

Best Practices

  • Use HTTPS (port 5986) in production environments
  • Implement certificate-based or Kerberos authentication for security
  • Always store credentials in Ansible Vault, never in plain text
  • Use win_ modules specifically designed for Windows
  • Test playbooks with --check mode before production runs
  • Handle reboots properly using win_reboot module
  • Use async tasks for long-running Windows operations
  • Increase WinRM timeouts for slow operations
  • Enable detailed logging for troubleshooting
  • Use DSC for complex configurations when appropriate
  • Implement proper error handling with block/rescue
  • Test domain operations in non-production first
  • Document OU paths and naming conventions
  • Use service accounts with minimum required privileges
  • Monitor WinRM memory usage for large-scale deployments

Next Steps