1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

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

This commit is contained in:
Jordan Borean 2019-02-01 06:32:12 +10:00 committed by GitHub
parent 2a701d22f4
commit f27078df52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 204 additions and 88 deletions

View file

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

View file

@ -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)
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'"
}
}
#fail if older than 2008r2...need to do it here before Get-PowerPlans function runs further down
If ([System.Environment]::OSVersion.Version -lt '6.1')
{
$result = @{
changed = $false changed = $false
power_plan_name = $name power_plan_name = $name
power_plan_enabled = $null power_plan_enabled = $null
all_available_plans = $null all_available_plans = $null
}
Fail-Json $result "The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer"
} }
$result = @{ $pinvoke_functions = @"
changed = $false using System;
power_plan_name = $name using System.Runtime.InteropServices;
power_plan_enabled = (Get-PowerPlans $name).isactive
all_available_plans = Get-PowerPlans
}
$all_available_plans = Get-PowerPlans namespace Ansible.WinPowerPlan
#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" public enum AccessFlags : uint
} {
AccessScheme = 16,
#If true, means plan is already active and we exit here with changed: false AccessSubgroup = 17,
#If false, means plan is not active and we move down to enable AccessIndividualSetting = 18
#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
}
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
$result.all_available_plans = Get-PowerPlans
Fail-Json $result "Failed to set the new plan: $($_.Exception.Message)"
} }
#set success parameters and exit 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 {
$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
}
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"
}
try {
$active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid])
} finally {
[Ansible.WinPowerPlan.NativeMethods]::LocalFree($buffer) > $null
}
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

View file

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

View file

@ -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,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 }}'

View file

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