(cherry picked from commit f27078df520007824969714ce5b442c536128044)
Jordan Borean authored on 2019/02/01 05:32:12... | ... |
@@ -7,73 +7,204 @@ |
7 | 7 |
|
8 | 8 |
$params = Parse-Args -arguments $args -supports_check_mode $true |
9 | 9 |
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false |
10 |
+$_remote_tmp = Get-AnsibleParam $params "_ansible_remote_tmp" -type "path" -default $env:TMP |
|
10 | 11 |
|
11 | 12 |
# these are your module parameters |
12 | 13 |
$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true |
13 | 14 |
|
14 |
-Function Get-PowerPlans { |
|
15 |
-Param ($PlanName) |
|
16 |
- If (-not $PlanName) { |
|
17 |
- Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan | |
|
18 |
- Select-Object -Property ElementName, IsActive | |
|
19 |
- ForEach-Object -Begin { $ht = @{} } -Process { $ht."$($_.ElementName)" = $_.IsActive } -End { $ht } |
|
20 |
- } |
|
21 |
- Else { |
|
22 |
- Get-CimInstance -Name root\cimv2\power -Class Win32_PowerPlan -Filter "ElementName = '$PlanName'" |
|
23 |
- } |
|
15 |
+$result = @{ |
|
16 |
+ changed = $false |
|
17 |
+ power_plan_name = $name |
|
18 |
+ power_plan_enabled = $null |
|
19 |
+ all_available_plans = $null |
|
24 | 20 |
} |
25 | 21 |
|
26 |
-#fail if older than 2008r2...need to do it here before Get-PowerPlans function runs further down |
|
22 |
+$pinvoke_functions = @" |
|
23 |
+using System; |
|
24 |
+using System.Runtime.InteropServices; |
|
27 | 25 |
|
28 |
-If ([System.Environment]::OSVersion.Version -lt '6.1') |
|
26 |
+namespace Ansible.WinPowerPlan |
|
29 | 27 |
{ |
30 |
- $result = @{ |
|
31 |
- changed = $false |
|
32 |
- power_plan_name = $name |
|
33 |
- power_plan_enabled = $null |
|
34 |
- all_available_plans = $null |
|
28 |
+ public enum AccessFlags : uint |
|
29 |
+ { |
|
30 |
+ AccessScheme = 16, |
|
31 |
+ AccessSubgroup = 17, |
|
32 |
+ AccessIndividualSetting = 18 |
|
33 |
+ } |
|
34 |
+ |
|
35 |
+ public class NativeMethods |
|
36 |
+ { |
|
37 |
+ [DllImport("Kernel32.dll", SetLastError = true)] |
|
38 |
+ public static extern IntPtr LocalFree( |
|
39 |
+ IntPtr hMen); |
|
40 |
+ |
|
41 |
+ [DllImport("PowrProf.dll")] |
|
42 |
+ public static extern UInt32 PowerEnumerate( |
|
43 |
+ IntPtr RootPowerKey, |
|
44 |
+ IntPtr SchemeGuid, |
|
45 |
+ IntPtr SubGroupOfPowerSettingsGuid, |
|
46 |
+ AccessFlags AccessFlags, |
|
47 |
+ UInt32 Index, |
|
48 |
+ IntPtr Buffer, |
|
49 |
+ ref UInt32 BufferSize); |
|
50 |
+ |
|
51 |
+ [DllImport("PowrProf.dll")] |
|
52 |
+ public static extern UInt32 PowerGetActiveScheme( |
|
53 |
+ IntPtr UserRootPowerKey, |
|
54 |
+ out IntPtr ActivePolicyGuid); |
|
55 |
+ |
|
56 |
+ [DllImport("PowrProf.dll")] |
|
57 |
+ public static extern UInt32 PowerReadFriendlyName( |
|
58 |
+ IntPtr RootPowerKey, |
|
59 |
+ Guid SchemeGuid, |
|
60 |
+ IntPtr SubGroupOfPowerSettingsGuid, |
|
61 |
+ IntPtr PowerSettingGuid, |
|
62 |
+ IntPtr Buffer, |
|
63 |
+ ref UInt32 BufferSize); |
|
64 |
+ |
|
65 |
+ [DllImport("PowrProf.dll")] |
|
66 |
+ public static extern UInt32 PowerSetActiveScheme( |
|
67 |
+ IntPtr UserRootPowerKey, |
|
68 |
+ Guid SchemeGuid); |
|
35 | 69 |
} |
36 |
- Fail-Json $result "The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer" |
|
37 | 70 |
} |
71 |
+"@ |
|
72 |
+$original_tmp = $env:TMP |
|
73 |
+$env:TMP = $_remote_tmp |
|
74 |
+Add-Type -TypeDefinition $pinvoke_functions |
|
75 |
+$env:TMP = $original_tmp |
|
38 | 76 |
|
39 |
-$result = @{ |
|
40 |
- changed = $false |
|
41 |
- power_plan_name = $name |
|
42 |
- power_plan_enabled = (Get-PowerPlans $name).isactive |
|
43 |
- all_available_plans = Get-PowerPlans |
|
77 |
+Function Get-LastWin32ErrorMessage { |
|
78 |
+ param([Int]$ErrorCode) |
|
79 |
+ $exp = New-Object -TypeName System.ComponentModel.Win32Exception -ArgumentList $ErrorCode |
|
80 |
+ $error_msg = "{0} - (Win32 Error Code {1} - 0x{1:X8})" -f $exp.Message, $ErrorCode |
|
81 |
+ return $error_msg |
|
44 | 82 |
} |
45 | 83 |
|
46 |
-$all_available_plans = Get-PowerPlans |
|
84 |
+Function Get-PlanName { |
|
85 |
+ param([Guid]$Plan) |
|
47 | 86 |
|
48 |
-#Terminate if plan is not found on the system |
|
49 |
-If (! ($all_available_plans.ContainsKey($name)) ) |
|
50 |
-{ |
|
51 |
- Fail-Json $result "Defined power_plan: ($name) is not available" |
|
87 |
+ $buffer_size = 0 |
|
88 |
+ $buffer = [IntPtr]::Zero |
|
89 |
+ [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero, [IntPtr]::Zero, |
|
90 |
+ $buffer, [ref]$buffer_size) > $null |
|
91 |
+ |
|
92 |
+ $buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size) |
|
93 |
+ try { |
|
94 |
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerReadFriendlyName([IntPtr]::Zero, $Plan, [IntPtr]::Zero, |
|
95 |
+ [IntPtr]::Zero, $buffer, [ref]$buffer_size) |
|
96 |
+ |
|
97 |
+ if ($res -ne 0) { |
|
98 |
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res |
|
99 |
+ Fail-Json -obj $result -message "Failed to get name for power scheme $Plan - $err_msg" |
|
100 |
+ } |
|
101 |
+ |
|
102 |
+ return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($buffer) |
|
103 |
+ } finally { |
|
104 |
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer) |
|
105 |
+ } |
|
52 | 106 |
} |
53 | 107 |
|
54 |
-#If true, means plan is already active and we exit here with changed: false |
|
55 |
-#If false, means plan is not active and we move down to enable |
|
56 |
-#Since the results here are the same whether check mode or not, no specific handling is required |
|
57 |
-#for check mode. |
|
58 |
-If ( $all_available_plans.item($name) ) |
|
59 |
-{ |
|
60 |
- Exit-Json $result |
|
108 |
+Function Get-PowerPlans { |
|
109 |
+ $plans = @{} |
|
110 |
+ |
|
111 |
+ $i = 0 |
|
112 |
+ while ($true) { |
|
113 |
+ $buffer_size = 0 |
|
114 |
+ $buffer = [IntPtr]::Zero |
|
115 |
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero, |
|
116 |
+ [Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size) |
|
117 |
+ |
|
118 |
+ if ($res -eq 259) { |
|
119 |
+ # 259 == ERROR_NO_MORE_ITEMS, there are no more power plans to enumerate |
|
120 |
+ break |
|
121 |
+ } elseif ($res -notin @(0, 234)) { |
|
122 |
+ # 0 == ERROR_SUCCESS and 234 == ERROR_MORE_DATA |
|
123 |
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res |
|
124 |
+ Fail-Json -obj $result -message "Failed to get buffer size on local power schemes at index $i - $err_msg" |
|
125 |
+ } |
|
126 |
+ |
|
127 |
+ $buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($buffer_size) |
|
128 |
+ try { |
|
129 |
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerEnumerate([IntPtr]::Zero, [IntPtr]::Zero, [IntPtr]::Zero, |
|
130 |
+ [Ansible.WinPowerPlan.AccessFlags]::AccessScheme, $i, $buffer, [ref]$buffer_size) |
|
131 |
+ |
|
132 |
+ if ($res -eq 259) { |
|
133 |
+ # Server 2008 does not return 259 in the first call above so we do an additional check here |
|
134 |
+ break |
|
135 |
+ } elseif ($res -notin @(0, 234, 259)) { |
|
136 |
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res |
|
137 |
+ Fail-Json -obj $result -message "Failed to enumerate local power schemes at index $i - $err_msg" |
|
138 |
+ } |
|
139 |
+ $scheme_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid]) |
|
140 |
+ } finally { |
|
141 |
+ [System.Runtime.InteropServices.Marshal]::FreeHGlobal($buffer) |
|
142 |
+ } |
|
143 |
+ $scheme_name = Get-PlanName -Plan $scheme_guid |
|
144 |
+ $plans.$scheme_name = $scheme_guid |
|
145 |
+ |
|
146 |
+ $i += 1 |
|
147 |
+ } |
|
148 |
+ |
|
149 |
+ return $plans |
|
61 | 150 |
} |
62 |
-Else |
|
63 |
-{ |
|
64 |
- Try { |
|
65 |
- $Null = Invoke-CimMethod -InputObject (Get-PowerPlans $name) -MethodName Activate -ErrorAction Stop -WhatIf:$check_mode |
|
151 |
+ |
|
152 |
+Function Get-ActivePowerPlan { |
|
153 |
+ $buffer = [IntPtr]::Zero |
|
154 |
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerGetActiveScheme([IntPtr]::Zero, [ref]$buffer) |
|
155 |
+ if ($res -ne 0) { |
|
156 |
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res |
|
157 |
+ Fail-Json -obj $result -message "Failed to get the active power plan - $err_msg" |
|
66 | 158 |
} |
67 |
- Catch { |
|
68 |
- $result.power_plan_enabled = (Get-PowerPlans $name).IsActive |
|
69 |
- $result.all_available_plans = Get-PowerPlans |
|
70 |
- Fail-Json $result "Failed to set the new plan: $($_.Exception.Message)" |
|
159 |
+ |
|
160 |
+ try { |
|
161 |
+ $active_guid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($buffer, [Type][Guid]) |
|
162 |
+ } finally { |
|
163 |
+ [Ansible.WinPowerPlan.NativeMethods]::LocalFree($buffer) > $null |
|
71 | 164 |
} |
72 | 165 |
|
73 |
- #set success parameters and exit |
|
166 |
+ return $active_guid |
|
167 |
+} |
|
168 |
+ |
|
169 |
+Function Set-ActivePowerPlan { |
|
170 |
+ [CmdletBinding(SupportsShouldProcess=$true)] |
|
171 |
+ param([Guid]$Plan) |
|
172 |
+ |
|
173 |
+ $res = 0 |
|
174 |
+ if ($PSCmdlet.ShouldProcess($Plan, "Set Power Plan")) { |
|
175 |
+ $res = [Ansible.WinPowerPlan.NativeMethods]::PowerSetActiveScheme([IntPtr]::Zero, $plan_guid) |
|
176 |
+ } |
|
177 |
+ |
|
178 |
+ if ($res -ne 0) { |
|
179 |
+ $err_msg = Get-LastWin32ErrorMessage -ErrorCode $res |
|
180 |
+ Fail-Json -obj $result -message "Failed to set the active power plan to $name - $err_msg" |
|
181 |
+ } |
|
182 |
+} |
|
183 |
+ |
|
184 |
+# Get all local power plans and the current active plan |
|
185 |
+$plans = Get-PowerPlans |
|
186 |
+$active_plan = Get-ActivePowerPlan |
|
187 |
+$result.all_available_plans = @{} |
|
188 |
+foreach ($plan_info in $plans.GetEnumerator()) { |
|
189 |
+ $result.all_available_plans.($plan_info.Key) = $plan_info.Value -eq $active_plan |
|
190 |
+} |
|
191 |
+ |
|
192 |
+if ($name -notin $plans.Keys) { |
|
193 |
+ Fail-Json -obj $result -message "Defined power_plan: ($name) is not available" |
|
194 |
+} |
|
195 |
+$plan_guid = $plans.$name |
|
196 |
+$is_active = $active_plan -eq $plans.$name |
|
197 |
+$result.power_plan_enabled = $is_active |
|
198 |
+ |
|
199 |
+if (-not $is_active) { |
|
200 |
+ Set-ActivePowerPlan -Plan $plan_guid -WhatIf:$check_mode |
|
74 | 201 |
$result.changed = $true |
75 |
- $result.power_plan_enabled = (Get-PowerPlans $name).IsActive |
|
76 |
- $result.all_available_plans = Get-PowerPlans |
|
77 |
- Exit-Json $result |
|
202 |
+ $result.power_plan_enabled = $true |
|
203 |
+ foreach ($plan_info in $plans.GetEnumerator()) { |
|
204 |
+ $is_active = $plan_info.Value -eq $plan_guid |
|
205 |
+ $result.all_available_plans.($plan_info.Key) = $is_active |
|
206 |
+ } |
|
78 | 207 |
} |
79 | 208 |
|
209 |
+Exit-Json -obj $result |
|
210 |
+ |
... | ... |
@@ -25,8 +25,6 @@ options: |
25 | 25 |
- String value that indicates the desired power plan. The power plan must already be |
26 | 26 |
present on the system. Commonly there will be options for C(balanced) and C(high performance). |
27 | 27 |
required: yes |
28 |
-requirements: |
|
29 |
- - Windows Server 2008R2 (6.1)/Windows 7 or higher |
|
30 | 28 |
''' |
31 | 29 |
|
32 | 30 |
EXAMPLES = ''' |
... | ... |
@@ -1,26 +1,16 @@ |
1 |
-- name: register os version (seems integration tests don't gather this fact) |
|
2 |
- raw: powershell.exe "gwmi Win32_OperatingSystem | select -expand version" |
|
3 |
- register: os_version |
|
4 |
- changed_when: False |
|
5 |
-# ^^ seems "raw" is the only module that works on 2008 non-r2. win_command and win_shell both failed |
|
1 |
+# I dislike this but 2008 doesn't support the Win32_PowerPlan WMI provider |
|
2 |
+- name: get current plan details |
|
3 |
+ win_shell: | |
|
4 |
+ $plan_info = powercfg.exe /list |
|
5 |
+ ($plan_info | Select-String -Pattern '\(([\w\s]*)\) \*$').Matches.Groups[1].Value |
|
6 |
+ ($plan_info | Select-String -Pattern '\(([\w\s]*)\)$').Matches.Groups[1].Value |
|
7 |
+ register: plan_info |
|
6 | 8 |
|
7 |
-- name: check if module fails gracefully when older than 2008r2 |
|
8 |
- win_power_plan: |
|
9 |
- name: "high performance" |
|
10 |
- when: os_version.stdout_lines[0] is version('6.1','lt') |
|
11 |
- check_mode: yes |
|
12 |
- register: old_os_check |
|
13 |
- failed_when: old_os_check.msg != 'The win_power_plan Ansible module is only available on Server 2008r2 (6.1) and newer' |
|
9 |
+- set_fact: |
|
10 |
+ original_plan: '{{ plan_info.stdout_lines[0] }}' |
|
11 |
+ name: '{{ plan_info.stdout_lines[1] }}' |
|
14 | 12 |
|
15 | 13 |
- block: |
16 |
- - name: register inactive power plan to test with |
|
17 |
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan | ? {! $_.IsActive}).ElementName[0] |
|
18 |
- register: disabled_power_plan |
|
19 |
- changed_when: False |
|
20 |
- |
|
21 |
- - set_fact: |
|
22 |
- name: "{{ disabled_power_plan.stdout_lines[0] }}" |
|
23 |
- |
|
24 | 14 |
#Test that plan detects change is needed, but doesn't actually apply change |
25 | 15 |
- name: set power plan (check mode) |
26 | 16 |
win_power_plan: |
... | ... |
@@ -28,20 +18,17 @@ |
28 | 28 |
register: set_plan_check |
29 | 29 |
check_mode: yes |
30 | 30 |
|
31 |
-# - debug: |
|
32 |
-# var: set_plan_check |
|
33 |
- |
|
34 | 31 |
- name: get result of set power plan (check mode) |
35 |
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan -Filter "ElementName = '{{ name }}'").IsActive |
|
32 |
+ win_shell: (powercfg.exe /list | Select-String -Pattern '\({{ name }}\)').Line |
|
36 | 33 |
register: set_plan_check_result |
37 | 34 |
changed_when: False |
38 |
- |
|
35 |
+ |
|
39 | 36 |
# verify that the powershell check is showing the plan as still inactive on the system |
40 | 37 |
- name: assert setting plan (check mode) |
41 | 38 |
assert: |
42 | 39 |
that: |
43 | 40 |
- set_plan_check is changed |
44 |
- - set_plan_check_result.stdout == 'False\r\n' |
|
41 |
+ - not set_plan_check_result.stdout_lines[0].endswith('*') |
|
45 | 42 |
|
46 | 43 |
#Test that setting plan and that change is applied |
47 | 44 |
- name: set power plan |
... | ... |
@@ -50,7 +37,7 @@ |
50 | 50 |
register: set_plan |
51 | 51 |
|
52 | 52 |
- name: get result of set power plan |
53 |
- win_shell: (Get-CimInstance -Name root\cimv2\power -Class win32_PowerPlan -Filter "ElementName = '{{ name }}'").IsActive |
|
53 |
+ win_shell: (powercfg.exe /list | Select-String -Pattern '\({{ name }}\)').Line |
|
54 | 54 |
register: set_plan_result |
55 | 55 |
changed_when: False |
56 | 56 |
|
... | ... |
@@ -58,7 +45,7 @@ |
58 | 58 |
assert: |
59 | 59 |
that: |
60 | 60 |
- set_plan is changed |
61 |
- - set_plan_result.stdout == 'True\r\n' |
|
61 |
+ - set_plan_result.stdout_lines[0].endswith('*') |
|
62 | 62 |
|
63 | 63 |
#Test that plan doesn't apply change if it is already set |
64 | 64 |
- name: set power plan (idempotent) |
... | ... |
@@ -71,8 +58,7 @@ |
71 | 71 |
that: |
72 | 72 |
- set_plan_idempotent is not changed |
73 | 73 |
|
74 |
- when: os_version.stdout_lines[0] is version('6.1','ge') |
|
75 | 74 |
always: |
76 |
- - name: always change back plan to high performance when done testing |
|
75 |
+ - name: always change back plan to the original when done testing |
|
77 | 76 |
win_power_plan: |
78 |
- name: high performance |
|
77 |
+ name: '{{ original_plan }}' |
... | ... |
@@ -56,7 +56,6 @@ lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingPositionalParameters |
56 | 56 |
lib/ansible/modules/windows/win_pagefile.ps1 PSAvoidUsingWMICmdlet |
57 | 57 |
lib/ansible/modules/windows/win_pagefile.ps1 PSUseDeclaredVarsMoreThanAssignments |
58 | 58 |
lib/ansible/modules/windows/win_pagefile.ps1 PSUseSupportsShouldProcess |
59 |
-lib/ansible/modules/windows/win_power_plan.ps1 PSUseDeclaredVarsMoreThanAssignments |
|
60 | 59 |
lib/ansible/modules/windows/win_psmodule.ps1 PSAvoidUsingCmdletAliases |
61 | 60 |
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingCmdletAliases |
62 | 61 |
lib/ansible/modules/windows/win_rabbitmq_plugin.ps1 PSAvoidUsingInvokeExpression |