win_power_plan: fix for Windows 10 and Server 2008 compatibility (#51471)

(cherry picked from commit f27078df52)
pull/51774/head
Jordan Borean 6 years ago committed by Toshio Kuratomi
parent ce033c6762
commit 9168e6844a

@ -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

@ -7,73 +7,204 @@
$params = Parse-Args -arguments $args -supports_check_mode $true $params = Parse-Args -arguments $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false $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 # these are your module parameters
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true $name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$result = @{
changed = $false
power_plan_name = $name
power_plan_enabled = $null
all_available_plans = $null
}
$pinvoke_functions = @"
using System;
using System.Runtime.InteropServices;
namespace Ansible.WinPowerPlan
{
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);
}
}
"@
$original_tmp = $env:TMP
$env:TMP = $_remote_tmp
Add-Type -TypeDefinition $pinvoke_functions
$env:TMP = $original_tmp
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
}
Function Get-PlanName {
param([Guid]$Plan)
$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)
}
}
Function Get-PowerPlans { Function Get-PowerPlans {
Param ($PlanName) $plans = @{}
If (-not $PlanName) {
Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan | $i = 0
Select-Object -Property ElementName, IsActive | while ($true) {
ForEach-Object -Begin { $ht = @{} } -Process { $ht."$($_.ElementName)" = $_.IsActive } -End { $ht } $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"
} }
Else {
Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan -Filter "ElementName = '$PlanName'" $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
#fail if older than 2008r2...need to do it here before Get-PowerPlans function runs further down $i += 1
}
If ([System.Environment]::OSVersion.Version -lt '6.1') return $plans
{
$result = @{
changed = $false
power_plan_name = $name
power_plan_enabled = $null
all_available_plans = $null
} }
Fail-Json $result "The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer"
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"
} }
$result = @{ try {
changed = $false $active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
power_plan_name = $name } finally {
power_plan_enabled = (Get-PowerPlans $name).isactive [Ansible.WinPowerPlan.NativeMethods]::LocalFree($buffer) > $null
all_available_plans = Get-PowerPlans
} }
$all_available_plans = Get-PowerPlans return $active_guid
}
#Terminate if plan is not found on the system Function Set-ActivePowerPlan {
If (! ($all_available_plans.ContainsKey($name)) ) [CmdletBinding(SupportsShouldProcess=$true)]
{ param([Guid]$Plan)
Fail-Json $result "Defined power_plan: ($name) is not available"
$res = 0
if ($PSCmdlet.ShouldProcess($Plan, "Set Power Plan")) {
$res = [Ansible.WinPowerPlan.NativeMethods]::PowerSetActiveScheme([IntPtr]::Zero, $plan_guid)
} }
#If true, means plan is already active and we exit here with changed: false if ($res -ne 0) {
#If false, means plan is not active and we move down to enable $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res
#Since the results here are the same whether check mode or not, no specific handling is required Fail-Json -obj $result -message "Failed to set the active power plan to $name - $err_msg"
#for check mode.
If ( $all_available_plans.item($name) )
{
Exit-Json $result
} }
Else
{
Try {
$Null = Invoke-CimMethod -InputObject (Get-PowerPlans $name) -MethodName Activate -ErrorAction Stop -WhatIf:$check_mode
} }
Catch {
$result.power_plan_enabled = (Get-PowerPlans $name).IsActive # Get all local power plans and the current active plan
$result.all_available_plans = Get-PowerPlans $plans = Get-PowerPlans
Fail-Json $result "Failed to set the new plan: $($_.Exception.Message)" $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
#set success parameters and exit if (-not $is_active) {
Set-ActivePowerPlan -Plan $plan_guid -WhatIf:$check_mode
$result.changed = $true $result.changed = $true
$result.power_plan_enabled = (Get-PowerPlans $name).IsActive $result.power_plan_enabled = $true
$result.all_available_plans = Get-PowerPlans foreach ($plan_info in $plans.GetEnumerator()) {
Exit-Json $result $is_active = $plan_info.Value -eq $plan_guid
$result.all_available_plans.($plan_info.Key) = $is_active
}
} }
Exit-Json -obj $result

@ -17,16 +17,16 @@ description:
- Windows defaults to C(balanced) which will cause CPU throttling. In some cases it can be preferable - Windows defaults to C(balanced) which will cause CPU throttling. In some cases it can be preferable
to change the mode to C(high performance) to increase CPU performance. to change the mode to C(high performance) to increase CPU performance.
version_added: "2.4" version_added: "2.4"
author:
- Noah Sparks (@nwsparks)
options: options:
name: name:
description: description:
- String value that indicates the desired power plan. The power plan must already be - String value that indicates the desired power plan.
present on the system. Commonly there will be options for C(balanced) and C(high performance). - The power plan must already be present on the system.
- Commonly there will be options for C(balanced) and C(high performance).
type: str
required: yes required: yes
requirements: author:
- Windows Server 2008R2 (6.1)/Windows 7 or higher - Noah Sparks (@nwsparks)
''' '''
EXAMPLES = ''' EXAMPLES = '''

@ -1,26 +1,16 @@
- name: register os version (seems integration tests don't gather this fact) # I dislike this but 2008 doesn't support the Win32_PowerPlan WMI provider
raw: powershell.exe "gwmi Win32_OperatingSystem | select -expand version" - name: get current plan details
register: os_version win_shell: |
changed_when: False $plan_info = powercfg.exe /list
# ^^ seems "raw" is the only module that works on 2008 non-r2. win_command and win_shell both failed ($plan_info | Select-String -Pattern '\(([\w\s]*)\) \*$').Matches.Groups[1].Value
($plan_info | Select-String -Pattern '\(([\w\s]*)\)$').Matches.Groups[1].Value
- name: check if module fails gracefully when older than 2008r2 register: plan_info
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'
- 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: - set_fact:
name: "{{ disabled_power_plan.stdout_lines[0] }}" original_plan: '{{ plan_info.stdout_lines[0] }}'
name: '{{ plan_info.stdout_lines[1] }}'
- block:
#Test that plan detects change is needed, but doesn't actually apply change #Test that plan detects change is needed, but doesn't actually apply change
- name: set power plan (check mode) - name: set power plan (check mode)
win_power_plan: win_power_plan:
@ -28,11 +18,8 @@
register: set_plan_check register: set_plan_check
check_mode: yes check_mode: yes
# - debug:
# var: set_plan_check
- name: get result of set power plan (check mode) - 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 register: set_plan_check_result
changed_when: False changed_when: False
@ -41,7 +28,7 @@
assert: assert:
that: that:
- set_plan_check is changed - 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 #Test that setting plan and that change is applied
- name: set power plan - name: set power plan
@ -50,7 +37,7 @@
register: set_plan register: set_plan
- name: get result of set power 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 register: set_plan_result
changed_when: False changed_when: False
@ -58,7 +45,7 @@
assert: assert:
that: that:
- set_plan is changed - 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 #Test that plan doesn't apply change if it is already set
- name: set power plan (idempotent) - name: set power plan (idempotent)
@ -71,8 +58,7 @@
that: that:
- set_plan_idempotent is not changed - set_plan_idempotent is not changed
when: os_version.stdout_lines[0] is version('6.1','ge')
always: 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: win_power_plan:
name: high performance name: '{{ original_plan }}'

@ -53,7 +53,6 @@ lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingPositionalParameters
lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingWMICmdlet lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingWMICmdlet
lib/ansible/modules/windows/win_pagefile.ps1 PSUseDeclaredVarsMoreThanAssignments lib/ansible/modules/windows/win_pagefile.ps1 PSUseDeclaredVarsMoreThanAssignments
lib/ansible/modules/windows/win_pagefile.ps1 PSUseSupportsShouldProcess 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_psmodule.ps1 PSAvoidUsingCmdletAliases
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingCmdletAliases lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingCmdletAliases
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingInvokeExpression lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingInvokeExpression

Loading…
Cancel
Save