Automating Windows Servers Configurations with Infrastructure as Code Configuration Management Tool (Ansible)

Daniel Dimitrov
Daniel Dimitrov
DevOps & Cloud Engineer
Reading time: 5 mins.
Last Updated: 12.02.2024

Table of Contents

In the realm of DevOps, there is always a drive for automating, even though it’s not always possible, but at least we can try it. We all know what Ansible is: a Configuration Management tool for Automation and Infrastructure as Code. Typically associated with Linux and Unix systems, open-source technology, Python, and SSH, Ansible might seem primarily tailored for the Linux ecosystem. However, in the real world, not everything runs on Linux. However, sometimes you have to automate the configuration of Windows machines. There are instances where automating Windows server configurations is necessary, and sometimes, this automation needs to occur within a pipeline triggered by a Linux container.

Why choose Ansible? Are there any other tools for Windows Server Configuration Management? Probably, but the Red Hat software is ageless, idempotent and with YAML-based automation syntax which is quick to learn. There is no need for a more complex solution. This is automation of Windows Servers, not rocket science.

Setting up connection with Windows Server

If you have prior experience with Ansible or if you have read our beginner’s guide, you will know that the connectivity between Ansible controller and the hosts is made via SSH. However, when dealing with Windows servers, we should use something called WINRM. Yeah, there is OpenSSH for Windows, but WinRM is preinstalled on every Windows Server. 

Also, we have to use PowerShell for this automation and modules for Windows, but first let’s connect a Linux controller with Windows Server 2019.

Windows Server 2019 Setup:

  1. First we have to make sure that the WinRm is running on the Server:
PS C:\Users\itgix\Downloads> winrm quickconfig
WinRM service is already running on this machine.
WinRM is already set up for remote management on this computer.

If it’s not running you can always enable it with:

PS C:\Users\itgix\Downloads> Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-RemoteManagement-Client

2. Next we can see if there are any Listeners configured to the Server:

PS C:\Users\itgix\Downloads> winrm enumerate winrm/config/Listener
    Address = *
    Transport = HTTP
    Port = 5985
    Enabled = true
    URLPrefix = wsman
    ListeningOn =,, ::1, fe80::9745:976c:b3e1:74bc%2

This should be the default Listener configuration. You can always Set Up a new Listener.  Also there is something we should point out. You can see the example that the listener is configured on port 5985, this is not a secure port (it’s not using SSL certificate). The secure WinRM port is 5986, but you’ll have to have and set up an SSL certificate to perform this type of connection. 

If you have a certificate you can configure it by following this guide from Microsoft. However our focus is not the security in this case, but we highly recommend caution when ssl is not used.

3. We need a dedicated Windows user to perform the tasks. In Ansible, we always have a user that performs the tasks that the controller sends. In that Windows case the user should be Administrator. Every admin user can be used, but normally when we use Ansible, we use a dedicated user. That’s just best practice.

4. Install Chocolatey

This will be needed so the Ansible module will use chocolatey package manager to download stuff:

Set-ExecutionPolicy Bypass -Scope Process -Force; `
  iex ((New-Object System.Net.WebClient).DownloadString(''))

Linux Set Up:

As everything has to be user friendly at the end, the Ansible playbook will be runned by a GitLab Pipeline, so it will be executed on a GitLab runner. The one we have is running on a Linux container, more specifically Alpine Container.

  1. Check the Windows Server connection first.

As Linux is going to connect to another virtual machine, first we have to make sure that they can connect to each other (check port availability).

Our Windows Server is located in Azure Cloud, so we need to make an inbound port rule to access port 5985 / or 5986

If it’s in another Cloud the concept is the same: AWS – Security Group inbound rule, GCP firewall rule, etc. 

If you’re using a virtual machine on your own server or local hypervisor, you should create a firewall rule to open the exact port.

After that we can check availability of the port from the Alpine Container:

# apk add net-tools
# telnet /WINDOWS IP/ 5985
Connected to /WINDOWS IP/

2. Install the needed dependencies:

# apk add python3 py3-pip gcc libffi-dev ansible
# pip3 install "pywinrm>=0.3.0"

3. Set up the inventory file

Now for the project we have to set up Ansible inventory (host) file and needed variables for the connection (port, user, password):

{ip addresses for the windows vms (one per row)}
ansible_user={Windows Admin User}

Check hosts:

 # ansible -i hosts  all --list-hosts
  hosts (2):

Create Ansible Playbook

Now is the time to do the configuration part of building an Ansible pipeline. Something simple can look like this:

- name: Set Up Automation of Windows Server 2019
  hosts: win
    # installing dependencies and packages
    - name: Install C#
        name: "{{ item }}"
        state: present
        - dotnet
    - name: Install Firefox using Chocolatey
        name: firefox
        state: present
    - name: Install Notepad++ using Chocolatey
        name: notepadplusplus
        version: 8.5.2
        state: present
    - name: Install Prometheus Windows Exporter using Chocolatey
        name: prometheus-windows-exporter.install
        state: present

This Ansible Playbook is installing some packages and dependencies via win_chocolatey

So to test Ansible playbook run:

# ansible-playbook -i hosts main.yml
PLAY [Set Up Automation of Windows Server 2019] ********************************
TASK [Gathering Facts] *********************************************************
changed: [windows1]
TASK [Install C#] **************************************************************
changed: [windows1] => (item=dotnet)
TASK [Install Firefox using Chocolatey] ****************************************
changed: [windows1]
TASK [Install Notepad++ using Chocolatey] **************************************
changed: [windows1]
TASK [Install Prometheus Windows Exporter using Chocolatey] ********************
changed: [windows1]
PLAY RECAP ******************************              : ok=5    changed=5    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

As you can see, the dependencies are successfully installed on the Windows Server from the Alpine container. For the test it is used only one Windows VM – windows1.

GitLab Pipeline

One final step. We have to create a Pipeline to run the playbook from.

Here is an example of pipeline you can use:

      - "/usr/bin/env"
      - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
  - windows-ansible-playbook

  stage: windows-ansible-playbook
    - ansible --version
    - ansible-playbook -i hosts main.yml
  when: manual

In this example, you can see that we’re exporting 3 variables. This is for the inventory file, because it’s not secure to put windows user and password directly in the file. The variables are taken from the GitLab CICD Environment Variables:

Then we can run the pipeline:


Automation is a goal for every DevOps Engineer. But this doesn’t need to be complex and everything to be automated at all costs. There are always the right tools for every situation and infrastructure.

This example of configuring Windows servers could be done with Server Templating tools like Packer for example. Depends on the situation and environment. Architecture is the key. 

4 Responses

  1. At the beginning of the article, you say ‘However, when dealing with Windows servers, we should use something called WINRM. Yeah, there is OpenSSH for Windows, but WinRM is preinstalled on every Windows Server.’, but in step 4 :
    4. Install Chocolatey :
    -first you can not install Chocolatey with:
    choco install –package-parameters=/SSHServerFeature OpenSSH
    because this command use choco
    With this command you install OpenSSH . My question is do we need OpenSSH to use ansible or not?

    1. Note appreciated, we fixed the code in the article. You are now able to check it again. Thank you for the comment and your detailed eye!

Leave a Reply

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

More Posts

In the dynamic world of serverless computing, securing your AWS Lambda function is crucial. However, one often neglected area is the security of containerized applications in Amazon Elastic Container Registry...
Note: The following example demonstrates upgrading a Kubernetes cluster from version 1.23 to 1.24. Replace the version numbers according to your specific setup. To ensure a seamless upgrade, it’s crucial...
Get In Touch
ITGix provides you with expert consultancy and tailored DevOps services to accelerate your business growth.
Newsletter for
Tech Experts
Join 12,000+ business leaders and engineers who receive blogs, e-Books, and case studies on emerging technology.