1.. _developing_modules_general_windows: 2 3************************************** 4Windows module development walkthrough 5************************************** 6 7In this section, we will walk through developing, testing, and debugging an 8Ansible Windows module. 9 10Because Windows modules are written in Powershell and need to be run on a 11Windows host, this guide differs from the usual development walkthrough guide. 12 13What's covered in this section: 14 15.. contents:: 16 :local: 17 18 19Windows environment setup 20========================= 21 22Unlike Python module development which can be run on the host that runs 23Ansible, Windows modules need to be written and tested for Windows hosts. 24While evaluation editions of Windows can be downloaded from 25Microsoft, these images are usually not ready to be used by Ansible without 26further modification. The easiest way to set up a Windows host so that it is 27ready to by used by Ansible is to set up a virtual machine using Vagrant. 28Vagrant can be used to download existing OS images called *boxes* that are then 29deployed to a hypervisor like VirtualBox. These boxes can either be created and 30stored offline or they can be downloaded from a central repository called 31Vagrant Cloud. 32 33This guide will use the Vagrant boxes created by the `packer-windoze <https://github.com/jborean93/packer-windoze>`_ 34repository which have also been uploaded to `Vagrant Cloud <https://app.vagrantup.com/boxes/search?utf8=%E2%9C%93&sort=downloads&provider=&q=jborean93>`_. 35To find out more info on how these images are created, please go to the GitHub 36repo and look at the ``README`` file. 37 38Before you can get started, the following programs must be installed (please consult the Vagrant and 39VirtualBox documentation for installation instructions): 40 41- Vagrant 42- VirtualBox 43 44Create a Windows server in a VM 45=============================== 46 47To create a single Windows Server 2016 instance, run the following: 48 49.. code-block:: shell 50 51 vagrant init jborean93/WindowsServer2016 52 vagrant up 53 54This will download the Vagrant box from Vagrant Cloud and add it to the local 55boxes on your host and then start up that instance in VirtualBox. When starting 56for the first time, the Windows VM will run through the sysprep process and 57then create a HTTP and HTTPS WinRM listener automatically. Vagrant will finish 58its process once the listeners are online, after which the VM can be used by Ansible. 59 60Create an Ansible inventory 61=========================== 62 63The following Ansible inventory file can be used to connect to the newly 64created Windows VM: 65 66.. code-block:: ini 67 68 [windows] 69 WindowsServer ansible_host=127.0.0.1 70 71 [windows:vars] 72 ansible_user=vagrant 73 ansible_password=vagrant 74 ansible_port=55986 75 ansible_connection=winrm 76 ansible_winrm_transport=ntlm 77 ansible_winrm_server_cert_validation=ignore 78 79.. note:: The port ``55986`` is automatically forwarded by Vagrant to the 80 Windows host that was created, if this conflicts with an existing local 81 port then Vagrant will automatically use another one at random and display 82 show that in the output. 83 84The OS that is created is based on the image set. The following 85images can be used: 86 87- `jborean93/WindowsServer2008-x86 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2008-x86>`_ 88- `jborean93/WindowsServer2008-x64 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2008-x64>`_ 89- `jborean93/WindowsServer2008R2 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2008R2>`_ 90- `jborean93/WindowsServer2012 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2012>`_ 91- `jborean93/WindowsServer2012R2 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2012R2>`_ 92- `jborean93/WindowsServer2016 <https://app.vagrantup.com/jborean93/boxes/WindowsServer2016>`_ 93 94When the host is online, it can accessible by RDP on ``127.0.0.1:3389`` but the 95port may differ depending if there was a conflict. To get rid of the host, run 96``vagrant destroy --force`` and Vagrant will automatically remove the VM and 97any other files associated with that VM. 98 99While this is useful when testing modules on a single Windows instance, these 100host won't work without modification with domain based modules. The Vagrantfile 101at `ansible-windows <https://github.com/jborean93/ansible-windows/tree/master/vagrant>`_ 102can be used to create a test domain environment to be used in Ansible. This 103repo contains three files which are used by both Ansible and Vagrant to create 104multiple Windows hosts in a domain environment. These files are: 105 106- ``Vagrantfile``: The Vagrant file that reads the inventory setup of ``inventory.yml`` and provisions the hosts that are required 107- ``inventory.yml``: Contains the hosts that are required and other connection information such as IP addresses and forwarded ports 108- ``main.yml``: Ansible playbook called by Vagrant to provision the domain controller and join the child hosts to the domain 109 110By default, these files will create the following environment: 111 112- A single domain controller running on Windows Server 2016 113- Five child hosts for each major Windows Server version joined to that domain 114- A domain with the DNS name ``domain.local`` 115- A local administrator account on each host with the username ``vagrant`` and password ``vagrant`` 116- A domain admin account ``vagrant-domain@domain.local`` with the password ``VagrantPass1`` 117 118The domain name and accounts can be modified by changing the variables 119``domain_*`` in the ``inventory.yml`` file if it is required. The inventory 120file can also be modified to provision more or less servers by changing the 121hosts that are defined under the ``domain_children`` key. The host variable 122``ansible_host`` is the private IP that will be assigned to the VirtualBox host 123only network adapter while ``vagrant_box`` is the box that will be used to 124create the VM. 125 126Provisioning the environment 127============================ 128 129To provision the environment as is, run the following: 130 131.. code-block:: shell 132 133 git clone https://github.com/jborean93/ansible-windows.git 134 cd vagrant 135 vagrant up 136 137.. note:: Vagrant provisions each host sequentially so this can take some time 138 to complete. If any errors occur during the Ansible phase of setting up the 139 domain, run ``vagrant provision`` to rerun just that step. 140 141Unlike setting up a single Windows instance with Vagrant, these hosts can also 142be accessed using the IP address directly as well as through the forwarded 143ports. It is easier to access it over the host only network adapter as the 144normal protocol ports are used, e.g. RDP is still over ``3389``. In cases where 145the host cannot be resolved using the host only network IP, the following 146protocols can be access over ``127.0.0.1`` using these forwarded ports: 147 148- ``RDP``: 295xx 149- ``SSH``: 296xx 150- ``WinRM HTTP``: 297xx 151- ``WinRM HTTPS``: 298xx 152- ``SMB``: 299xx 153 154Replace ``xx`` with the entry number in the inventory file where the domain 155controller started with ``00`` and is incremented from there. For example, in 156the default ``inventory.yml`` file, WinRM over HTTPS for ``SERVER2012R2`` is 157forwarded over port ``29804`` as it's the fourth entry in ``domain_children``. 158 159.. note:: While an SSH server is available on all Windows hosts but Server 160 2008 (non R2), it is not a support connection for Ansible managing Windows 161 hosts and should not be used with Ansible. 162 163Windows new module development 164============================== 165 166When creating a new module there are a few things to keep in mind: 167 168- Module code is in Powershell (.ps1) files while the documentation is contained in Python (.py) files of the same name 169- Avoid using ``Write-Host/Debug/Verbose/Error`` in the module and add what needs to be returned to the ``$module.Result`` variable 170- To fail a module, call ``$module.FailJson("failure message here")``, an Exception or ErrorRecord can be set to the second argument for a more descriptive error message 171- You can pass in the exception or ErrorRecord as a second argument to ``FailJson("failure", $_)`` to get a more detailed output 172- Most new modules require check mode and integration tests before they are merged into the main Ansible codebase 173- Avoid using try/catch statements over a large code block, rather use them for individual calls so the error message can be more descriptive 174- Try and catch specific exceptions when using try/catch statements 175- Avoid using PSCustomObjects unless necessary 176- Look for common functions in ``./lib/ansible/module_utils/powershell/`` and use the code there instead of duplicating work. These can be imported by adding the line ``#Requires -Module *`` where * is the filename to import, and will be automatically included with the module code sent to the Windows target when run via Ansible 177- As well as PowerShell module utils, C# module utils are stored in ``./lib/ansible/module_utils/csharp/`` and are automatically imported in a module execution if the line ``#AnsibleRequires -CSharpUtil *`` is present 178- C# and PowerShell module utils achieve the same goal but C# allows a developer to implement low level tasks, such as calling the Win32 API, and can be faster in some cases 179- Ensure the code runs under Powershell v3 and higher on Windows Server 2008 and higher; if higher minimum Powershell or OS versions are required, ensure the documentation reflects this clearly 180- Ansible runs modules under strictmode version 2.0. Be sure to test with that enabled by putting ``Set-StrictMode -Version 2.0`` at the top of your dev script 181- Favour native Powershell cmdlets over executable calls if possible 182- Use the full cmdlet name instead of aliases, e.g. ``Remove-Item`` over ``rm`` 183- Use named parameters with cmdlets, e.g. ``Remove-Item -Path C:\temp`` over ``Remove-Item C:\temp`` 184 185A very basic powershell module `win_environment <https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/windows/win_environment.ps1>`_ is included below. It demonstrates how to implement check-mode and diff-support, and also shows a warning to the user when a specific condition is met. 186 187.. .. include:: ../../../../lib/ansible/modules/windows/win_environment.ps1 188.. :code: powershell 189 190.. literalinclude:: ../../../../lib/ansible/modules/windows/win_environment.ps1 191 :language: powershell 192 193A slightly more advanced module is `win_uri <https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/windows/win_uri.ps1>`_ 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. 194 195As part of the new ``AnsibleModule`` wrapper, the input parameters are defined and validated based on an argument 196spec. The following options can be set at the root level of the argument spec: 197 198- ``mutually_exclusive``: A list of lists, where the inner list contains module options that cannot be set together 199- ``no_log``: Stops the module from emitting any logs to the Windows Event log 200- ``options``: A dictionary where the key is the module option and the value is the spec for that option 201- ``required_by``: A dictionary where the option(s) specified by the value must be set if the option specified by the key is also set 202- ``required_if``: A list of lists where the inner list contains 3 or 4 elements; 203 * The first element is the module option to check the value against 204 * The second element is the value of the option specified by the first element, if matched then the required if check is run 205 * The third element is a list of required module options when the above is matched 206 * An optional fourth element is a boolean that states whether all module options in the third elements are required (default: ``$false``) or only one (``$true``) 207- ``required_one_of``: A list of lists, where the inner list contains module options where at least one must be set 208- ``required_together``: A list of lists, where the inner list contains module options that must be set together 209- ``supports_check_mode``: Whether the module supports check mode, by default this is ``$false`` 210 211The actual input options for a module are set within the ``options`` value as a dictionary. The keys of this dictionary 212are the module option names while the values are the spec of that module option. Each spec can have the following 213options set: 214 215- ``aliases``: A list of aliases for the module option 216- ``choices``: A list of valid values for the module option, if ``type=list`` then each list value is validated against the choices and not the list itself 217- ``default``: The default value for the module option if not set 218- ``elements``: When ``type=list``, this sets the type of each list value, the values are the same as ``type`` 219- ``no_log``: Will sanitise the input value before being returned in the ``module_invocation`` return value 220- ``removed_in_version``: States when a deprecated module option is to be removed, a warning is displayed to the end user if set 221- ``required``: Will fail when the module option is not set 222- ``type``: The type of the module option, if not set then it defaults to ``str``. The valid types are; 223 * ``bool``: A boolean value 224 * ``dict``: A dictionary value, if the input is a JSON or key=value string then it is converted to dictionary 225 * ``float``: A float or `Single <https://docs.microsoft.com/en-us/dotnet/api/system.single?view=netframework-4.7.2>`_ value 226 * ``int``: An Int32 value 227 * ``json``: A string where the value is converted to a JSON string if the input is a dictionary 228 * ``list``: A list of values, ``elements=<type>`` can convert the individual list value types if set. If ``elements=dict`` then ``options`` is defined, the values will be validated against the argument spec. When the input is a string then the string is split by ``,`` and any whitespace is trimmed 229 * ``path``: A string where values likes ``%TEMP%`` are expanded based on environment values. If the input value starts with ``\\?\`` then no expansion is run 230 * ``raw``: No conversions occur on the value passed in by Ansible 231 * ``sid``: Will convert Windows security identifier values or Windows account names to a `SecurityIdentifier <https://docs.microsoft.com/en-us/dotnet/api/system.security.principal.securityidentifier?view=netframework-4.7.2>`_ value 232 * ``str``: The value is converted to a string 233 234When ``type=dict``, or ``type=list`` and ``elements=dict``, the following keys can also be set for that module option: 235 236- ``apply_defaults``: The value is based on the ``options`` spec defaults for that key if ``True`` and null if ``False``. Only valid when the module option is not defined by the user and ``type=dict``. 237- ``mutually_exclusive``: Same as the root level ``mutually_exclusive`` but validated against the values in the sub dict 238- ``options``: Same as the root level ``options`` but contains the valid options for the sub option 239- ``required_if``: Same as the root level ``required_if`` but validated against the values in the sub dict 240- ``required_by``: Same as the root level ``required_by`` but validated against the values in the sub dict 241- ``required_together``: Same as the root level ``required_together`` but validated against the values in the sub dict 242- ``required_one_of``: Same as the root level ``required_one_of`` but validated against the values in the sub dict 243 244A module type can also be a delegate function that converts the value to whatever is required by the module option. For 245example the following snippet shows how to create a custom type that creates a ``UInt64`` value: 246 247.. code-block:: powershell 248 249 $spec = @{ 250 uint64_type = @{ type = [Func[[Object], [UInt64]]]{ [System.UInt64]::Parse($args[0]) } } 251 } 252 $uint64_type = $module.Params.uint64_type 253 254When in doubt, look at some of the other core modules and see how things have been 255implemented there. 256 257Sometimes there are multiple ways that Windows offers to complete a task; this 258is the order to favour when writing modules: 259 260- Native Powershell cmdlets like ``Remove-Item -Path C:\temp -Recurse`` 261- .NET classes like ``[System.IO.Path]::GetRandomFileName()`` 262- WMI objects through the ``New-CimInstance`` cmdlet 263- COM objects through ``New-Object -ComObject`` cmdlet 264- Calls to native executables like ``Secedit.exe`` 265 266PowerShell modules support a small subset of the ``#Requires`` options built 267into PowerShell as well as some Ansible-specific requirements specified by 268``#AnsibleRequires``. These statements can be placed at any point in the script, 269but are most commonly near the top. They are used to make it easier to state the 270requirements of the module without writing any of the checks. Each ``requires`` 271statement must be on its own line, but there can be multiple requires statements 272in one script. 273 274These are the checks that can be used within Ansible modules: 275 276- ``#Requires -Module Ansible.ModuleUtils.<module_util>``: Added in Ansible 2.4, specifies a module_util to load in for the module execution. 277- ``#Requires -Version x.y``: Added in Ansible 2.5, specifies the version of PowerShell that is required by the module. The module will fail if this requirement is not met. 278- ``#AnsibleRequires -OSVersion x.y``: Added in Ansible 2.5, specifies the OS build version that is required by the module and will fail if this requirement is not met. The actual OS version is derived from ``[Environment]::OSVersion.Version``. 279- ``#AnsibleRequires -Become``: Added in Ansible 2.5, forces the exec runner to run the module with ``become``, which is primarily used to bypass WinRM restrictions. If ``ansible_become_user`` is not specified then the ``SYSTEM`` account is used instead. 280- ``#AnsibleRequires -CSharpUtil Ansible.<module_util>``: Added in Ansible 2.8, specifies a C# module_util to load in for the module execution. 281 282C# module utils can reference other C# utils by adding the line 283``using Ansible.<module_util>;`` to the top of the script with all the other 284using statements. 285 286 287Windows module utilities 288======================== 289 290Like Python modules, PowerShell modules also provide a number of module 291utilities that provide helper functions within PowerShell. These module_utils 292can be imported by adding the following line to a PowerShell module: 293 294.. code-block:: powershell 295 296 #Requires -Module Ansible.ModuleUtils.Legacy 297 298This will import the module_util at ``./lib/ansible/module_utils/powershell/Ansible.ModuleUtils.Legacy.psm1`` 299and enable calling all of its functions. As of Ansible 2.8, Windows module 300utils can also be written in C# and stored at ``lib/ansible/module_utils/csharp``. 301These module_utils can be imported by adding the following line to a PowerShell 302module: 303 304.. code-block:: powershell 305 306 #AnsibleRequires -CSharpUtil Ansible.Basic 307 308This will import the module_util at ``./lib/ansible/module_utils/csharp/Ansible.Basic.cs`` 309and automatically load the types in the executing process. C# module utils can 310reference each other and be loaded together by adding the following line to the 311using statements at the top of the util: 312 313.. code-block:: csharp 314 315 using Ansible.Become; 316 317There are special comments that can be set in a C# file for controlling the 318compilation parameters. The following comments can be added to the script; 319 320- ``//AssemblyReference -Name <assembly dll> [-CLR [Core|Framework]]``: The assembly DLL to reference during compilation, the optional ``-CLR`` flag can also be used to state whether to reference when running under .NET Core, Framework, or both (if omitted) 321- ``//NoWarn -Name <error id> [-CLR [Core|Framework]]``: A compiler warning ID to ignore when compiling the code, the optional ``-CLR`` works the same as above. A list of warnings can be found at `Compiler errors <https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/index>`_ 322 323As well as this, the following pre-processor symbols are defined; 324 325- ``CORECLR``: This symbol is present when PowerShell is running through .NET Core 326- ``WINDOWS``: This symbol is present when PowerShell is running on Windows 327- ``UNIX``: This symbol is present when PowerShell is running on Unix 328 329A combination of these flags help to make a module util interoperable on both 330.NET Framework and .NET Core, here is an example of them in action: 331 332.. code-block:: csharp 333 334 #if CORECLR 335 using Newtonsoft.Json; 336 #else 337 using System.Web.Script.Serialization; 338 #endif 339 340 //AssemblyReference -Name Newtonsoft.Json.dll -CLR Core 341 //AssemblyReference -Name System.Web.Extensions.dll -CLR Framework 342 343 // Ignore error CS1702 for all .NET types 344 //NoWarn -Name CS1702 345 346 // Ignore error CS1956 only for .NET Framework 347 //NoWarn -Name CS1956 -CLR Framework 348 349 350The following is a list of module_utils that are packaged with Ansible and a general description of what 351they do: 352 353- ArgvParser: Utiliy used to convert a list of arguments to an escaped string compliant with the Windows argument parsing rules. 354- CamelConversion: Utility used to convert camelCase strings/lists/dicts to snake_case. 355- CommandUtil: Utility used to execute a Windows process and return the stdout/stderr and rc as separate objects. 356- FileUtil: Utility that expands on the ``Get-ChildItem`` and ``Test-Path`` to work with special files like ``C:\pagefile.sys``. 357- Legacy: General definitions and helper utilities for Ansible module. 358- LinkUtil: Utility to create, remove, and get information about symbolic links, junction points and hard inks. 359- SID: Utilities used to convert a user or group to a Windows SID and vice versa. 360 361For more details on any specific module utility and their requirements, please see the `Ansible 362module utilities source code <https://github.com/ansible/ansible/tree/devel/lib/ansible/module_utils/powershell>`_. 363 364PowerShell module utilities can be stored outside of the standard Ansible 365distribution for use with custom modules. Custom module_utils are placed in a 366folder called ``module_utils`` located in the root folder of the playbook or role 367directory. 368 369C# module utilities can also be stored outside of the standard Ansible distribution for use with custom modules. Like 370PowerShell utils, these are stored in a folder called ``module_utils`` and the filename must end in the extension 371``.cs``, start with ``Ansible.`` and be named after the namespace defined in the util. 372 373The below example is a role structure that contains two PowerShell custom module_utils called 374``Ansible.ModuleUtils.ModuleUtil1``, ``Ansible.ModuleUtils.ModuleUtil2``, and a C# util containing the namespace 375``Ansible.CustomUtil``:: 376 377 meta/ 378 main.yml 379 defaults/ 380 main.yml 381 module_utils/ 382 Ansible.ModuleUtils.ModuleUtil1.psm1 383 Ansible.ModuleUtils.ModuleUtil2.psm1 384 Ansible.CustomUtil.cs 385 tasks/ 386 main.yml 387 388Each PowerShell module_util must contain at least one function that has been exported with ``Export-ModuleMember`` 389at the end of the file. For example 390 391.. code-block:: powershell 392 393 Export-ModuleMember -Function Invoke-CustomUtil, Get-CustomInfo 394 395 396Windows playbook module testing 397=============================== 398 399You can test a module with an Ansible playbook. For example: 400 401- Create a playbook in any directory ``touch testmodule.yml``. 402- Create an inventory file in the same directory ``touch hosts``. 403- Populate the inventory file with the variables required to connect to a Windows host(s). 404- Add the following to the new playbook file:: 405 406 --- 407 - name: test out windows module 408 hosts: windows 409 tasks: 410 - name: test out module 411 win_module: 412 name: test name 413 414- Run the playbook ``ansible-playbook -i hosts testmodule.yml`` 415 416This can be useful for seeing how Ansible runs with 417the new module end to end. Other possible ways to test the module are 418shown below. 419 420 421Windows debugging 422================= 423 424Debugging a module currently can only be done on a Windows host. This can be 425useful when developing a new module or implementing bug fixes. These 426are some steps that need to be followed to set this up: 427 428- Copy the module script to the Windows server 429- Copy the folders ``./lib/ansible/module_utils/powershell`` and ``./lib/ansible/module_utils/csharp`` to the same directory as the script above 430- Add an extra ``#`` to the start of any ``#Requires -Module`` lines in the module code, this is only required for any lines starting with ``#Requires -Module`` 431- Add the following to the start of the module script that was copied to the server: 432 433.. code-block:: powershell 434 435 # Set $ErrorActionPreference to what's set during Ansible execution 436 $ErrorActionPreference = "Stop" 437 438 # Set the first argument as the path to a JSON file that contains the module args 439 $args = @("$($pwd.Path)\args.json") 440 441 # Or instead of an args file, set $complex_args to the pre-processed module args 442 $complex_args = @{ 443 _ansible_check_mode = $false 444 _ansible_diff = $false 445 path = "C:\temp" 446 state = "present" 447 } 448 449 # Import any C# utils referenced with '#AnsibleRequires -CSharpUtil' or 'using Ansible.; 450 # The $_csharp_utils entries should be the context of the C# util files and not the path 451 Import-Module -Name "$($pwd.Path)\powershell\Ansible.ModuleUtils.AddType.psm1" 452 $_csharp_utils = @( 453 [System.IO.File]::ReadAllText("$($pwd.Path)\csharp\Ansible.Basic.cs") 454 ) 455 Add-CSharpType -References $_csharp_utils -IncludeDebugInfo 456 457 # Import any PowerShell modules referenced with '#Requires -Module` 458 Import-Module -Name "$($pwd.Path)\powershell\Ansible.ModuleUtils.Legacy.psm1" 459 460 # End of the setup code and start of the module code 461 #!powershell 462 463You can add more args to ``$complex_args`` as required by the module or define the module options through a JSON file 464with the structure:: 465 466 { 467 "ANSIBLE_MODULE_ARGS": { 468 "_ansible_check_mode": false, 469 "_ansible_diff": false, 470 "path": "C:\\temp", 471 "state": "present" 472 } 473 } 474 475There are multiple IDEs that can be used to debug a Powershell script, two of 476the most popular ones are 477 478- `Powershell ISE`_ 479- `Visual Studio Code`_ 480 481.. _Powershell ISE: https://docs.microsoft.com/en-us/powershell/scripting/core-powershell/ise/how-to-debug-scripts-in-windows-powershell-ise 482.. _Visual Studio Code: https://blogs.technet.microsoft.com/heyscriptingguy/2017/02/06/debugging-powershell-script-in-visual-studio-code-part-1/ 483 484To be able to view the arguments as passed by Ansible to the module follow 485these steps. 486 487- Prefix the Ansible command with :envvar:`ANSIBLE_KEEP_REMOTE_FILES=1<ANSIBLE_KEEP_REMOTE_FILES>` to specify that Ansible should keep the exec files on the server. 488- Log onto the Windows server using the same user account that Ansible used to execute the module. 489- Navigate to ``%TEMP%\..``. It should contain a folder starting with ``ansible-tmp-``. 490- Inside this folder, open the PowerShell script for the module. 491- In this script is a raw JSON script under ``$json_raw`` which contains the module arguments under ``module_args``. These args can be assigned manually to the ``$complex_args`` variable that is defined on your debug script or put in the ``args.json`` file. 492 493 494Windows unit testing 495==================== 496 497Currently there is no mechanism to run unit tests for Powershell modules under Ansible CI. 498 499 500Windows integration testing 501=========================== 502 503Integration tests for Ansible modules are typically written as Ansible roles. These test 504roles are located in ``./test/integration/targets``. You must first set up your testing 505environment, and configure a test inventory for Ansible to connect to. 506 507In this example we will set up a test inventory to connect to two hosts and run the integration 508tests for win_stat: 509 510- Run the command ``source ./hacking/env-setup`` to prepare environment. 511- Create a copy of ``./test/integration/inventory.winrm.template`` and name it ``inventory.winrm``. 512- Fill in entries under ``[windows]`` and set the required variables that are needed to connect to the host. 513- :ref:`Install the required Python modules <windows_winrm>` to support WinRM and a configured authentication method. 514- To execute the integration tests, run ``ansible-test windows-integration win_stat``; you can replace ``win_stat`` with the role you wish to test. 515 516This will execute all the tests currently defined for that role. You can set 517the verbosity level using the ``-v`` argument just as you would with 518ansible-playbook. 519 520When developing tests for a new module, it is recommended to test a scenario once in 521check mode and twice not in check mode. This ensures that check mode 522does not make any changes but reports a change, as well as that the second run is 523idempotent and does not report changes. For example: 524 525.. code-block:: yaml 526 527 - name: remove a file (check mode) 528 win_file: 529 path: C:\temp 530 state: absent 531 register: remove_file_check 532 check_mode: yes 533 534 - name: get result of remove a file (check mode) 535 win_command: powershell.exe "if (Test-Path -Path 'C:\temp') { 'true' } else { 'false' }" 536 register: remove_file_actual_check 537 538 - name: assert remove a file (check mode) 539 assert: 540 that: 541 - remove_file_check is changed 542 - remove_file_actual_check.stdout == 'true\r\n' 543 544 - name: remove a file 545 win_file: 546 path: C:\temp 547 state: absent 548 register: remove_file 549 550 - name: get result of remove a file 551 win_command: powershell.exe "if (Test-Path -Path 'C:\temp') { 'true' } else { 'false' }" 552 register: remove_file_actual 553 554 - name: assert remove a file 555 assert: 556 that: 557 - remove_file is changed 558 - remove_file_actual.stdout == 'false\r\n' 559 560 - name: remove a file (idempotent) 561 win_file: 562 path: C:\temp 563 state: absent 564 register: remove_file_again 565 566 - name: assert remove a file (idempotent) 567 assert: 568 that: 569 - not remove_file_again is changed 570 571 572Windows communication and development support 573============================================= 574 575Join the ``#ansible-devel`` or ``#ansible-windows`` irc channels on `irc.libera.chat <https://libera.chat/>`_ for discussions about Ansible development for Windows. 576 577For questions and discussions pertaining to using the Ansible product, 578use the ``#ansible`` channel. 579