Contents
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 step through the steps of deploying the Ansible controlling node on CentOS 7, and 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 managing 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
- Once 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 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.
Name | 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
1 2 3 4 5 6 7 8 9 10 | 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 the private network and is shared only with the other servers in this lab.
- Edit /etc/sysconfig/network-scripts/ifcfg-eth1
1 2 3 4 5 6 7 8 9 10 11 12 | 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=yes |
The resolv.conf configures the Linux DNS client.
- Edit /etc/resolv/conf
1 2 | search Lab1AD1.local nameserver 172.20.20.1 |
systemctl disable NetworkManager
Set the hostname
- Edit: /etc/hostname
1 | 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
1 | yum -y group install “Development Tools” |
- Install EPEL
1 | yum -y install epel-release |
- Install Ansible
1 | yum -y install ansible |
- Install Kerberos
1 | yum -y install python-devel krb5-devel krb5-libs krb5-workstation |
- Install Python PIP
1 | yum -y install python-pip |
- Install BIND utilities for nslookup
1 | yum -y install bind-utils |
- Bring all packages up to the latest version
1 | yum -y update |
Check Ansible and Python is Installed
- Run the commands:
1 2 | 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:
1 | pip install pywinrm[Kerberos] |
Kerberos packages were installed previously which will have created /etc/krb5.conf
- Edit /etc/krb5.conf
Add:
1 2 3 4 | [realms] LAB1AD1.LOCAL = { kdc = AD1.LAB1AD1.LOCAL } |
Add:
1 2 3 | [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:
1 | 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 which 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/hosts and add:
1 2 | [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 which 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/windows and add:
1 2 3 4 5 6 | --- 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
To configure 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 Administrator.
Log into WinServer1 as 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 simply 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 setup
to 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 Ansible1 for the playbooks, YAML files, modules, scripts, etc. For these exercises we created a folder under /root called win_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:
1 2 3 4 | cd /root mkdir win_playbooks mkdir win_playbooks/library mkdir win_playbooks/scripts |
- Create the first playbook example “netstate.yml”
The contents are:
1 2 3 4 5 6 7 | - 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 which 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 use by other tests
- The module returns a flag called “changed” which is important to maintain “Desired State Configurations”. A return of “changed = True” will signal other tests to run to achieve “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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #!/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's OS version is below a minimum - The "changed" flag is t 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #!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 setup and run the new module.
From the win_playbooks folder, create a file with the following contents:
1 2 3 4 5 6 7 8 | - 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:
1 | 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.
1 | ansible-playbook get_version.yml -v |
Setting the Very Very Verbose flag give more information, and oddly enough formats the JSON into a more readable format.
1 | 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).
1 2 3 4 5 6 7 8 | - 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
1 | ansible-playbook get_version.yml |
The “changed” flag is set.
We could extend this playbook with other PowerShell modules, for example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - 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 setup initially, but relatively simple to operate. Playbooks can be created which 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 onto 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
In the /etc/ansible/group_vars/windows file, add:
ansible_winrm_transport: kerberos
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 authentication
If 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 ?