+

Search Tips   |   Advanced Search

Windows module development walkthrough

In this section, we will walk through developing, testing, and debugging an Ansible Windows module.

Because Windows modules are written in Powershell and need to be run on a Windows host, this guide differs from the usual development walkthrough guide.

What's covered in this section:


Windows environment setup

Unlike Python module development which can be run on the host that runs Ansible, Windows modules need to be written and tested for Windows hosts. While evaluation editions of Windows can be downloaded from Microsoft, these images are usually not ready to be used by Ansible without further modification. The easiest way to set up a Windows host so that it is ready to by used by Ansible is to set up a virtual machine using Vagrant. Vagrant can be used to download existing OS images called boxes that are then deployed to a hypervisor like VirtualBox. These boxes can either be created and stored offline or they can be downloaded from a central repository called Vagrant Cloud.

This guide will use the Vagrant boxes created by the packer-windoze repository which have also been uploaded to Vagrant Cloud. To find out more info on how these images are created, please go to the GitHub repo and look at the README file.

Before you can get started, the following programs must be installed (please consult the Vagrant and VirtualBox documentation for installation instructions):


Create a Windows server in a VM

To create a single Windows Server 2016 instance, run the following:

This will download the Vagrant box from Vagrant Cloud and add it to the local boxes on your host and then start up that instance in VirtualBox. When starting for the first time, the Windows VM will run through the sysprep process and then create a HTTP and HTTPS WinRM listener automatically. Vagrant will finish its process once the listeners are online, after which the VM can be used by Ansible.


Create an Ansible inventory

The following Ansible inventory file can be used to connect to the newly created Windows VM:

The port 55986 is automatically forwarded by Vagrant to the Windows host that was created, if this conflicts with an existing local port then Vagrant will automatically use another one at random and display show that in the output.

The OS that is created is based on the image set. The following images can be used:

When the host is online, it can accessible by RDP on 127.0.0.1:3389 but the port may differ depending if there was a conflict. To get rid of the host, run vagrant destroy --force and Vagrant will automatically remove the VM and any other files associated with that VM.

While this is useful when testing modules on a single Windows instance, these host won't work without modification with domain based modules. The Vagrantfile at ansible-windows can be used to create a test domain environment to be used in Ansible. This repo contains three files which are used by both Ansible and Vagrant to create multiple Windows hosts in a domain environment. These files are:

By default, these files will create the following environment:

The domain name and accounts can be modified by changing the variables domain_* in the inventory.yml file if it is required. The inventory file can also be modified to provision more or less servers by changing the hosts that are defined under the domain_children key. The host variable ansible_host is the private IP that will be assigned to the VirtualBox host only network adapter while vagrant_box is the box that will be used to create the VM.


Provisioning the environment

To provision the environment as is, run the following:

Vagrant provisions each host sequentially so this can take some time to complete. If any errors occur during the Ansible phase of setting up the domain, run vagrant provision to rerun just that step.

Unlike setting up a single Windows instance with Vagrant, these hosts can also be accessed using the IP address directly as well as through the forwarded ports. It is easier to access it over the host only network adapter as the normal protocol ports are used, for example RDP is still over 3389. In cases where the host cannot be resolved using the host only network IP, the following protocols can be access over 127.0.0.1 using these forwarded ports:

Replace xx with the entry number in the inventory file where the domain controller started with 00 and is incremented from there. For example, in the default inventory.yml file, WinRM over HTTPS for SERVER2012R2 is forwarded over port 29804 as it's the fourth entry in domain_children.

While an SSH server is available on all Windows hosts but Server 2008 (non R2), it is not a support connection for Ansible managing Windows hosts and should not be used with Ansible.


Windows new module development

When creating a new module there are a few things to keep in mind:

A very basic Powershell module win_environment incorporates best practices for Powershell modules. It demonstrates how to implement check-mode and diff-support, and also shows a warning to the user when a specific condition is met.

A slightly more advanced module is win_uri which additionally shows how to use different parameter types (bool, str, int, list, dict, path) and a selection of choices for parameters, how to fail a module and how to handle exceptions.

As part of the new AnsibleModule wrapper, the input parameters are defined and validated based on an argument spec. The following options can be set at the root level of the argument spec:

The actual input options for a module are set within the options value as a dictionary. The keys of this dictionary are the module option names while the values are the spec of that module option. Each spec can have the following options set:

When type=dict, or type=list and elements=dict, the following keys can also be set for that module option:

A module type can also be a delegate function that converts the value to whatever is required by the module option. For example the following snippet shows how to create a custom type that creates a UInt64 value:

When in doubt, look at some of the other core modules and see how things have been implemented there.

Sometimes there are multiple ways that Windows offers to complete a task; this is the order to favor when writing modules:

PowerShell modules support a small subset of the #Requires options built into PowerShell as well as some Ansible-specific requirements specified by #AnsibleRequires. These statements can be placed at any point in the script, but are most commonly near the top. They are used to make it easier to state the requirements of the module without writing any of the checks. Each requires statement must be on its own line, but there can be multiple requires statements in one script.

These are the checks that can be used within Ansible modules:

C# module utils can reference other C# utils by adding the line using Ansible.<module_util>; to the top of the script with all the other using statements.


Windows module utilities

Like Python modules, PowerShell modules also provide a number of module utilities that provide helper functions within PowerShell. These module_utils can be imported by adding the following line to a PowerShell module:

This will import the module_util at ./lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1 and enable calling all of its functions. As of Ansible 2.8, Windows module utils can also be written in C# and stored at lib/ansible/module_utils/csharp. These module_utils can be imported by adding the following line to a PowerShell module:

This will import the module_util at ./lib/ansible/module_utils/csharp/Ansible.Basic.cs and automatically load the types in the executing process. C# module utils can reference each other and be loaded together by adding the following line to the using statements at the top of the util:

There are special comments that can be set in a C# file for controlling the compilation parameters. The following comments can be added to the script;

As well as this, the following pre-processor symbols are defined;

A combination of these flags help to make a module util interoperable on both .NET Framework and .NET Core, here is an example of them in action:

The following is a list of module_utils that are packaged with Ansible and a general description of what they do:

For more details on any specific module utility and their requirements, please see the Ansible module utilities source code.

PowerShell module utilities can be stored outside of the standard Ansible distribution for use with custom modules. Custom module_utils are placed in a folder called module_utils located in the root folder of the playbook or role directory.

C# module utilities can also be stored outside of the standard Ansible distribution for use with custom modules. Like PowerShell utils, these are stored in a folder called module_utils and the filename must end in the extension .cs, start with Ansible. and be named after the namespace defined in the util.

The below example is a role structure that contains two PowerShell custom module_utils called Ansible.ModuleUtils.ModuleUtil1, Ansible.ModuleUtils.ModuleUtil2, and a C# util containing the namespace Ansible.CustomUtil:

Each PowerShell module_util must contain at least one function that has been exported with Export-ModuleMember at the end of the file. For example


Exposing shared module options

PowerShell module utils can expose common module options that a module can use when building its argument spec. This allows common features to be stored and maintained in one location and have those features used by multiple modules with minimal effort. Any new features or bugifxes added to one of these utils are then automatically used by the various modules that call that util.

An example of this would be to have a module util that handles authentication and communication against an API This util can be used by multiple modules to expose a common set of module options like the API endpoint, username, password, timeout, cert validation, and so on without having to add those options to each module spec.

The standard convention for a module util that has a shared argument spec would have

Because these options can be shared across various module it is highly recommended to keep the module option names and aliases in the shared spec as specific as they can be. For example do not have a util option called password, rather you should prefix it with a unique name like acme_password.

Warning

Failure to have a unique option name or alias can prevent the util being used by module that also use those names or aliases for its own options.

The following is an example module util called ServiceAuth.psm1 in a collection that implements a common way for modules to authentication with a service.

For a module to take advantage of this common argument spec it can be set out like

Options defined in the module spec will always have precedence over a util spec. Any list values under the same key in a util spec will be appended to the module spec for that same key. Dictionary values will add any keys that are missing from the module spec and merge any values that are lists or dictionaries. This is similar to how the doc fragment plugins work when extending module documentation.

To document these shared util options for a module, create a doc fragment plugin that documents the options implemented by the module util and extend the module docs for every module that implements the util to include that fragment in its docs.


Windows playbook module testing

You can test a module with an Ansible playbook. For example:

This can be useful for seeing how Ansible runs with the new module end to end. Other possible ways to test the module are shown below.


Windows debugging

Debugging a module currently can only be done on a Windows host. This can be useful when developing a new module or implementing bug fixes. These are some steps that need to be followed to set this up:

You can add more args to $complex_args as required by the module or define the module options through a JSON file with the structure:

There are multiple IDEs that can be used to debug a Powershell script, two of the most popular ones are

To be able to view the arguments as passed by Ansible to the module follow these steps.


Windows unit testing

Currently there is no mechanism to run unit tests for Powershell modules under Ansible CI.


Windows integration testing

Integration tests for Ansible modules are typically written as Ansible roles. These test roles are located in ./test/integration/targets. You must first set up your testing environment, and configure a test inventory for Ansible to connect to.

In this example we will set up a test inventory to connect to two hosts and run the integration tests for win_stat:

This will execute all the tests currently defined for that role. You can set the verbosity level using the -v argument just as you would with ansible-playbook.

When developing tests for a new module, it is recommended to test a scenario once in check mode and twice not in check mode. This ensures that check mode does not make any changes but reports a change, as well as that the second run is idempotent and does not report changes. For example:


Windows communication and development support

Join the IRC channel #ansible-devel or #ansible-windows on freenode for discussions about Ansible development for Windows.

For questions and discussions pertaining to using the Ansible product, use the #ansible channel.

Next Previous