# HG changeset patch
# Node ID d69475aa6c1d
# Parent  30a484da0840
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,12 +3,21 @@
 This repository contains the ansible configuration for running our non
 kubernetes infrastructure.
 
-# Setup
+## Setup
+
+Before you can run our ansible playbooks, you need to meet the following
+prerequisites:
 
-Before you can run this ansible script, you need to creat a password named
-`vault_password` in the repository root with the proper vault password.
+* create a file named `vault_password` in the repository root with the proper
+  decryption password of the ansible vaulted file with our secrets.
+* download all the collections our roles depend on with the following command
 
-# Running
+  ```
+  ansible-galaxy collection install \
+    --requirements-file collections/requirements.yml
+  ```
+
+## Running
 
 There are a number of playbooks that you can run.
 
diff --git a/collections/requirements.yml b/collections/requirements.yml
new file mode 100644
--- /dev/null
+++ b/collections/requirements.yml
@@ -0,0 +1,4 @@
+---
+collections:
+  - name: community.crypto
+  - name: community.windows
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+ansible
+requests-credssp
+pywinrm
diff --git a/roles/git/meta/main.yml b/roles/git/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/git/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+  - role: win_common
+    when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/git/tasks/main.yml b/roles/git/tasks/main.yml
--- a/roles/git/tasks/main.yml
+++ b/roles/git/tasks/main.yml
@@ -7,4 +7,5 @@
   when: ansible_system == "FreeBSD"
 - include_tasks: openbsd.yml
   when: ansible_system == "OpenBSD"
-
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/git/tasks/windows.yml b/roles/git/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/git/tasks/windows.yml
@@ -0,0 +1,6 @@
+---
+- include_role:
+    name: win_common
+    tasks_from: msys-install-package
+  vars:
+    package_name: git
diff --git a/roles/mercurial/meta/main.yml b/roles/mercurial/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/mercurial/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+  - role: python
+    when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/mercurial/tasks/main.yml b/roles/mercurial/tasks/main.yml
--- a/roles/mercurial/tasks/main.yml
+++ b/roles/mercurial/tasks/main.yml
@@ -7,4 +7,6 @@
   when: ansible_facts['os_family'] == "FreeBSD"
 - include_tasks: openbsd.yml
   when: ansible_facts['os_family'] == "OpenBSD"
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
 
diff --git a/roles/mercurial/tasks/windows.yml b/roles/mercurial/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/mercurial/tasks/windows.yml
@@ -0,0 +1,29 @@
+---
+- include_vars: windows.yml
+
+- name: "install mercurial {{ mercurial_version }}"
+  ansible.windows.win_package:
+    path: "https://www.mercurial-scm.org/release/windows/mercurial-{{ mercurial_version }}-x64.msi"
+    product_id: "{{ mercurial_product_id }}"
+    arguments: /quiet
+    state: present
+
+# note: we assume python_install_path is defined in the `python` role that this
+# role depends on
+- name: install latest version of hg-evolve from pypi
+  become: yes
+  ansible.windows.win_command: "pip3.exe install --upgrade hg-evolve"
+  args:
+    chdir: '{{ python_install_path }}\Scripts'
+  register: install_output
+  changed_when: "'Successfully installed' in install_output.stdout"
+
+# evolve will only be enabled for the user ansible is running as, which we
+# assume is the one that will be used to login interactively when needed
+- name: "enable evolve extension in mercurial config for user {{ ansible_user }}"
+  ansible.windows.win_copy:
+    dest: '{{ ansible_env.USERPROFILE }}\.hgrc'
+    content: |
+      [extensions]
+      evolve = {{ python_install_path }}\Lib\site-packages\hgext3rd\evolve
+    force: yes
diff --git a/roles/mercurial/vars/windows.yml b/roles/mercurial/vars/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/mercurial/vars/windows.yml
@@ -0,0 +1,3 @@
+---
+mercurial_version: 5.9.3
+mercurial_product_id: '{6CBAA69D-20AD-4B50-A715-7477201A02EF}'
diff --git a/roles/meson/defaults/main.yml b/roles/meson/defaults/main.yml
--- a/roles/meson/defaults/main.yml
+++ b/roles/meson/defaults/main.yml
@@ -1,3 +1,2 @@
 ---
 meson_install_from_pip: false
-
diff --git a/roles/meson/meta/main.yml b/roles/meson/meta/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/meson/meta/main.yml
@@ -0,0 +1,4 @@
+---
+dependencies:
+  - role: python
+    when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/meson/tasks/main.yml b/roles/meson/tasks/main.yml
--- a/roles/meson/tasks/main.yml
+++ b/roles/meson/tasks/main.yml
@@ -7,4 +7,6 @@
   when: ansible_facts['os_family'] == "FreeBSD"
 - include_tasks: openbsd.yml
   when: ansible_facts['os_family'] == "OpenBSD"
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
 
diff --git a/roles/meson/tasks/windows.yml b/roles/meson/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/meson/tasks/windows.yml
@@ -0,0 +1,10 @@
+---
+# note: we assume python_install_path is defined in the `python` role that this
+# role depends on
+- name: install latest meson from pypi
+  become: yes
+  ansible.windows.win_command: "pip3.exe install --upgrade meson"
+  args:
+    chdir: '{{ python_install_path }}\Scripts'
+  register: install_output
+  changed_when: "'Successfully installed' in install_output.stdout"
diff --git a/roles/openjdk/tasks/main.yml b/roles/openjdk/tasks/main.yml
--- a/roles/openjdk/tasks/main.yml
+++ b/roles/openjdk/tasks/main.yml
@@ -7,3 +7,5 @@
   when: ansible_system == "FreeBSD"
 - include_tasks: openbsd.yml
   when: ansible_system == "OpenBSD"
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/openjdk/tasks/windows.yml b/roles/openjdk/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/openjdk/tasks/windows.yml
@@ -0,0 +1,34 @@
+---
+# note: for Windows we'll be using the Microsoft build of OpenJDK, mainly due to
+# it being distributed with an installer that's easy to use with ansible
+- include_vars: windows.yml
+
+- name: install microsoft openjdk
+  ansible.windows.win_package:
+    path: "https://aka.ms/download-jdk/microsoft-jdk-{{ microsoft_openjdk_version }}-windows-x64.msi"
+    product_id: "{{ microsoft_openjdk_product_id }}"
+    arguments: /passive
+    state: present
+
+- name: find path of java executable
+  ansible.windows.win_find:
+    paths: 'C:\Program Files\Microsoft\jdk-{{ microsoft_openjdk_version.split(".")[0:-1] | join(".") }}-hotspot'
+    patterns: java.exe
+    recurse: yes
+  register: java_executable
+
+# the following is so that we don't get a firewall prompt when the java process
+# is started for the first time
+- name: deny inbound tcp/udp connections for opendjk
+  community.windows.win_firewall_rule:
+    name: "OpenJDK {{ item }}"
+    program: "{{ java_executable.files[0].path }}"
+    localport: any
+    action: block
+    direction: in
+    protocol: "{{ item }}"
+    state: present
+    enabled: yes
+  loop:
+    - tcp
+    - udp
diff --git a/roles/openjdk/vars/windows.yml b/roles/openjdk/vars/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/openjdk/vars/windows.yml
@@ -0,0 +1,3 @@
+---
+microsoft_openjdk_version: 11.0.12.7.1
+microsoft_openjdk_product_id: '{0D2DCFF8-7746-445B-B643-9E85ABE01173}'
diff --git a/roles/pidgin3_dependencies/meta/main.yml b/roles/pidgin3_dependencies/meta/main.yml
--- a/roles/pidgin3_dependencies/meta/main.yml
+++ b/roles/pidgin3_dependencies/meta/main.yml
@@ -1,3 +1,8 @@
+---
 dependencies:
   - libhandy
   - libnice
+  - role: python
+    when: ansible_facts['os_family'] == "Windows"
+  - role: win_common
+    when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/pidgin3_dependencies/tasks/main.yml b/roles/pidgin3_dependencies/tasks/main.yml
--- a/roles/pidgin3_dependencies/tasks/main.yml
+++ b/roles/pidgin3_dependencies/tasks/main.yml
@@ -7,4 +7,6 @@
   when: ansible_system == "FreeBSD"
 - include_tasks: openbsd.yml
   when: ansible_system == "OpenBSD"
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
 
diff --git a/roles/pidgin3_dependencies/tasks/windows.yml b/roles/pidgin3_dependencies/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/pidgin3_dependencies/tasks/windows.yml
@@ -0,0 +1,39 @@
+---
+# note: we assume python_install_path is defined in the `python` role that this
+# role depends on
+- name: install latest ninja from pypi
+  become: yes
+  ansible.windows.win_command: "pip3.exe install --upgrade ninja"
+  args:
+    chdir: '{{ python_install_path }}\Scripts'
+  register: install_output
+  changed_when: "'Successfully installed' in install_output.stdout"
+
+- name: "install pidgin3 msys dependencies"
+  include_role:
+    name: win_common
+    tasks_from: msys-install-package
+  loop:
+    - help2man
+    - mingw-w64-x86_64-cmake
+    - mingw-w64-x86_64-cmark
+    - mingw-w64-x86_64-farstream
+    - mingw-w64-x86_64-gcc
+    - mingw-w64-x86_64-gcc-libgfortran
+    - mingw-w64-x86_64-gettext
+    - mingw-w64-x86_64-gi-docgen
+    - mingw-w64-x86_64-gnupg
+    - mingw-w64-x86_64-gobject-introspection
+    - mingw-w64-x86_64-gspell
+    - mingw-w64-x86_64-gst-plugins-bad-libs
+    - mingw-w64-x86_64-gst-plugins-base
+    - mingw-w64-x86_64-gstreamer
+    - mingw-w64-x86_64-gtk3
+    - mingw-w64-x86_64-gumbo-parser
+    - mingw-w64-x86_64-libglade
+    - mingw-w64-x86_64-libhandy
+    - mingw-w64-x86_64-libsoup
+    - mingw-w64-x86_64-vala
+  loop_control:
+    label: "{{ package_name }}"
+    loop_var: package_name
diff --git a/roles/python/tasks/main.yml b/roles/python/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/python/tasks/main.yml
@@ -0,0 +1,3 @@
+---
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/python/tasks/windows.yml b/roles/python/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/python/tasks/windows.yml
@@ -0,0 +1,27 @@
+---
+- include_vars: windows.yml
+
+- name: "install python {{ python_version }}"
+  ansible.windows.win_package:
+    path: "https://www.python.org/ftp/python/{{ python_version }}/python-{{ python_version }}-amd64.exe"
+    product_id: "{{ python_product_id }}"
+    arguments: /quiet InstallAllUsers=1 PrependPath=1
+    state: present
+
+# we need to update pip using python -m pip since otherwise we'll hit
+# https://github.com/pypa/pip/issues/6893
+- name: update pip to latest version in pypi
+  become: yes
+  ansible.windows.win_command: "python.exe -m pip install --upgrade pip"
+  args:
+    chdir: '{{ python_install_path }}'
+  register: install_output
+  changed_when: "'Successfully installed' in install_output.stdout"
+
+- name: update setuptools and wheel to latest version in pypi
+  become: yes
+  ansible.windows.win_command: "pip3.exe install --upgrade setuptools wheel"
+  args:
+    chdir: '{{ python_install_path }}\Scripts'
+  register: install_output
+  changed_when: "'Successfully installed' in install_output.stdout"
diff --git a/roles/python/vars/windows.yml b/roles/python/vars/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/python/vars/windows.yml
@@ -0,0 +1,4 @@
+---
+python_version: 3.10.0
+python_product_id: '{B137EFE9-BD8A-4138-AC7F-360461C4EEAF}'
+python_install_path: 'C:\Program Files\Python{{ python_version.split(".")[0:2] | join }}'
diff --git a/roles/teamcity/defaults/main.yml b/roles/teamcity/defaults/main.yml
--- a/roles/teamcity/defaults/main.yml
+++ b/roles/teamcity/defaults/main.yml
@@ -1,9 +1,8 @@
 ---
-teamcity_agent_name: "{{ansible_host}}"
+teamcity_agent_name: "{{ ansible_host }}"
 teamcity_user: teamcity
-teamcity_group: teamcity
-teamcity_groups: []
-teamcity_home: /var/lib/teamcity/
 teamcity_properties: []
 teamcity_authorization_token:
 teamcity_jdk_version: openjdk11
+
+teamcity_build_agent_zip_url: "{{ teamcity_server }}update/buildAgent.zip"
diff --git a/roles/teamcity/meta/main.yaml b/roles/teamcity/meta/main.yaml
--- a/roles/teamcity/meta/main.yaml
+++ b/roles/teamcity/meta/main.yaml
@@ -1,6 +1,8 @@
 ---
 dependencies:
-  - curl
-  - git
-  - mercurial
-  - openjdk
+  - role: curl
+  - role: git
+  - role: mercurial
+  - role: openjdk
+  - role: win_common
+    when: ansible_facts['os_family'] == "Windows"
diff --git a/roles/teamcity/tasks/agent.yml b/roles/teamcity/tasks/agent.yml
--- a/roles/teamcity/tasks/agent.yml
+++ b/roles/teamcity/tasks/agent.yml
@@ -39,17 +39,9 @@
   when: _build_agent_token.changed
 
 - name: configure agent
-  copy:
+  template:
+    src: buildAgent.properties.j2
     dest: "{{ teamcity_agent_home }}conf/buildAgent.properties"
-    content: |
-      serverUrl={{ teamcity_server }}
-      name={{ teamcity_agent_name }}
-      workDir=../work
-      tempDir=../temp
-      systemDir=../system
-      authorizationToken={{ teamcity_authorization_token }}
-      {% for item in teamcity_properties %}{{ item }}
-      {% endfor %}
     owner: "{{ teamcity_user }}"
     group: "{{ teamcity_group }}"
     mode: 0700
diff --git a/roles/teamcity/tasks/main.yml b/roles/teamcity/tasks/main.yml
--- a/roles/teamcity/tasks/main.yml
+++ b/roles/teamcity/tasks/main.yml
@@ -1,26 +1,31 @@
 # vi:et:ts=2 sw=2 sts=2
 ---
-- name: precondition - teamcity server
-  fail: msg="ERROR - required variable 'teamcity_server' missing"
-  when: teamcity_server is not defined
+- block:
+    - name: precondition - teamcity server
+      fail: msg="ERROR - required variable 'teamcity_server' missing"
+      when: teamcity_server is not defined
+
+    - include_tasks: dependencies.yml
 
-- include_tasks: dependencies.yml
+    - include_vars: nix.yml
 
-- include_tasks: vars.yml
+    - include_tasks: user.yml
+      when: ansible_system != "Darwin"
 
-- include_tasks: user.yml
-  when: ansible_system != "Darwin"
+    - include_tasks: known_hosts.yml
 
-- include_tasks: known_hosts.yml
+    - include_tasks: agent.yml
 
-- include_tasks: agent.yml
+    - include_tasks: systemd.yml
+      when: ansible_system == "Linux"
 
-- include_tasks: systemd.yml
-  when: ansible_system == "Linux"
+    - include_tasks: freebsd.yml
+      when: ansible_system == "FreeBSD"
 
-- include_tasks: freebsd.yml
-  when: ansible_system == "FreeBSD"
+    - include_tasks: openbsd.yml
+      when: ansible_system == "OpenBSD"
+  when: ansible_facts['os_family'] != "Windows"
 
-- include_tasks: openbsd.yml
-  when: ansible_system == "OpenBSD"
+- include_tasks: windows.yml
+  when: ansible_facts['os_family'] == "Windows"
 
diff --git a/roles/teamcity/tasks/vars.yml b/roles/teamcity/tasks/vars.yml
deleted file mode 100644
--- a/roles/teamcity/tasks/vars.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-- name: set internal var for buildAgent.zip url
-  set_fact:
-    teamcity_build_agent_zip_url: "{{ teamcity_server }}update/buildAgent.zip"
-- name: set internal var for teamcity_agent_home
-  set_fact:
-    teamcity_agent_home: "{{ teamcity_home }}tc/"
-
diff --git a/roles/teamcity/tasks/windows.yml b/roles/teamcity/tasks/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/teamcity/tasks/windows.yml
@@ -0,0 +1,149 @@
+---
+# part of these set of tasks were taken from
+# https://github.com/ccdc-opensource/ansible-role-ccdc-teamcity-agent
+- include_vars: windows.yml
+
+- name: "create user {{ teamcity_user }}"
+  ansible.windows.win_user:
+    name: "{{ teamcity_user }}"
+    password: "{{ teamcity_user_password }}"
+    password_never_expires: yes
+    state: present
+    groups_action: replace
+    groups:
+      - Users
+
+- name: "create profile directory structure for user {{ teamcity_user }}"
+  community.windows.win_user_profile:
+    username: "{{ teamcity_user }}"
+    state: present
+
+# for some reason creating the profile for the user with win_user_profile does
+# not create the `Startup` directory (although it does get all the way to
+# creating the `Programs` directory which is its parent) so we need to ensure
+# that it's effectively there
+- name: ensure directory that contains autostart tasks on logon is present
+  ansible.windows.win_file:
+    path: "{{ teamcity_user_startup_directory }}"
+    state: directory
+
+# needed so that scheduled task that starts agent is triggered
+- name: "set autologon for {{ teamcity_user }}"
+  community.windows.win_auto_logon:
+    username: "{{ teamcity_user }}"
+    password: "{{ teamcity_user_password }}"
+    state: present
+
+# note: autolock is not instantaneous and it can take up to 20 seconds from
+# logon for the lock to take effect. For a potentially faster (but more
+# complex to implement) approach to autolock we can see
+# https://superuser.com/a/899927
+- name: "set autolock after logon for {{ teamcity_user }}"
+  community.windows.win_shortcut:
+    src: C:\Windows\System32\rundll32.exe
+    arguments: "user32.dll, LockWorkStation"
+    dest: '{{ teamcity_user_startup_directory }}\Lock Computer.lnk'
+    directory: C:\Windows\System32
+
+- name: create teamcity agent install directory
+  ansible.windows.win_file:
+    path: "{{ teamcity_agent_install_dir }}"
+    state: directory
+
+- name: download agent files
+  ansible.windows.win_get_url:
+    url: "{{ teamcity_build_agent_zip_url }}"
+    dest: "{{ ansible_env.TEMP }}"
+  register: downloaded_file
+
+- name: get installed version of teamcity agent
+  ansible.windows.win_find:
+    paths: "{{ teamcity_agent_install_dir }}"
+    recurse: no
+    file_type: file
+    patterns: "BUILD*"
+  register: current_version
+
+- name: check if agent is installed
+  set_fact:
+    is_installed: "{{ true if (current_version.files | length > 0) else false }}"
+
+- block:
+    - name: ensure teamcity agent is stopped
+      ansible.windows.win_shell: bin\agent.bat stop force
+      args:
+        chdir: "{{ teamcity_agent_install_dir }}"
+      register: stop_process
+
+    # the agent may take a bit to stop so we retry a few times before we
+    # actually fail the task
+    - name: delete teamcity agent files
+      ansible.windows.win_file:
+        path: "{{ teamcity_agent_install_dir }}"
+        state: absent
+      retries: 3
+      delay: 10
+      register: delete_files
+      until: not delete_files.failed
+  when: is_installed and teamcity_agent_force_reinstall
+
+- name: unzip agent files
+  community.windows.win_unzip:
+    src: "{{ downloaded_file.dest }}"
+    dest: "{{ teamcity_agent_install_dir }}"
+  when: not is_installed or teamcity_agent_force_reinstall
+
+- name: configure agent
+  ansible.windows.win_template:
+    src: buildAgent.properties.j2
+    dest: '{{ teamcity_agent_install_dir }}\conf\buildAgent.properties'
+    force: yes
+
+- name: "change ownership of all build agent files to {{ teamcity_user }}"
+  ansible.windows.win_owner:
+    path: "{{ teamcity_agent_install_dir }}"
+    user: "{{ teamcity_user }}"
+    recurse: true
+
+- name: "give {{ teamcity_user }} full control on files"
+  ansible.windows.win_acl:
+    user: "{{ teamcity_user }}"
+    path: "{{ teamcity_agent_install_dir }}"
+    rights: FullControl
+    type: allow
+    state: present
+    propagation: InheritOnly
+    inherit: ContainerInherit, ObjectInherit
+
+# we are not using a windows service to start the teamcity agent since its
+# recognized by jetbrains that it may cause issues for processes executed by
+# the agent (like a build pipeline)
+# https://www.jetbrains.com/help/teamcity/setting-up-and-running-additional-build-agents.html#Automatic+Agent+Start+under+Windows
+# note: the win_scheduled_task appears to have some sort of bug when using the
+# logon type trigger that causes the task to always be marked as changed
+- name: add scheduled task to start teamcity agent at logon
+  community.windows.win_scheduled_task:
+    name: TeamCityAgent
+    description: "Start the Teamcity agent in {{ teamcity_agent_install_dir }}"
+    path: \TCAgents
+    enabled: yes
+    logon_type: interactive_token
+    restart_count: 3
+    restart_interval: PT5M  # 5 minutes in ISO8601 duration representation
+    execution_time_limit: PT0S  # 0 seconds, i.e. no time limit
+    start_when_available: yes
+    actions:
+      - path: bin\agent.bat
+        arguments: start
+        working_directory: "{{ teamcity_agent_install_dir }}"
+    triggers:
+      - enabled: yes
+        type: logon
+        user_id: "{{ teamcity_user }}"
+    username: "{{ teamcity_user }}"
+    password: "{{ teamcity_user_password }}"
+    update_password: no
+
+- name: reboot computer to allow teamcity agent to start
+  ansible.windows.win_reboot:
+  when: not is_installed or teamcity_agent_force_reinstall
diff --git a/roles/teamcity/templates/buildAgent.properties.j2 b/roles/teamcity/templates/buildAgent.properties.j2
new file mode 100644
--- /dev/null
+++ b/roles/teamcity/templates/buildAgent.properties.j2
@@ -0,0 +1,52 @@
+## TeamCity build agent configuration file
+
+######################################
+#   Required Agent Properties        #
+######################################
+
+## The address of the TeamCity server. The same as is used to open TeamCity web interface in the browser.
+## Example:  serverUrl=https://buildserver.mydomain.com:8111
+serverUrl={{ teamcity_server }}
+
+## The unique name of the agent used to identify this agent on the TeamCity server
+## Use blank name to let server generate it.
+## By default, this name would be created from the build agent's host name
+name={{ teamcity_agent_name }}
+
+## Container directory to create default checkout directories for the build configurations.
+## TeamCity agent assumes ownership of the directory and will delete unknown directories inside.
+workDir=../work
+
+## Container directory for the temporary directories.
+## TeamCity agent assumes ownership of the directory. The directory may be cleaned between the builds.
+tempDir=../temp
+
+## Container directory for agent state files and caches.
+## TeamCity agent assumes ownership of the directory and can delete content inside.
+systemDir=../system
+
+
+######################################
+#   Optional Agent Properties        #
+######################################
+
+## A token which is used to identify this agent on the TeamCity server for agent authorization purposes.
+## It is automatically generated and saved back on the first agent connection to the server.
+authorizationToken={{ teamcity_authorization_token }}
+
+
+######################################
+#   Default Build Properties         #
+######################################
+## All properties starting with "system.name" will be passed to the build script as "name"
+## All properties starting with "env.name" will be set as environment variable "name" for the build process
+## Note that value should be properly escaped. (use "\\" to represent single backslash ("\"))
+## More on file structure: http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html#load(java.io.InputStream)
+
+# Build Script Properties
+{% for item in teamcity_properties %}
+{{ item }}
+{% endfor %}
+
+# Environment Variables
+#env.exampleEnvVar=example Env Value
\ No newline at end of file
diff --git a/roles/teamcity/vars/nix.yml b/roles/teamcity/vars/nix.yml
new file mode 100644
--- /dev/null
+++ b/roles/teamcity/vars/nix.yml
@@ -0,0 +1,5 @@
+---
+teamcity_group: teamcity
+teamcity_groups: []
+teamcity_home: /var/lib/teamcity/
+teamcity_agent_home: "{{ teamcity_home }}tc/"
diff --git a/roles/teamcity/vars/windows.yml b/roles/teamcity/vars/windows.yml
new file mode 100644
--- /dev/null
+++ b/roles/teamcity/vars/windows.yml
@@ -0,0 +1,5 @@
+---
+teamcity_agent_install_dir: C:\teamcity
+teamcity_user: teamcity
+teamcity_user_startup_directory: 'C:\Users\{{ teamcity_user }}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup'
+teamcity_agent_force_reinstall: no
diff --git a/roles/win_common/defaults/main.yml b/roles/win_common/defaults/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/defaults/main.yml
@@ -0,0 +1,186 @@
+---
+update_windows: yes
+
+msys_install_path: C:\msys64
+msys_force_reinstall: no
+msys_version: 2021-07-25
+msys_product_id: '{3c947ee0-f9a9-41af-b8a6-31e7375d164f}'
+msys_checksum: 7e055b71306e64192e2612f959f54ae99a5cf57186206ac702d113ef00ba35c0
+msys_installer_path: "{{ ansible_env.TEMP }}\\msys2.exe"
+
+spice_guest_tools_version: 0.141
+spice_guest_tools_checksum: b5be0754802bcd7f7fe0ccdb877f8a6224ba13a2af7d84eb087a89b3b0237da2
+spice_guest_tools_product_id: '{8122E54F-475E-4DA7-BEC2-B6B2DE16A988}'
+spice_guest_tools_url: https://www.spice-space.org/download/windows/spice-guest-tools/spice-guest-tools-{{ spice_guest_tools_version }}/spice-guest-tools-{{ spice_guest_tools_version }}.exe
+
+# remove/disable various annoyances from Windows
+remove_annoyances_regkeys:
+  - description: hide news and interests from taskbar
+    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Feeds
+    name: ShellFeedsTaskbarViewMode
+    data: 2
+    type: dword
+  - description: hide cortana button from taskbar
+    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
+    name: ShowCortanaButton
+    data: 0
+    type: dword
+  - description: hide meet now button from taskbar
+    path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer
+    name: HideSCAMeetNow
+    data: 1
+    type: dword
+  - description: prevent suggested applications from returning
+    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\CloudContent
+    name: DisableWindowsConsumerFeatures
+    data: 1
+    type: dword
+  - description: opt out of telemetry
+    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection
+    name: AllowTelemetry
+    data: 0
+    type: dword
+  - description: opt out of sharing advertising ID
+    path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\AdvertisingInfo
+    name: Enabled
+    data: 0
+    type: dword
+  - description: opt out of sending information about what's typed
+    path: HKCU:\SOFTWARE\Microsoft\Input\TIPC
+    name: Enabled
+    data: 0
+    type: dword
+  - description: opt out of sharing what the language list
+    path: HKCU:\Control Panel\International\User Profile
+    name: HttpAcceptLanguageOptOut
+    data: 0
+    type: dword
+  - description: opt out of sharing what app has been launched
+    path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
+    name: Start_TrackProgs
+    data: 0
+    type: dword
+  - description: show hidden files
+    path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
+    name: Hidden
+    data: 1
+    type: dword
+  - description: show file extensions
+    path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced
+    name: HideFileExt
+    data: 0
+    type: dword
+  - description: set default explorer view to this pc
+    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced
+    name: LaunchTo
+    data: 1
+    type: dword
+  - description: disable bing search from start menu
+    path: HKCU:\Software\Microsoft\Windows\CurrentVersion\Search
+    name: BingSearchEnabled
+    data: 0
+    type: dword
+  - description: disable privacy prompt on first logon
+    path: HKLM:\SOFTWARE\Policies\Microsoft\Windows\OOBE
+    name: DisablePrivacyExperience
+    data: 1
+    type: dword
+  - description: disable ads via notifications
+    path: HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\ContentDeliveryManager
+    name: SubscribedContent-338389Enabled
+    data: 0
+    type: dword
+
+uac_regkey:
+  path: HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
+  name: EnableLUA
+  type: dword
+
+unwanted_apps:
+  # list from
+  # https://github.com/W4RH4WK/Debloat-Windows-10/blob/master/scripts/remove-default-apps.ps1
+  # default Windows 10 apps
+  - "Microsoft.549981C3F5F10" #Cortana
+  - "Microsoft.3DBuilder"
+  - "Microsoft.Appconnector"
+  - "Microsoft.BingFinance"
+  - "Microsoft.BingNews"
+  - "Microsoft.BingSports"
+  - "Microsoft.BingTranslator"
+  - "Microsoft.BingWeather"
+  - "Microsoft.GamingServices"
+  - "Microsoft.Microsoft3DViewer"
+  - "Microsoft.MicrosoftOfficeHub"
+  - "Microsoft.MicrosoftPowerBIForWindows"
+  - "Microsoft.MicrosoftSolitaireCollection"
+  - "Microsoft.MinecraftUWP"
+  - "Microsoft.NetworkSpeedTest"
+  - "Microsoft.Office.OneNote"
+  - "Microsoft.Print3D"
+  - "Microsoft.SkypeApp"
+  - "Microsoft.WindowsMaps"
+  - "Microsoft.WindowsPhone"
+  - "Microsoft.Xbox.TCUI"
+  - "Microsoft.XboxApp"
+  - "Microsoft.XboxGameOverlay"
+  - "Microsoft.XboxGamingOverlay"
+  - "Microsoft.XboxSpeechToTextOverlay"
+  - "Microsoft.ZuneMusic"
+  # Threshold 2 apps
+  - "Microsoft.ConnectivityStore"
+  - "Microsoft.Getstarted"
+  - "Microsoft.Messaging"
+  - "Microsoft.Office.Sway"
+  - "Microsoft.OneConnect"
+  - "Microsoft.WindowsFeedbackHub"
+  # Creators Update apps
+  - "Microsoft.Microsoft3DViewer"
+  # Redstone apps
+  - "Microsoft.BingFoodAndDrink"
+  - "Microsoft.BingHealthAndFitness"
+  - "Microsoft.BingTravel"
+  - "Microsoft.WindowsReadingList"
+  # Redstone 5 apps
+  - "Microsoft.MixedReality.Portal"
+  - "Microsoft.ScreenSketch"
+  - "Microsoft.XboxGamingOverlay"
+  # non-Microsoft
+  - "2FE3CB00.PicsArt-PhotoStudio"
+  - "46928bounde.EclipseManager"
+  - "4DF9E0F8.Netflix"
+  - "613EBCEA.PolarrPhotoEditorAcademicEdition"
+  - "6Wunderkinder.Wunderlist"
+  - "7EE7776C.LinkedInforWindows"
+  - "89006A2E.AutodeskSketchBook"
+  - "9E2F88E3.Twitter"
+  - "A278AB0D.DisneyMagicKingdoms"
+  - "A278AB0D.MarchofEmpires"
+  - "ActiproSoftwareLLC.562882FEEB491" # next one is for the Code Writer from Actipro Software LLC
+  - "CAF9E577.Plex"
+  - "ClearChannelRadioDigital.iHeartRadio"
+  - "D52A8D61.FarmVille2CountryEscape"
+  - "D5EA27B7.Duolingo-LearnLanguagesforFree"
+  - "DB6EA5DB.CyberLinkMediaSuiteEssentials"
+  - "DolbyLaboratories.DolbyAccess"
+  - "DolbyLaboratories.DolbyAccess"
+  - "Drawboard.DrawboardPDF"
+  - "Facebook.Facebook"
+  - "Fitbit.FitbitCoach"
+  - "Flipboard.Flipboard"
+  - "GAMELOFTSA.Asphalt8Airborne"
+  - "KeeperSecurityInc.Keeper"
+  - "NORDCURRENT.COOKINGFEVER"
+  - "PandoraMediaInc.29680B314EFC2"
+  - "Playtika.CaesarsSlotsFreeCasino"
+  - "ShazamEntertainmentLtd.Shazam"
+  - "SlingTVLLC.SlingTV"
+  - "SpotifyAB.SpotifyMusic"
+  - "ThumbmunkeysLtd.PhototasticCollage"
+  - "TuneIn.TuneInRadio"
+  - "WinZipComputing.WinZipUniversal"
+  - "XINGAG.XING"
+  - "flaregamesGmbH.RoyalRevolt2"
+  - "king.com.*"
+  - "king.com.BubbleWitch3Saga"
+  - "king.com.CandyCrushSaga"
+  - "king.com.CandyCrushSodaSaga"
diff --git a/roles/win_common/files/redhat.pem b/roles/win_common/files/redhat.pem
new file mode 100644
--- /dev/null
+++ b/roles/win_common/files/redhat.pem
@@ -0,0 +1,31 @@
+# Red Hat Publisher Certificate
+# From https://gitlab.com/mon/autolab/-/wikis/home
+-----BEGIN CERTIFICATE-----
+MIIFBjCCA+6gAwIBAgIQVsbSZ63gf3LutGA7v4TOpTANBgkqhkiG9w0BAQUFADCB
+tDELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
+ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug
+YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykxMDEuMCwGA1UEAxMl
+VmVyaVNpZ24gQ2xhc3MgMyBDb2RlIFNpZ25pbmcgMjAxMCBDQTAeFw0xNjAzMTgw
+MDAwMDBaFw0xODEyMjkyMzU5NTlaMGgxCzAJBgNVBAYTAlVTMRcwFQYDVQQIEw5O
+b3J0aCBDYXJvbGluYTEQMA4GA1UEBxMHUmFsZWlnaDEWMBQGA1UEChQNUmVkIEhh
+dCwgSW5jLjEWMBQGA1UEAxQNUmVkIEhhdCwgSW5jLjCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMA3SYpIcNIEzqqy1PNimjt3bVY1KuIuvDABkx8hKUG6
+rl9WDZ7ibcW6f3cKgr1bKOAeOsMSDu6i/FzB7Csd9u/a/YkASAIIw48q9iD4K6lb
+Kvd+26eJCUVyLHcWlzVkqIEFcvCrvaqaU/YlX/antLWyHGbtOtSdN3FfY5pvvTbW
+xf8PJBWGO3nV9CVL1DMK3wSn3bRNbkTLttdIUYdgiX+q8QjbM/VyGz7nA9UvGO0n
+FWTZRdoiKWI7HA0Wm7TjW3GSxwDgoFb2BZYDDNSlfzQpZmvnKth/fQzNDwumhDw7
+tVicu/Y8E7BLhGwxFEaP0xZtENTpn+1f0TxPxpzL2zMCAwEAAaOCAV0wggFZMAkG
+A1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMCsGA1UdHwQkMCIwIKAeoByGGmh0dHA6
+Ly9zZi5zeW1jYi5jb20vc2YuY3JsMGEGA1UdIARaMFgwVgYGZ4EMAQQBMEwwIwYI
+KwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3BzMCUGCCsGAQUFBwICMBkM
+F2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMBMGA1UdJQQMMAoGCCsGAQUFBwMDMFcG
+CCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0cDovL3NmLnN5bWNkLmNvbTAm
+BggrBgEFBQcwAoYaaHR0cDovL3NmLnN5bWNiLmNvbS9zZi5jcnQwHwYDVR0jBBgw
+FoAUz5mp6nsm9EvJjo/X8AUm7+PSp50wHQYDVR0OBBYEFL/39F5yNDVDib3B3Uk3
+I8XJSrxaMA0GCSqGSIb3DQEBBQUAA4IBAQDWtaW0Dar82t1AdSalPEXshygnvh87
+Rce6PnM2/6j/ijo2DqwdlJBNjIOU4kxTFp8jEq8oM5Td48p03eCNsE23xrZl5qim
+xguIfHqeiBaLeQmxZavTHPNM667lQWPAfTGXHJb3RTT4siowcmGhxwJ3NGP0gNKC
+PHW09x3CdMNCIBfYw07cc6h9+Vm2Ysm9MhqnVhvROj+AahuhvfT9K0MJd3IcEpjX
+Z7aMX78Vt9/vrAIUR8EJ54YGgQsF/G9Adzs6fsfEw5Nrk8R0pueRMHRTMSroTe0V
+Ae2nvuUU6rVI30q8+UjQCxu/ji1/JnitNkUyOPyC46zL+kfHYSnld8U1
+-----END CERTIFICATE-----
diff --git a/roles/win_common/tasks/main.yml b/roles/win_common/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/main.yml
@@ -0,0 +1,25 @@
+---
+- name: install all windows updates and reboot as needed
+  ansible.windows.win_updates:
+    category_names: '*'
+    reboot: yes
+  when: update_windows
+
+- include_tasks: remove-annoyances.yml
+
+# required by win_psmodule
+- name: ensure the required NuGet package provider is installed
+  ansible.windows.win_shell: Find-PackageProvider -Name Nuget -ForceBootstrap -IncludeDependencies -Force
+  changed_when: false
+
+# required by the win_unzip ansible module
+- name: install powershell community extensions module
+  community.windows.win_psmodule:
+    name: Pscx
+    state: latest
+    allow_clobber: yes
+
+- include_tasks: spice-guest-tools.yml
+  when: ansible_system_vendor == "QEMU"
+
+- include_tasks: msys.yml
diff --git a/roles/win_common/tasks/msys-install-package.yml b/roles/win_common/tasks/msys-install-package.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/msys-install-package.yml
@@ -0,0 +1,11 @@
+---
+# this task is meant to be included when you need to install a package
+# thus, it's not actually triggered by this role
+- name: "install {{ package_name }}"
+  ansible.windows.win_command: "sh.exe --login -c {{ pacman_command | quote }}"
+  args:
+    chdir: "{{ msys_install_path }}\\usr\\bin"
+  vars:
+    pacman_command: "pacman --sync {{ '--needed' if not msys_force_reinstall }} --noconfirm {{ package_name }}"
+  register: install_output
+  changed_when: "'nothing to do' not in install_output.stdout"
diff --git a/roles/win_common/tasks/msys.yml b/roles/win_common/tasks/msys.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/msys.yml
@@ -0,0 +1,29 @@
+---
+# just using the `win_package` module along the `product_id` parameter does not
+# seem to work with MSYS2 since it throws the following exception
+# CreateProcessW() failed (The system cannot find the file specified, Win32ErrorCode 2 - 0x00000002)"
+# we'll have to investigate more but for now this setup works
+- name: "check if MSYS2 {{ msys_version }} is installed"
+  ansible.windows.win_shell: Get-Package "MSYS2*" | Select-Object -ExpandProperty Version
+  changed_when: false
+  failed_when: false
+  register: msys_installed
+
+- block:
+    - name: "download MSYS2 {{ msys_version }} installer"
+      ansible.windows.win_get_url:
+        url: "https://github.com/msys2/msys2-installer/releases/download/{{ msys_version }}/msys2-x86_64-{{ msys_version | replace('-', '') }}.exe"
+        dest: "{{ msys_installer_path }}"
+        checksum: "{{ msys_checksum }}"
+        checksum_algorithm: sha256
+
+    # TODO: check if updates are handled by this or if we need to check the version,
+    # uninstall it if it's older than the one desired and then install the desired
+    # version
+    - name: install MSYS2
+      become: yes
+      ansible.windows.win_package:
+        path: "{{ msys_installer_path }}"
+        state: present
+        arguments: "in --confirm-command --accept-messages --root C:/msys64"
+  when: (msys_version | replace('-', '')) not in msys_installed.stdout
diff --git a/roles/win_common/tasks/remove-annoyances.yml b/roles/win_common/tasks/remove-annoyances.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/remove-annoyances.yml
@@ -0,0 +1,39 @@
+---
+# sadly, HKCU will only impact the user we are connecting as with ansible
+# so the those annoyances will only impact that user and not others
+# currently I don't have a nice way of handling multiple users since Windows
+# has no way to impersonate/become another user without its password
+# also sadly, some of the settings need to be reapplied if you haven't
+# interactively logged in as the target user before, like the news feed disabler
+- name: remove annoyances
+  ansible.windows.win_regedit:
+    path: "{{ item.path }}"
+    name: "{{ item.name }}"
+    data: "{{ item.data }}"
+    type: "{{ item.type }}"
+  loop: "{{ remove_annoyances_regkeys }}"
+  loop_control:
+    label: "{{ item.description }}"
+
+# OneDrive seems to be added automatically installed in every new user so the
+# following task only affects the user that it's being executed as
+- name: uninstall onedrive
+  ansible.windows.win_package:
+    product_id: 'OneDriveSetup.exe'
+    state: absent
+
+- name: get installed default applications
+  ansible.windows.win_shell: Get-AppxPackage -AllUsers | Select-Object Name
+  changed_when: false
+  register: installed_appxpackages
+
+- name: get installed default provisioned applications
+  ansible.windows.win_shell: Get-AppxProvisionedPackage -Online | Select-Object DisplayName
+  changed_when: false
+  register: installed_appxprovisionedpackages
+
+- name: uninstall default applications
+  include_tasks: remove-appxpackage.yml
+  loop: "{{ unwanted_apps }}"
+  loop_control:
+    label: "{{ item }}"
diff --git a/roles/win_common/tasks/remove-appxpackage.yml b/roles/win_common/tasks/remove-appxpackage.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/remove-appxpackage.yml
@@ -0,0 +1,32 @@
+---
+- name: "remove appxpackage {{ item }}"
+  ansible.windows.win_shell: "Get-AppxPackage {{ item }} -AllUsers | Remove-AppxPackage"
+  when: item in installed_appxpackages.stdout
+
+- name: "remove appxprovisionedpackage {{ item }}"
+  ansible.windows.win_shell: "Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -eq \"{{ item }}\"} | Remove-AppxProvisionedPackage -Online"
+  when: item in installed_appxprovisionedpackages.stdout
+  register: remove_appxprovisionedpackage_output
+  failed_when:
+    - remove_appxprovisionedpackage_output.rc != 0
+    - '"The system cannot find the path specified" not in remove_appxprovisionedpackage_output.stderr'
+
+# sometimes the first task in this file removes the appx package but the
+# registry key for it does not get deleted. This causes the second task
+# to fail since it finds the key, tries to delete the path associated with
+# it and fails since it doesn't exist
+# the following block takes care of this situation when it happens (haven't been
+# able to figure out when this actually happens, so far it seems to be random)
+- block:
+    - name: "get PackageName for {{ item }}"
+      ansible.windows.win_shell: "Get-AppxProvisionedPackage -Online | Where-Object {$_.DisplayName -eq \"{{ item }}\"} | Select-Object -ExpandProperty PackageName"
+      register: package_name
+
+    - name: "remove remaining registry key for appxpackage {{ item }}"
+      ansible.windows.win_regedit:
+        path: 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Appx\AppxAllUserStore\Applications\{{ package_name.stdout }}'
+        state: absent
+        delete_key: yes
+  when:
+    - remove_appxprovisionedpackage_output.rc is defined and remove_appxprovisionedpackage_output.rc != 0
+    - '"The system cannot find the path specified" in remove_appxprovisionedpackage_output.stderr'
diff --git a/roles/win_common/tasks/spice-guest-tools.yml b/roles/win_common/tasks/spice-guest-tools.yml
new file mode 100644
--- /dev/null
+++ b/roles/win_common/tasks/spice-guest-tools.yml
@@ -0,0 +1,46 @@
+---
+# we need to specify the path to the .pem file since this module is currently
+# unable to automatically look for files under the `files` dir of the role
+# its being executed from, other modules like win_copy do this properly and
+# thus only the file name is needed
+# the task runs only on localhost because the module does not run on Windows
+- name: get thumbprint of Red Hat publisher certificate
+  become: no
+  community.crypto.x509_certificate_info:
+    path: "{{ role_path }}/files/redhat.pem"
+  register: redhat_publisher_certificate_info
+  run_once: yes
+  delegate_to: localhost
+
+- name: check if Red Hat publisher certificate is installed
+  community.windows.win_certificate_info:
+    store_location: LocalMachine
+    store_name: TrustedPublisher
+    thumbprint: "{{ redhat_publisher_certificate_info.fingerprints.sha1.replace(':', '') | upper }}"
+  register: matched_certificate
+
+- block:
+    - name: copy Red Hat publisher certificate
+      ansible.windows.win_copy:
+        src: redhat.pem
+        dest: '{{ ansible_env.TEMP }}'
+
+    # mark the publisher of the spice installer (Red Hat in this case) as trusted
+    # without this, the cannot install the guest tools in an unattended fashion as
+    # we'll get a prompt asking to trust the publisher of the installer being
+    # executed otherwise, thus preventing the installation from completing
+    - name: setup Red Hat publisher certificate as trusted
+      ansible.windows.win_certificate_store:
+        path: '{{ ansible_env.TEMP }}\redhat.pem'
+        file_type: pem
+        store_location: LocalMachine
+        store_name: TrustedPublisher
+        state: present
+  when: not matched_certificate.exists
+
+- name: install spice Windows guest tools
+  ansible.windows.win_package:
+    path: "{{ spice_guest_tools_url }}"
+    state: present
+    product_id: "{{ spice_guest_tools_product_id }}"
+    arguments: /S
