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

pull/51563/head
Jordan Borean 6 years ago committed by GitHub
parent 2a701d22f4
commit f27078df52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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
Function Get-PowerPlans { $result = @{
Param ($PlanName) changed = $false
If (-not $PlanName) { power_plan_name = $name
Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan | power_plan_enabled = $null
Select-Object -Property ElementName, IsActive | all_available_plans = $null
ForEach-Object -Begin { $ht = @{} } -Process { $ht."$($_.ElementName)" = $_.IsActive } -End { $ht }
}
Else {
Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan -Filter "ElementName = '$PlanName'"
}
} }
#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 = @{ public enum AccessFlags : uint
changed = $false {
power_plan_name = $name AccessScheme = 16,
power_plan_enabled = $null AccessSubgroup = 17,
all_available_plans = $null 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 = @{ Function Get-LastWin32ErrorMessage {
changed = $false param([Int]$ErrorCode)
power_plan_name = $name $exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode
power_plan_enabled = (Get-PowerPlans $name).isactive $error_msg = "{0} - (Win32 Error Code {1} - 0x{1:X8})" -f $exp.Message, $ErrorCode
all_available_plans = Get-PowerPlans return $error_msg
} }
$all_available_plans = Get-PowerPlans Function Get-PlanName {
param([Guid]$Plan)
#Terminate if plan is not found on the system $buffer_size = 0
If (! ($all_available_plans.ContainsKey($name)) ) $buffer = [IntPtr]::Zero
{ [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero, [IntPtr]::Zero,
Fail-Json $result "Defined power_plan: ($name) is not available" $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 Function Get-PowerPlans {
#If false, means plan is not active and we move down to enable $plans = @{}
#Since the results here are the same whether check mode or not, no specific handling is required
#for check mode. $i = 0
If ( $all_available_plans.item($name) ) while ($true) {
{ $buffer_size = 0
Exit-Json $result $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
{ Function Get-ActivePowerPlan {
Try { $buffer = [IntPtr]::Zero
$Null = Invoke-CimMethod -InputObject (Get-PowerPlans $name) -MethodName Activate -ErrorAction Stop -WhatIf:$check_mode $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 try {
$result.all_available_plans = Get-PowerPlans $active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
Fail-Json $result "Failed to set the new plan: $($_.Exception.Message)" } 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.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,8 +17,6 @@ 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"
requirements:
- Windows Server 2008R2 (6.1)/Windows 7 or higher
options: options:
name: name:
description: description:

@ -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
register: plan_info
- name: check if module fails gracefully when older than 2008r2 - set_fact:
win_power_plan: original_plan: '{{ plan_info.stdout_lines[0] }}'
name: "high performance" name: '{{ plan_info.stdout_lines[1] }}'
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: - 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 #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,20 +18,17 @@
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
# verify that the powershell check is showing the plan as still inactive on the system # verify that the powershell check is showing the plan as still inactive on the system
- name: assert setting plan (check mode) - name: assert setting plan (check mode)
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 }}'

@ -54,7 +54,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