Introduction
Ansible is quickly becoming the dominant DevOps platform for automating software provisioning, configuration management, and application deployment in a heterogeneous datacenter and hybrid cloud environment. Ansible has facilities to integrate and manage various technologies including Microsoft Windows, systems with REST API support and of course Linux.
This article will go through the steps of deploying the Ansible controlling node on CentOS 7, the configuration of Windows Server 2016 for management and create Ansible playbook examples with custom Powershell Ansible modules.
Windows and Ansible integration is documented in the official Ansible documentation.
By following the instructions in this article, you will be able to manage Windows systems using Ansible as easily as any other environment, including Linux.
Example Lab
The examples in this article will reference this minimal configuration:
- One Ansible Controlling Node running Linux CentOS 7
- One Windows Server 2016 server to be managed
- One Windows Server 2016 Active Directory server providing DNS services
This lab is built on three VMs running on Hyper-V on a Windows 10 desktop. Here is an article describing a similar scenario, How to Build Windows Storage Spaces Direct on a Virtual Lab.
Below is a diagram of the example lab environment.

All communications between systems runs on a private network segment for simplicity.
Install Lab Servers
Build the following servers in your environment.
| Server | Notes |
|---|---|
| Ansible1 | This will be a CentOS version 7 system with the minimal software selection. Make sure you can access this system via SSH from a client such as Putty. |
| WinServer1 | This is a Windows Server 2016.
|
| AD1 | This is a Windows Server 2016 with the Active Directory Domain Services role configured. This system is not required for domain services for the examples below. This node only provides DNS services for this environment.
|
Configure Lab Network
Configure AD1 DNS services
Configure DNS Forward and Reverse Lookups, Kerberos requires both forward and reverse DNS lookup to resolve correctly.
Configure the DNS Reverse Lookup Zone. This will look like the screenshot below.

The DNS Forward Lookup Zone will be like the screenshot below

Add Ansible1 and WinServer1 A records:
- Ansible1: 172.20.20.100 – Select to create the PTR record automatically
- WinServer1: 172.20.20.101 – Select to create the PTR record automatically
Review the Reverse Look Zone 20.20.172.in-addr.arpa. You may need to select Right-Click and REFRESH to see the new records.
Configure Ansible1 CentOS System Network
Update the following files. Be sure to modify as appropriate for your own environment.
The eth0 interface in this example uses DHCP addresses to access the Internet and provide SSH access for administration.
- Edit:
/etc/sysconfig/network-scripts/ifcfg-eth0
TYPE="Ethernet"
PROXY_METHOD="none"
BROWSER_ONLY="no"
BOOTPROTO="dhcp"
DEFROUTE="yes"
PEERDNS="no"
IPV4_FAILURE_FATAL="no"
NAME="eth0"
DEVICE="eth0"
ONBOOT="yes"The eth1 interface is a private network and is shared only with the other servers in this lab.
- Edit
/etc/sysconfig/network-scripts/ifcfg-eth1
TYPE=Ethernet
PROXY_METHOD=none
BROWSER_ONLY=no
BOOTPROTO=static
DEFROUTE=yes
IPADDR=172.20.20.100
NETMAST=255.255.0.0
DNS1=172.20.20.1
IPV4_FAILURE_FATAL=no
NAME=eth1
DEVICE=eth1
ONBOOT=yesThe resolv.conf configures the Linux DNS client.
- Edit
/etc/resolv/conf
search Lab1AD1.local
nameserver 172.20.20.1
Set the hostname
- Edit:
/etc/hostname
Ansible1
- Reboot the server
Type: reboot
- Test your network. Make sure you can resolve DNS correctly.
Type: nslookup WinServer1
This command will query the AD1 DNS server for WinServer1 without a fully qualified name.

Type: nslookup 172.20.20.101
This command will do a reverse lookup on the IP address. This should return WinServer1 as the name.

Configure Ansible Environment
Install Prerequisite Packages
Use Yum to install the following packages.
- Install GCC required for Kerberos
yum -y group install “Development Tools”
- Install EPEL
yum -y install epel-release
- Install Ansible
yum -y install ansible
- Install Kerberos
yum -y install python-devel krb5-devel krb5-libs krb5-workstation
- Install Python PIP
yum -y install python-pip
- Install BIND utilities for nslookup
yum -y install bind-utils
- Bring all packages up to the latest version
yum -y update
Check Ansible and Python is Installed
- Run the commands:
ansible - - version | head -l 1
python - - version

Configure Kerberos
There are other options than Kerberos, but Kerberos is generally the best option, though not the simplest.
- Install the Kerberos wrapper:
pip install pywinrm[Kerberos]
Kerberos packages were installed previously which will have created /etc/krb5.conf
- Edit
/etc/krb5.conf
Add:
[realms]
LAB1AD1.LOCAL = {
kdc = AD1.LAB1AD1.LOCAL
}
Add:
[domain_realm]
.lab1ad1.local = LAB1AD1.LOCAL
lab1ad1.local = LAB1AD1.LOCAL
The /etc/krb5.conf file when complete will be similar to:

Test Kerberos
- Run the following commands to test Kerberos:
kinit administrator@LAB1AD1.LOCAL
You will be prompted for the administrator password klist
You should see a Kerberos KEYRING record.

Configure Ansible
Ansible is complex and is sensitive to the environment. Troubleshooting an environment that has never initially worked is complex and confusing. We are going to configure Ansible with the least complex possible configuration. Once you have a working environment, you can make extensions and enhancements in small steps.
The core configuration of Ansible resides at /etc/ansible

We are only going to update two files for this exercise.
Update the Ansible Inventory file
- Edit
/etc/ansible/hostsand add:
[windows]
WinServer1.lab1ad1.local

[windows] is a created group of servers called “windows”. In reality, this should be named something more appropriate for a group that would have similar configurations, such as “Active Directory Servers”, or “Production Floor Windows 10 PCs”, etc.
Update the Ansible Group Variables for Windows
Ansible Group Variables are variable settings for a specific inventory group. In this case, we will create the group variables for the “windows” servers created in the /etc/ansible/hosts file.
- Create
/etc/ansible/group_vars/windowsand add:
---
ansible_user: Administrator
ansible_password: Abcd1234
ansible_port: 5986
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
Naturally, change the Administrator password to the password for WinServer1.

Configure Windows Servers to Manage
Configuring the Windows Server for remote management by Ansible requires a bit of work. Luckily the Ansible team has created a PowerShell script for this. Download this script from [here] to each Windows Server to manage and run this script as an Administrator.
Log in to WinServer1 as an Administrator, download ConfigureRemotingForAnsible.ps1 and run this PowerShell script without any parameters.
Once this command has been run on the WinServer1, return to the Ansible1 Controller host.
Test Connectivity to the Windows Server
If all has gone well, we should be able to perform an Ansible PING test command. This command will connect to the remote WinServer1 server and report success or failure.
- Type:
ansible windows -m win_ping

This command runs the Ansible module win_ping on every server in the windows inventory group.
- Type:
ansible windows -m setupto retrieve a complete configuration of Ansible environmental settings. - Type:
ansible windows -c ipconfig
If this command is successful, the next steps will be to build Ansible playbooks to manage Windows Servers.
Managing Windows Servers with Playbooks
Let’s create some playbooks and test Ansible for real on Windows systems.
- Create a folder on
Ansible1for the playbooks, YAML files, modules, scripts, etc. For these exercises, we created a folder under/rootcalledwin_playbooks.

Ansible has some expectations on the directory structure where playbooks reside. Create the library and scripts folders for use later in this exercise.
Commands:
cd /root
mkdir win_playbooks
mkdir win_playbooks/library
mkdir win_playbooks/scripts
- Create the first playbook example “netstate.yml”
The contents are:
- name: test cmd from win_command module
hosts: windows
tasks:
- name: run netstat and return Ethernet stats
win_command: netstat -e
register: netstat
- debug: var=netstat
This playbook does only one task, to connect to the servers in the Ansible inventory group windows and run the command netstat.exe -a and return the results.
To run this playbook, run this command on Ansible1: ansible-playbook netstat_e.yml

OK, not exciting, but it did run, just not very friendly.
Also, notice that the changed flag is set. As of the time this article was created, all of the Windows commands return the changed flag as true. This is the same with running PowerShell scripts remotely. This makes much of the value of Ansible difficult as a configuration and deployment tool.
Interestingly enough, Ansible modules created with PowerShell do work correctly and return the changed flag correctly. PowerShell scripts and direct commands always return the changed flag as true. This is problematic for managing systems with Ansible. The rest of this article will focus on PowerShell modules that can perform complex management functions as well as integrate with other non-Windows systems.
Creating Ansible Modules with PowerShell
Ansible modules are plugin programs which are:
- Loaded by Ansible when running a playbook test
- Ansible generates module input parameters in the JSON format
- Modifies the module into a generalized script and command
- Copies the modified script to the remote system(s)
- Executes the modified module on the remote system
- The module generates a response in JSON and this response is returned
- The returned JSON is parsed and values are saved or used by other tests
- The module returns a flag called
changedwhich is essential to maintain “Desired State Configurations”. A return ofchanged = Truewill signal other tests to run to achieve the “Desired State”
Modules are stored in several locations where Ansible will find them. One location is a folder named library located in the folder where the playbook is run. Modules stored in our library are first in the module search path and will override modules of the same name.
This example lab has a folder win_playbooks/library/

PowerShell modules consist of two programs, a Python program, and a PowerShell program. The Python program configures the local Ansible environment and the PowerShell program does the actual processing on the remote systems.
Create a file called win_playbooks/library/get_version.py and add the following contents:
#!/usr/bin/python
# -*- coding: utf-8 -*-
#License: Public Domain
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['stableinterface'],
'supported_by': 'core'}
DOCUMENTATION = r'''
---
module: get_version
short_description: Windows get OS verison and return changed flag if below min
description:
- Checks Windows OS version and returns the version data in JSON
- Checks that the min version against the passed "major,minor,build" parameters
- Returns "changed" if the remote system OS version is below a minimum
- The "changed" flag is to notify operations or to trigger another task
options:
major:
description:
- Windows major version [MAJOR.minor.build]
default: 5
minor:
description:
- Windows minor version [major.MINOR.build]
default: 1
build:
description:
- Windows build version [major.minor.BUILD]
default: None
author:
- Robert Keith (Argon Systems)
'''
EXAMPLES = r'''
# Example Ansible Playbook
- name: test remote Windows OS version
hosts: windows
tasks:
- name: PowerShell module example to get Windows OS version
get_version:
major: "5"
minor: "1"
build: "13493"
'''
RETURN = '''
description: Output of (Get-Host).Version powershell in JSON format
returned: success
type: string
'''
Next create a file win_playbooks/library/get_version.ps1 and add the following contents:
#!powershell
# WANT_JSON
# POWERSHELL_COMMON
$ErrorActionPreference = "Stop"
$params = Parse-Args $args -supports_check_mode $true
$MajorVer = Get-AnsibleParam -obj $params -name "major" -type "str"
$MinorVer = Get-AnsibleParam -obj $params -name "minor" -type "str"
$BuildVer = Get-AnsibleParam -obj $params -name "build" -type "str"
#Should validate input parameters here naturally
$Vers = (Get-Host).Version
$Message = $Message = "Windows OS is at desired version"
$BelowMin = $false
if ($Vers.Major -lt $MajorVer) { $BelowMin = $true }
if ($Vers.Minor -lt $MinorVer) { $BelowMin = $true }
if ($Vers.Build -lt $BuildVer) { $BelowMin = $true }
if ($BelowMin) { $Message = "Windows OS below desired version" }
$result = @{
changed = $BelowMin
version = $Vers
message = $Message
desired_major_ver = $MajorVer
desired_minor_ver = $MinorVer
desired_build_ver = $BuildVer
}
Exit-Json $result
Next, create the playbook YAML file to set up and run the new module.
From the win_playbooks folder, create a file with the following contents:
- name: test simple powershell module - get OS version and test for minimum desired version
hosts: windows
tasks:
- name: simple powershell module example to get Windows OS version
get_version:
major: "5"
minor: "1"
build: "14393"
Execute this playbook with the command:
ansible-playbook get_version.yml

Notice the changed flag is set to False. In this case, this means the remote operating system matched the values in the get_version.yml playbook.
If we set the Verbose flag, we see the data returned from the PowerShell module in the JSON format.
ansible-playbook get_version.yml -v

Setting the Very Very Verbose flag gives more information, and oddly enough formats the JSON into a more readable format.
ansible-playbook get_version.yml -vvv

Now if we modify the playbook expecting a different version. We set the major version from 5 to 9 (which does not exist).
- name: test simple powershell module - get OS version and test for minimum desired version
hosts: windows
tasks:
- name: simple powershell module example to get Windows OS version
get_version:
major: "9"
minor: "1"
build: "14393"
And run the playbook again
ansible-playbook get_version.yml

The “changed” flag is set.
We could extend this playbook with other PowerShell modules, for example:
- name: test simple powershell module - get OS version and test for minimum desired version
hosts: windows
tasks:
- name: simple powershell module example to get Windows OS version
get_version:
major: "9"
minor: "1"
build: "14393"
register: get_version_output
- debug:
var: get_version_output.message
- enable_win_updates: Enable unattended Windows Updates
when get_version_output.changed
This playbook could check the Windows version, and if it is below a version threshold, the next task could then enable updates, manually install updates, or practically any possible action imaginable to maintain the Windows servers to a “Desired State”.
Summary
Ansible is a powerful management and DevOps framework. It is complex to set up initially, but relatively simple to operate. Playbooks can be created that manage large and complex environments consisting of cloud systems, servers, storage systems, network systems, security systems, hardware and facilities management devices, and any of a large set of disparate systems.
Configured correctly, managing and monitoring complex can be consolidated into a single framework, and with the Ansible Windows support and some initial instructions detailed in this article, can include Windows Servers, Windows desktops and other Windows-based systems.

On picture both Centos and Windows hosts have the same IP addresses.
Oops
Thank you for the tutorial. It was excellent 🙂
I have proxy that ansible needs to go through to connect to windows hosts. How i can add this in the windows var so that it uses the proxy to create the tunnel connection? when i use win_ping
Thanks for giving this informative blog. Windows Server 2008 can only install PowerShell 3.0; specifying a newer version .
This is really comprehensive, however, I can’t get the ansible server to connect to the windows servers. I am getting a | UNREACHABLE! => {
“changed”:false,
“msg”; “ssl: the specified credentials were rejected by the server”,
“unreachable”: true
}
I am working in a test lab that has a domain established already and the authentication seems to be working, however the group_vars config seems to only work for the local administrator. I have created an account that is part of the local administrator group and it won’t work. I did create an account locally and it works so there seems to be some domain authentication that isn’t being passed through.
Any help would be awesome…
did you install Kerberos on Ansible control box.
configure krb5.conf
anisble_user has to be in this format username@DOMAIN.COM for kerberos
the user you authenticate with needs permissions on windows box
I have installed everything as per this lab setup like the krb5 config file, name server etc., but still, I am getting the following error.
fatal: [172.20.16.9]: UNREACHABLE! => {
“changed”: false,
“msg”: “kerberos: authGSSClientStep() failed: ((‘Unspecified GSS failure. Minor code may provide more information’, 851968), (‘Server not found in Kerberos database’, -1765328377))”,
“unreachable”: true
}
please help me with that.
The most succinct and to the point beginners tutorial I’ve come across to date. As well as adding basic powershell modules to my ansible arsenal I was also able to combine this with jinja2 templating examples elsewhere to pull the results of a dotnet version query from multiple windows servers into an easy to read report. Great article!!
The directory “group_vars” (/etc/ansible/group_vars) didn’t exist in my environment (clean centos install, then followed your setup for downloading/installing ansible. I realize I can create the directory, but just wondering if that directory should have already been there?
Nevermind, I created the directory and it worked
There is a warning on Ansible website about runnign the powershell config script:
“The ConfigureRemotingForAnsible.ps1 script is intended for training and development purposes only and should not be used in a production environment, since it enables settings (like Basic authentication) that can be inherently insecure.”
Yes, the Ansible article on this PS Function: ConfigureRemotingForAnsible.ps1 describes this option:
-DisableBasicAuth to disable basic authenticationIf Basic Authentication is disabled, then you will need to configure Ansible to use certificates instead of passing username/passwords.
hi Robert,
Trying to ping all VMs in Azure via Dynamic Inventory by running “ansible -i ./ansible/contrib/inventory/azure_rm.py windows -m win_ping” and getting following error.
Server01 | UNREACHABLE! => {
“changed”: false,
“msg”: “ssl: auth method ssl requires a username”,
“unreachable”: true
}
SERVER02 | UNREACHABLE! => {
“changed”: false,
“msg”: “Failed to connect to the host via ssh: ssh: Could not resolve hostnameSERVER02: Name or service not known”,
“unreachable”: true
Hi All,
i have installed the ansible 2.9 in ubuntu , enabled WinRM in another windows host , connection successful but when i tried to run the simple powershell script on target windows host getting error like below can any one of you help to solve the issue.
error ‘win_command’ is not a valid attribute for a play
script :
– name: Run remote PowerShell Script
win_command: powershell.exe -ExecutionPolicy ByPass -File C:/temp/powershellscript.ps1
Thanks, this is a very interesting article.
I have a (hopefully) small problem:
ansible system_servers -m win_ping works fine
ansible system_servers -m setup works fine too
ansible system_servers -c ipconfig fails with error “ERROR! No argument passed to command module”
Any idea how to solve this?
Nice article. I’ve bee looking to take this on for some time. Finally got some free time to setup this config. All was good until the kinit administrator@homelab.local command. I’m getting the following error: Cannot contact any KDC for realm ‘HOMELAB.LOCAL’ while getting initial credentials.
winrm or requests is not installed no module named winrm
let me google that for you…
https://www.mail-archive.com/ansible-project@googlegroups.com/msg47885.html
https://access.redhat.com/solutions/3356681
https://github.com/ansible/ansible/issues/58805
Thanks for the Article, it is very help full.
Does any one have playbooks to install Oracle WebLogic patches on a windows hosts.
]# ansible windows -m win_ping
gives below error
172.31.37.252 | UNREACHABLE! => {
“changed”: false,
“msg”: “ssl: HTTPSConnectionPool(host=’172.31.37.252′, port=5986): Max retries exceeded with url: /wsman (Caused by ConnectTimeoutError(, ‘Connection to 172.31.37.252 timed out. (connect timeout=30)’))”,
“unreachable”: true
can we configure windows file and print services / gateway & terminal services using Ansible ?
SCVMM powershell commands to install a new NIC on a specific network , is it possible ? Please help
requires bind-utils for nslookup to work.
A few things seem strange here – there is only one network adapter on the Centos server but the steps seem to give it a static IP address before the software has been downloaded from a public yum repo which needs the default DHCP option surely ?
Thank you Robert. Really good article.
Can you please share some updates for python3 ? I am having some issue with python3 winrm module not being part of python by default.
how to make windows domain user entry in ansible host or inventory file?
Is that worked with your method??