summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJordan Borean <jborean93@gmail.com>2019-02-01 06:32:12 +1000
committerToshio Kuratomi <a.badger@gmail.com>2019-02-04 12:31:44 -0800
commit72703bd3b75a8c414a4203fa49e7c6ab1ab63aca (patch)
tree78bbc9b53550ba9a135ef3cd37181e0d93418354
parente9e3999ddca2ff8961b020d85f8de93e8369837b (diff)
downloadansible-72703bd3b75a8c414a4203fa49e7c6ab1ab63aca.tar.gz
win_power_plan: fix for Windows 10 and Server 2008 compatibility (#51471)
(cherry picked from commit f27078df520007824969714ce5b442c536128044)
-rw-r--r--changelogs/fragments/win_power_plan-windows10.yaml2
-rw-r--r--lib/ansible/modules/windows/win_power_plan.ps1225
-rw-r--r--lib/ansible/modules/windows/win_power_plan.py2
-rw-r--r--test/integration/targets/win_power_plan/tasks/main.yml48
-rw-r--r--test/sanity/pslint/ignore.txt1
5 files changed, 197 insertions, 81 deletions
diff --git a/changelogs/fragments/win_power_plan-windows10.yaml b/changelogs/fragments/win_power_plan-windows10.yaml
new file mode 100644
index 0000000000..4bfabf56a1
--- /dev/null
+++ b/changelogs/fragments/win_power_plan-windows10.yaml
@@ -0,0 +1,2 @@
+bugfixes:
+- win_power_plan - Fix issue where win_power_plan failed on newer Windows 10 builds - https://github.com/ansible/ansible/issues/43827
diff --git a/lib/ansible/modules/windows/win_power_plan.ps1 b/lib/ansible/modules/windows/win_power_plan.ps1
index 6022b447b8..1914862ab7 100644
--- a/lib/ansible/modules/windows/win_power_plan.ps1
+++ b/lib/ansible/modules/windows/win_power_plan.ps1
@@ -7,73 +7,204 @@
$params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false
+$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP
# these are your module parameters
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
-Function Get-PowerPlans {
-Param ($PlanName)
- If (-not $PlanName) {
- Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan |
- Select-Object -Property ElementName, IsActive |
- ForEach-Object -Begin { $ht = @{} } -Process { $ht."$($_.ElementName)" = $_.IsActive } -End { $ht }
- }
- Else {
- Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan -Filter "ElementName = '$PlanName'"
- }
+$result = @{
+ changed = $false
+ power_plan_name = $name
+ power_plan_enabled = $null
+ all_available_plans = $null
}
-#fail if older than 2008r2...need to do it here before Get-PowerPlans function runs further down
+$pinvoke_functions = @"
+using System;
+using System.Runtime.InteropServices;
-If ([System.Environment]::OSVersion.Version -lt '6.1')
+namespace Ansible.WinPowerPlan
{
- $result = @{
- changed = $false
- power_plan_name = $name
- power_plan_enabled = $null
- all_available_plans = $null
+ public enum AccessFlags : uint
+ {
+ AccessScheme = 16,
+ AccessSubgroup = 17,
+ AccessIndividualSetting = 18
+ }
+
+ public class NativeMethods
+ {
+ [DllImport("Kernel32.dll", SetLastError = true)]
+ public static extern IntPtr LocalFree(
+ IntPtr hMen);
+
+ [DllImport("PowrProf.dll")]
+ public static extern UInt32 PowerEnumerate(
+ IntPtr RootPowerKey,
+ IntPtr SchemeGuid,
+ IntPtr SubGroupOfPowerSettingsGuid,
+ AccessFlags AccessFlags,
+ UInt32 Index,
+ IntPtr Buffer,
+ ref UInt32 BufferSize);
+
+ [DllImport("PowrProf.dll")]
+ public static extern UInt32 PowerGetActiveScheme(
+ IntPtr UserRootPowerKey,
+ out IntPtr ActivePolicyGuid);
+
+ [DllImport("PowrProf.dll")]
+ public static extern UInt32 PowerReadFriendlyName(
+ IntPtr RootPowerKey,
+ Guid SchemeGuid,
+ IntPtr SubGroupOfPowerSettingsGuid,
+ IntPtr PowerSettingGuid,
+ IntPtr Buffer,
+ ref UInt32 BufferSize);
+
+ [DllImport("PowrProf.dll")]
+ public static extern UInt32 PowerSetActiveScheme(
+ IntPtr UserRootPowerKey,
+ Guid SchemeGuid);
}
- Fail-Json $result "The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer"
}
+"@
+$original_tmp = $env:TMP
+$env:TMP = $_remote_tmp
+Add-Type -TypeDefinition $pinvoke_functions
+$env:TMP = $original_tmp
-$result = @{
- changed = $false
- power_plan_name = $name
- power_plan_enabled = (Get-PowerPlans $name).isactive
- all_available_plans = Get-PowerPlans
+Function Get-LastWin32ErrorMessage {
+ param([Int]$ErrorCode)
+ $exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode
+ $error_msg = "{0} - (Win32 Error Code {1} - 0x{1:X8})" -f $exp.Message, $ErrorCode
+ return $error_msg
}
-$all_available_plans = Get-PowerPlans
+Function Get-PlanName {
+ param([Guid]$Plan)
-#Terminate if plan is not found on the system
-If (! ($all_available_plans.ContainsKey($name)) )
-{
- Fail-Json $result "Defined power_plan: ($name) is not available"
+ $buffer_size = 0
+ $buffer = [IntPtr]::Zero
+ [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero, [IntPtr]::Zero,
+ $buffer, [ref]$buffer_size) > $null
+
+ $buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size)
+ try {
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero,
+ [IntPtr]::Zero, $buffer, [ref]$buffer_size)
+
+ if ($res -ne 0) {
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
+ Fail-Json -obj $result -message "Failed to get name for power scheme $Plan - $err_msg"
+ }
+
+ return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($buffer)
+ } finally {
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer)
+ }
}
-#If true, means plan is already active and we exit here with changed: false
-#If false, means plan is not active and we move down to enable
-#Since the results here are the same whether check mode or not, no specific handling is required
-#for check mode.
-If ( $all_available_plans.item($name) )
-{
- Exit-Json $result
+Function Get-PowerPlans {
+ $plans = @{}
+
+ $i = 0
+ while ($true) {
+ $buffer_size = 0
+ $buffer = [IntPtr]::Zero
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero,
+ [Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size)
+
+ if ($res -eq 259) {
+ # 259 == ERROR_NO_MORE_ITEMS, there are no more power plans to enumerate
+ break
+ } elseif ($res -notin @(0, 234)) {
+ # 0 == ERROR_SUCCESS and 234 == ERROR_MORE_DATA
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
+ Fail-Json -obj $result -message "Failed to get buffer size on local power schemes at index $i - $err_msg"
+ }
+
+ $buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size)
+ try {
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero,
+ [Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size)
+
+ if ($res -eq 259) {
+ # Server 2008 does not return 259 in the first call above so we do an additional check here
+ break
+ } elseif ($res -notin @(0, 234, 259)) {
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
+ Fail-Json -obj $result -message "Failed to enumerate local power schemes at index $i - $err_msg"
+ }
+ $scheme_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
+ } finally {
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer)
+ }
+ $scheme_name = Get-PlanName -Plan $scheme_guid
+ $plans.$scheme_name = $scheme_guid
+
+ $i += 1
+ }
+
+ return $plans
}
-Else
-{
- Try {
- $Null = Invoke-CimMethod -InputObject (Get-PowerPlans $name) -MethodName Activate -ErrorAction Stop -WhatIf:$check_mode
+
+Function Get-ActivePowerPlan {
+ $buffer = [IntPtr]::Zero
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerGetActiveScheme([IntPtr]::Zero, [ref]$buffer)
+ if ($res -ne 0) {
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
+ Fail-Json -obj $result -message "Failed to get the active power plan - $err_msg"
}
- Catch {
- $result.power_plan_enabled = (Get-PowerPlans $name).IsActive
- $result.all_available_plans = Get-PowerPlans
- Fail-Json $result "Failed to set the new plan: $($_.Exception.Message)"
+
+ try {
+ $active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
+ } finally {
+ [Ansible.WinPowerPlan.NativeMethods]::LocalFree($buffer) > $null
}
- #set success parameters and exit
+ return $active_guid
+}
+
+Function Set-ActivePowerPlan {
+ [CmdletBinding(SupportsShouldProcess=$true)]
+ param([Guid]$Plan)
+
+ $res = 0
+ if ($PSCmdlet.ShouldProcess($Plan, "Set Power Plan")) {
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerSetActiveScheme([IntPtr]::Zero, $plan_guid)
+ }
+
+ if ($res -ne 0) {
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
+ Fail-Json -obj $result -message "Failed to set the active power plan to $name - $err_msg"
+ }
+}
+
+# Get all local power plans and the current active plan
+$plans = Get-PowerPlans
+$active_plan = Get-ActivePowerPlan
+$result.all_available_plans = @{}
+foreach ($plan_info in $plans.GetEnumerator()) {
+ $result.all_available_plans.($plan_info.Key) = $plan_info.Value -eq $active_plan
+}
+
+if ($name -notin $plans.Keys) {
+ Fail-Json -obj $result -message "Defined power_plan: ($name) is not available"
+}
+$plan_guid = $plans.$name
+$is_active = $active_plan -eq $plans.$name
+$result.power_plan_enabled = $is_active
+
+if (-not $is_active) {
+ Set-ActivePowerPlan -Plan $plan_guid -WhatIf:$check_mode
$result.changed = $true
- $result.power_plan_enabled = (Get-PowerPlans $name).IsActive
- $result.all_available_plans = Get-PowerPlans
- Exit-Json $result
+ $result.power_plan_enabled = $true
+ foreach ($plan_info in $plans.GetEnumerator()) {
+ $is_active = $plan_info.Value -eq $plan_guid
+ $result.all_available_plans.($plan_info.Key) = $is_active
+ }
}
+Exit-Json -obj $result
+
diff --git a/lib/ansible/modules/windows/win_power_plan.py b/lib/ansible/modules/windows/win_power_plan.py
index f6b2da4485..052b4ac665 100644
--- a/lib/ansible/modules/windows/win_power_plan.py
+++ b/lib/ansible/modules/windows/win_power_plan.py
@@ -25,8 +25,6 @@ options:
- String value that indicates the desired power plan. The power plan must already be
present on the system. Commonly there will be options for C(balanced) and C(high performance).
required: yes
-requirements:
- - Windows Server 2008R2 (6.1)/Windows 7 or higher
'''
EXAMPLES = '''
diff --git a/test/integration/targets/win_power_plan/tasks/main.yml b/test/integration/targets/win_power_plan/tasks/main.yml
index 0b6f0c315e..000d8b1a66 100644
--- a/test/integration/targets/win_power_plan/tasks/main.yml
+++ b/test/integration/targets/win_power_plan/tasks/main.yml
@@ -1,26 +1,16 @@
-- name: register os version (seems integration tests don't gather this fact)
- raw: powershell.exe "gwmi Win32_OperatingSystem | select -expand version"
- register: os_version
- changed_when: False
-# ^^ seems "raw" is the only module that works on 2008 non-r2. win_command and win_shell both failed
+# I dislike this but 2008 doesn't support the Win32_PowerPlan WMI provider
+- name: get current plan details
+ win_shell: |
+ $plan_info = powercfg.exe /list
+ ($plan_info | Select-String -Pattern '\(([\w\s]*)\) \*$').Matches.Groups[1].Value
+ ($plan_info | Select-String -Pattern '\(([\w\s]*)\)$').Matches.Groups[1].Value
+ register: plan_info
-- name: check if module fails gracefully when older than 2008r2
- win_power_plan:
- name: "high performance"
- when: os_version.stdout_lines[0] is version('6.1','lt')
- check_mode: yes
- register: old_os_check
- failed_when: old_os_check.msg != 'The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer'
+- set_fact:
+ original_plan: '{{ plan_info.stdout_lines[0] }}'
+ name: '{{ plan_info.stdout_lines[1] }}'
- block:
- - name: register inactive power plan to test with
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan | ? {! $_.IsActive}).ElementName[0]
- register: disabled_power_plan
- changed_when: False
-
- - set_fact:
- name: "{{ disabled_power_plan.stdout_lines[0] }}"
-
#Test that plan detects change is needed, but doesn't actually apply change
- name: set power plan (check mode)
win_power_plan:
@@ -28,20 +18,17 @@
register: set_plan_check
check_mode: yes
-# - debug:
-# var: set_plan_check
-
- name: get result of set power plan (check mode)
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan -Filter "ElementName = '{{ name }}'").IsActive
+ win_shell: (powercfg.exe /list | Select-String -Pattern '\({{ name }}\)').Line
register: set_plan_check_result
changed_when: False
-
+
# verify that the powershell check is showing the plan as still inactive on the system
- name: assert setting plan (check mode)
assert:
that:
- set_plan_check is changed
- - set_plan_check_result.stdout == 'False\r\n'
+ - not set_plan_check_result.stdout_lines[0].endswith('*')
#Test that setting plan and that change is applied
- name: set power plan
@@ -50,7 +37,7 @@
register: set_plan
- name: get result of set power plan
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan -Filter "ElementName = '{{ name }}'").IsActive
+ win_shell: (powercfg.exe /list | Select-String -Pattern '\({{ name }}\)').Line
register: set_plan_result
changed_when: False
@@ -58,7 +45,7 @@
assert:
that:
- set_plan is changed
- - set_plan_result.stdout == 'True\r\n'
+ - set_plan_result.stdout_lines[0].endswith('*')
#Test that plan doesn't apply change if it is already set
- name: set power plan (idempotent)
@@ -71,8 +58,7 @@
that:
- set_plan_idempotent is not changed
- when: os_version.stdout_lines[0] is version('6.1','ge')
always:
- - name: always change back plan to high performance when done testing
+ - name: always change back plan to the original when done testing
win_power_plan:
- name: high performance
+ name: '{{ original_plan }}'
diff --git a/test/sanity/pslint/ignore.txt b/test/sanity/pslint/ignore.txt
index 3da598c5c3..0239a1376c 100644
--- a/test/sanity/pslint/ignore.txt
+++ b/test/sanity/pslint/ignore.txt
@@ -56,7 +56,6 @@ lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingPositionalParameters
lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingWMICmdlet
lib/ansible/modules/windows/win_pagefile.ps1 PSUseDeclaredVarsMoreThanAssignments
lib/ansible/modules/windows/win_pagefile.ps1 PSUseSupportsShouldProcess
-lib/ansible/modules/windows/win_power_plan.ps1 PSUseDeclaredVarsMoreThanAssignments
lib/ansible/modules/windows/win_psmodule.ps1 PSAvoidUsingCmdletAliases
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingCmdletAliases
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingInvokeExpression