From 31ceba7fd8d68f5c976c65fd68915934eee3c089 Mon Sep 17 00:00:00 2001 From: Carson Anderson Date: Tue, 19 Mar 2019 20:13:06 -0600 Subject: [PATCH] Windows module: win_optional_feature for Windows workstations (#53709) * initial commit * fix execute and \r\n * \r attempt 2 * updated with integration tests and using new csharp import * Apply suggestions from code review Co-Authored-By: rcanderson23 * fixed small docuement inaccuracies wrt returns * removal of state in feature result * removal of rc * small fixes suggested in code review * fixed variable assigning to result * addition of comments on conditionals for clarity on matching * swap logic of check_mode * set $reboot_required so it is always returned * removal of extraneous return information * addition of integration tests * set installation of parent features to true * remove 2008 from tests * changed test for TelnetClient from NetFx3 * change of tabs to spaces * Add test check for OS version --- .../modules/windows/win_optional_feature.ps1 | 71 +++++++++++++++ .../modules/windows/win_optional_feature.py | 83 +++++++++++++++++ .../targets/win_optional_feature/aliases | 3 + .../win_optional_feature/tasks/main.yml | 25 ++++++ .../win_optional_feature/tasks/tests.yml | 88 +++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 lib/ansible/modules/windows/win_optional_feature.ps1 create mode 100644 lib/ansible/modules/windows/win_optional_feature.py create mode 100644 test/integration/targets/win_optional_feature/aliases create mode 100644 test/integration/targets/win_optional_feature/tasks/main.yml create mode 100644 test/integration/targets/win_optional_feature/tasks/tests.yml diff --git a/lib/ansible/modules/windows/win_optional_feature.ps1 b/lib/ansible/modules/windows/win_optional_feature.ps1 new file mode 100644 index 00000000000..e4fe8691be7 --- /dev/null +++ b/lib/ansible/modules/windows/win_optional_feature.ps1 @@ -0,0 +1,71 @@ +#!powershell + +# Copyright: (c) 2019, Carson Anderson +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +#AnsibleRequires -CSharpUtil Ansible.Basic + +$spec = @{ + options = @{ + name = @{ type = "str"; required = $true } + state = @{ type = "str"; default = "present"; choices = @("absent", "present") } + source = @{ type = "str" } + include_parent = @{ type = "bool"; default = $false } + } + supports_check_mode = $true +} + +$module = [Ansible.Basic.AnsibleModule]::Create($args, $spec) + +$name = $module.Params.name +$state = $module.Params.state +$source = $module.Params.source +$include_parent = $module.Params.include_parent + +$module.Result.reboot_required = $false + +if (-not (Get-Command -Name Enable-WindowsOptionalFeature -ErrorAction SilentlyContinue)) { + $module.FailJson("This version of Windows does not support the Enable-WindowsOptionalFeature.") +} + +$feature_state_start = Get-WindowsOptionalFeature -Online -FeatureName $name +if (-not $feature_state_start) { + $module.FailJson("Failed to find feature '$name'") +} + +if ($state -eq "present") { + # Matches for "Enabled" and "EnabledPending" + if ($feature_state_start.State -notlike "Enabled*") { + $install_args = @{ + FeatureName = $name + All = $include_parent + } + + if ($source) { + if (-not (Test-Path -LiteralPath $source)) { + $module.FailJson("Path could not be found '$source'") + } + $install_args.Source = $source + } + + if (-not $module.CheckMode) { + $action_result = Enable-WindowsOptionalFeature -Online -NoRestart @install_args + $module.Result.reboot_required = $action_result.RestartNeeded + } + $module.Result.changed = $true + } +} else { + # Matches for Disabled, DisabledPending, and DisabledWithPayloadRemoved + if ($feature_state_start.State -notlike "Disabled*") { + $remove_args = @{ + FeatureName = $name + } + + if (-not $module.CheckMode) { + $action_result = Disable-WindowsOptionalFeature -Online -NoRestart @remove_args + $module.Result.reboot_required = $action_result.RestartNeeded + } + $module.Result.changed = $true + } +} +$module.ExitJson() diff --git a/lib/ansible/modules/windows/win_optional_feature.py b/lib/ansible/modules/windows/win_optional_feature.py new file mode 100644 index 00000000000..95918424819 --- /dev/null +++ b/lib/ansible/modules/windows/win_optional_feature.py @@ -0,0 +1,83 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Carson Anderson +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# this is a windows documentation stub. actual code lives in the .ps1 +# file of the same name + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = r''' +--- +module: win_optional_feature +version_added: "2.8" +short_description: Manage optional Windows features +description: + - Install or uninstall optional Windows features on non-Server Windows. + - This module uses the C(Enable-WindowsOptionalFeature) and C(Disable-WindowsOptionalFeature) cmdlets. +options: + name: + description: + - The name of the feature to install. + - This relates to C(FeatureName) in the Powershell cmdlet. + - To list all available features use the PowerShell command C(Get-WindowsOptionalFeature). + type: str + required: yes + state: + description: + - Whether to ensure the feature is absent or present on the system. + type: str + choices: [ absent, present ] + default: present + include_parent: + description: + - Whether to enable the parent feature and the parent's dependencies. + type: bool + default: no + source: + description: + - Specify a source to install the feature from. + - Can either be C({driveletter}:\sources\sxs) or C(\\{IP}\share\sources\sxs). + type: str +seealso: +- module: win_chocolatey +- module: win_feature +- module: win_package +author: + - Carson Anderson (@rcanderson23) +''' + +EXAMPLES = r''' +- name: Install .Net 3.5 + win_optional_feature: + name: NetFx3 + state: present + +- name: Install .Net 3.5 from source + win_optional_feature: + name: NetFx3 + source: \\share01\win10\sources\sxs + state: present + +- name: Install Microsoft Subsystem for Linux + win_optional_feature: + name: Microsoft-Windows-Subsystem-Linux + state: present + register: wsl_status + +- name: Reboot if installing Linux Subsytem as feature requires it + win_reboot: + when: wsl_status.reboot_required +''' + +RETURN = r''' +reboot_required: + description: True when the target server requires a reboot to complete updates + returned: success + type: bool + sample: true +''' diff --git a/test/integration/targets/win_optional_feature/aliases b/test/integration/targets/win_optional_feature/aliases new file mode 100644 index 00000000000..9ad549d4a68 --- /dev/null +++ b/test/integration/targets/win_optional_feature/aliases @@ -0,0 +1,3 @@ +shippable/windows/group2 +skip/windows/2008 +skip/windows/2008-R2 diff --git a/test/integration/targets/win_optional_feature/tasks/main.yml b/test/integration/targets/win_optional_feature/tasks/main.yml new file mode 100644 index 00000000000..c0b61c1410a --- /dev/null +++ b/test/integration/targets/win_optional_feature/tasks/main.yml @@ -0,0 +1,25 @@ +# Test code for win_optional_feature module +# Copyright: (c) 2019, Carson Anderson + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +- name: check if host supports module + win_shell: if (Get-Command -Name Enable-WindowsOptionalFeature -ErrorAction SilentlyContinue) { $true } else { $false } + register: run_tests + +- name: run tests + include_tasks: tests.yml + when: run_tests.stdout | trim | bool diff --git a/test/integration/targets/win_optional_feature/tasks/tests.yml b/test/integration/targets/win_optional_feature/tasks/tests.yml new file mode 100644 index 00000000000..e109c19fb2c --- /dev/null +++ b/test/integration/targets/win_optional_feature/tasks/tests.yml @@ -0,0 +1,88 @@ +# Test code for win_optional_feature module +# Copyright: (c) 2019, Carson Anderson + +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . + +- name: run with check_mode + win_optional_feature: + name: TelnetClient + state: present + include_parent: true + check_mode: yes + register: feature_check + +- name: assert check_mode + assert: + that: + - feature_check.changed + +- name: run without check_mode + win_optional_feature: + name: TelnetClient + state: present + include_parent: true + register: real_feature_check + +- name: assert feature installed + assert: + that: + - real_feature_check.changed + +- name: test idempotence for install + win_optional_feature: + name: TelnetClient + state: present + include_parent: true + register: real_feature_check + +- name: assert idempotence + assert: + that: + - not real_feature_check.changed + +- name: removal run with check_mode + win_optional_feature: + name: TelnetClient + state: absent + check_mode: yes + register: feature_check + +- name: assert removal check_mode + assert: + that: + - feature_check.changed + +- name: remove feature + win_optional_feature: + name: TelnetClient + state: absent + register: real_feature_check + +- name: assert feature removed + assert: + that: + - real_feature_check.changed + +- name: test idempotence for removal + win_optional_feature: + name: TelnetClient + state: absent + register: real_feature_check + +- name: assert idempotence + assert: + that: + - not real_feature_check.changed