Select Page

Configuring Ansible to Manage Windows Servers – Step by Step

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 Environment

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
The Windows systems are not required to be domain joined. In this example, the Windows system is a standalone WORKGROUP machine.

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.

Ansible Managing Windows Server

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.
  • After installing Windows Server 2016, apply all the latest Microsoft Updates
  • Rename the server to WinServer1 (or whatever you like)
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.
  • Apply all the latest Microsoft Updates
  • Rename the server to AD1 (or whatever)
  • Install the Active Directory Domain Services role
  • Configure the Active Directory
  • Domain: Lab1AD1.local

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 the 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.

  1. 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"
PEERDNS is set to “no” to prevent DHCP from inserting external DNS records. The only DNS records should come from AD1.

The eth1 interface is the private network and is shared only with the other servers in this lab.

  1. 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=yes

The resolv.conf configures the Linux DNS client.

  1. Edit /etc/resolv/conf
search Lab1AD1.local
nameserver 172.20.20.1
If Network Manager is running (the default configuration), it will overwrite this configuration. You can disable the Network Manager with the command systemctl disable NetworkManager

Set the hostname

  1. Edit: /etc/hostname
Ansible1
  1. Reboot the server

Type: reboot

  1. 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 the Ansible Environment

Other versions of Linux will work equally well. The configuration commands will have to be adjusted for each version of Linux.

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 that Ansible and Python is Installed

Run the commands:

ansible - - version | head -l 1
python - - version

The versions of Ansible and Python here are 2.4.2 and 2.7.5. Ansible is developing extremely rapidly so these instructions will likely change in the near future.

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:

Only the [realms] and [domain_realm] were updated manually.

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 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:

[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:

---
ansible_user: Administrator
ansible_password: Abcd1234
ansible_port: 5986
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore
This is a YAML configuration file, so make sure the first line is three dashes “‐‐‐”

Naturally change the Administrator password to the password for WinServer1.

For best practices, Ansible can encrypt this file into the Ansible Vault. This would prevent the password from being stored here in clear text. For this lab, we are attempting to keep the configuration as simple as possible. Naturally in production this would not be appropriate.

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:

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 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:

  1. Loaded by Ansible when running a playbook test
  2. Ansible generates module input parameters in the JSON format
  3. Modifies the module into a generalized script and command
  4. Copies the modified script to the remote system(s)
  5. Executes the modified module on the remote system
  6. The module generates a response in JSON and this response is returned
  7. The returned JSON is parsed and values are saved or use by other tests
  8. 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:

#!/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:

#!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:

- 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 give 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 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.

Was this article helpful?

10 Comments

  1. kamil

    On picture both Centos and Windows hosts have the same IP addresses.

    Reply
  2. Nick Tailor

    Thank you for the tutorial. It was excellent 🙂

    Reply
  3. Nick Tailor

    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

    Reply
  4. support wala

    Thanks for giving this informative blog. Windows Server 2008 can only install PowerShell 3.0; specifying a newer version .

    Reply
  5. Stanton

    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…

    Reply
    • HappyCamper

      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

      Reply
  6. Satheesh Subramanian

    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.

    Reply
  7. Mickey

    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!!

    Reply
  8. Grant

    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?

    Reply
    • Grant

      Nevermind, I created the directory and it worked

      Reply

Submit a Comment

Your email address will not be published. Required fields are marked *