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:
parent
2a701d22f4
commit
f27078df52
5 changed files with 204 additions and 88 deletions
2
changelogs/fragments/win_power_plan-windows10.yaml
Normal file
2
changelogs/fragments/win_power_plan-windows10.yaml
Normal 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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,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 }}'
|
||||||
|
|
|
@ -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…
Reference in a new issue