From 92c03dc3be15e089374c65021de9ffc51e85820f Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Fri, 11 Jan 2019 19:44:27 +0100 Subject: [PATCH 01/17] Changes to use powercfg.exe instead of WMI/CIM --- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 58 ++++++--------- .../MSFT_PowerPlan/MSFT_PowerPlan.schema.mof | 2 +- .../en-US/MSFT_PowerPlan.schema.mfl | 2 +- .../en-US/MSFT_PowerPlan.strings.psd1 | 1 - .../ComputerManagementDsc.Common.psm1 | 44 +++++++++++- .../ComputerManagementDsc.Common.Tests.ps1 | 70 +++++++++++++++++++ 6 files changed, 136 insertions(+), 41 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index d174ba8a..79cb49b9 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -23,7 +23,7 @@ $script:localizedData = Get-LocalizedData ` Specifies the resource is a single instance, the value must be 'Yes'. .PARAMETER Name - Specifies the name of the power plan to assign to the node. + Specifies the name or GUID of the power plan to assign to the node. .EXAMPLE Get-TargetResource -IsSingleInstance 'Yes' -Name 'High performance' @@ -46,21 +46,7 @@ function Get-TargetResource $Name ) - $getCimInstanceArguments = @{ - Name = 'root\cimv2\power' - Class = 'Win32_PowerPlan' - Filter = "ElementName = '$Name'" - } - - try - { - $plan = Get-CimInstance @getCimInstanceArguments - } - catch - { - New-InvalidOperationException ` - -Message ($script:localizedData.PowerPlanCimError -f $getCimInstanceArguments.Class) - } + $plan = Get-PowerPlans | Where-Object{($_.Name -eq $Name) -or ($_.Guid -eq $Name)} if ($plan) { @@ -94,7 +80,7 @@ function Get-TargetResource Specifies the resource is a single instance, the value must be 'Yes'. .PARAMETER Name - Specifies the name of the power plan to assign to the node. + Specifies the name or GUID of the power plan to assign to the node. .EXAMPLE Set-TargetResource -IsSingleInstance 'Yes' -Name 'High performance' @@ -118,30 +104,28 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.PowerPlanIsBeingActivated -f $Name) - $getCimInstanceArguments = @{ - Name = 'root\cimv2\power' - Class = 'Win32_PowerPlan' - Filter = "ElementName = '$Name'" - } + $plan = Get-PowerPlans | Where-Object{($_.Name -eq $Name) -or ($_.Guid -eq $Name)} - try - { - $plan = Get-CimInstance @getCimInstanceArguments - } - catch + if($plan) { - New-InvalidOperationException ` - -Message ($script:localizedData.PowerPlanCimError -f $getCimInstanceArguments.Class) - } - - try - { - $plan | Invoke-CimMethod -MethodName Activate + try + { + powercfg.exe /S $plan.Guid + if (-not $?) + { + Throw $error[0] + } + } + catch + { + New-InvalidOperationException ` + -Message ($script:localizedData.PowerPlanWasUnableToBeSet -f $Name, $_.Exception.Message) + } } - catch + else { New-InvalidOperationException ` - -Message ($script:localizedData.PowerPlanWasUnableToBeSet -f $Name, $_.Exception.Message) + -Message ($script:localizedData.PowerPlanNotFound -f $Name) } } @@ -153,7 +137,7 @@ function Set-TargetResource Specifies the resource is a single instance, the value must be 'Yes'. .PARAMETER Name - Specifies the name of the power plan to assign to the node. + Specifies the name or GUID of the power plan to assign to the node. .EXAMPLE Test-TargetResource -IsSingleInstance 'Yes' -Name 'High performance' diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof index 9d4a885c..41820d6f 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.schema.mof @@ -2,6 +2,6 @@ class MSFT_PowerPlan : OMI_BaseResource { [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'."), ValueMap{"Yes"}, Values{"Yes"}] String IsSingleInstance; - [Required, Description("The name of the power plan to activate.")] String Name; + [Required, Description("The name or GUID of the power plan to activate.")] String Name; [Read, Description("Determines if the power plan is active.")] Boolean IsActive; }; diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.schema.mfl b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.schema.mfl index 9ad6c2a6..3b208b85 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.schema.mfl +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.schema.mfl @@ -2,5 +2,5 @@ class MSFT_PowerPlan : OMI_BaseResource { [Key, Description("Specifies the resource is a single instance, the value must be 'Yes'.") : Amended] String IsSingleInstance; - [Description("The name of the power plan to activate.") : Amended] String Name; + [Description("The name or GUID of the power plan to activate.") : Amended] String Name; }; diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 index 8d673bfc..951d8789 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 @@ -7,5 +7,4 @@ ConvertFrom-StringData @' PowerPlanIsBeingActivated = Activating power plan '{0}'. PowerPlanIsBeingValidated = Validating power plan '{0}'. PowerPlanWasUnableToBeSet = Unable to set the power plan '{0}' to the active plan. Error message: {1} - PowerPlanCimError = Could not get the Common Information Model (CIM) instances of class '{0}'. '@ diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 49d6d09e..1c3a0a4b 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -491,6 +491,47 @@ function Set-TimeZoneUsingDotNet [Microsoft.PowerShell.TimeZone.TimeZone]::Set($TimeZoneId) } # function Set-TimeZoneUsingDotNet +<# + .SYNOPSIS + This function gets all power plans/schemes available on the machine using the powercfg.exe utility and returns a custom object for each plan + It exists because the WMI classes are not available on all platforms (e.g on Server 2012 R2 core or nano server) + + .NOTES + This function is used by the PowerPlan resource +#> +function Get-PowerPlans { + [CmdletBinding()] + param + ( + ) + + $powercfgOutPut = Invoke-Expression -Command 'powercfg.exe /l' + + $allPlans = @() + + foreach($line in $powercfgOutPut) + { + if($line -match "^.*?:[ ]*(?'guid'.*?)[ ]*\((?'name'.*?)\)") + { + $plan = [PSCustomObject]@{ + Name = [String]$Matches.name + Guid = [Guid]$Matches.guid + IsActive = $false + } + + if($line -match "\*$") + { + $plan.IsActive = $true + } + + $allPlans += $plan + } + } + + return $allPlans +} + + Export-ModuleMember -Function ` Test-DscParameterState, ` Test-DscObjectHasProperty, ` @@ -498,4 +539,5 @@ Export-ModuleMember -Function ` Get-TimeZoneId, ` Test-TimeZoneId, ` Set-TimeZoneId, ` - Set-TimeZoneUsingDotNet + Set-TimeZoneUsingDotNet, ` + Get-PowerPlans diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 3b42866a..651d0317 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -690,6 +690,76 @@ try } } } + + Describe 'ComputerManagementDsc.Common\Get-PowerPlans' { + Context 'When only the "Balanced" power plan is available (default on Windows 10 for example)' { + Mock ` + -CommandName Invoke-Expression ` + -MockWith { + return @( + "Existing Power Schemes (* Active)" + "-----------------------------------" + "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" + ) + } ` + -ParameterFilter {$Command -eq 'powercfg.exe /l'} + + It 'Should not throw exception' { + {Get-PowerPlans} | Should -Not -Throw + } + + It 'Should return an object of type PSCustomObject' { + Get-PowerPlans | Should -BeOfType [PSCustomObject] + } + + It 'Should return only one object' { + Get-PowerPlans | Should -HaveCount 1 + } + + It 'Should be the active plan (property "IsActive" of the returned object should be $true)' { + (Get-PowerPlans).IsActive | Should -Be $true + } + + It 'Should return a Object with the property "Name" with the value "Balanced"' { + (Get-PowerPlans).Name | Should -Be 'Balanced' + } + } + + Context 'When three power plans are available (default on server OS)' { + Mock ` + -CommandName Invoke-Expression ` + -MockWith { + return @( + "Existing Power Schemes (* Active)" + "-----------------------------------" + "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" + "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance)" + "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" + ) + } ` + -ParameterFilter {$Command -eq 'powercfg.exe /l'} + + It 'Should not throw exception' { + {Get-PowerPlans} | Should -Not -Throw + } + + It 'Should return an object of type PSCustomObject' { + Get-PowerPlans | Should -BeOfType [PSCustomObject] + } + + It 'Should return three objects' { + Get-PowerPlans | Should -HaveCount 3 + } + + It 'Should be only have one plan marked as active' { + Get-PowerPlans | Where-Object{$_.IsActive -eq $true} | Should -HaveCount 1 + } + + It 'Should be have the plan "Balanced" set as active' { + (Get-PowerPlans | Where-Object{$_.IsActive -eq $true}).Name | Should -Be 'Balanced' + } + } + } } } finally From bb6219a67e67a65f0096a8e5cce2ebd9507ea679 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Fri, 11 Jan 2019 22:00:59 +0100 Subject: [PATCH 02/17] Update tests --- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 6 +- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 284 ++++++++++++------ 2 files changed, 187 insertions(+), 103 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 79cb49b9..00f92f90 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -110,10 +110,10 @@ function Set-TargetResource { try { - powercfg.exe /S $plan.Guid - if (-not $?) + Invoke-Expression -Command "powercfg.exe /S $($plan.Guid)" -ErrorVariable powerCfgError + if ($powerCfgError) { - Throw $error[0] + Throw $powerCfgError } } catch diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index b40b2b96..48020ae8 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -32,31 +32,58 @@ try $LocalizedData } - Describe "$($script:DSCResourceName)\Get-TargetResource" { - BeforeEach { - $testParameters = @{ - IsSingleInstance = 'Yes' - Name = 'High performance' - Verbose = $true - } + $testCases =@( + #Power plan as name specified + @{ + Type = 'Name' + Name = 'High performance' + }, + + #Power plan as Guid specified + @{ + Type = 'Guid' + Name = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } + ) + Describe "$($script:DSCResourceName)\Get-TargetResource" { Context 'When the system is in the desired present state' { BeforeEach { Mock ` - -CommandName Get-CimInstance ` + -CommandName Get-PowerPlans ` -MockWith { - return New-Object Object | - Add-Member -MemberType NoteProperty -Name IsActive -Value $true -PassThru -Force - } ` + return @( + [PSCustomObject]@{ + Name = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + IsActive = $true + }, + [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Power saver' + Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + IsActive = $false + } + ) + } ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should return the same values as passed as parameters' { - $result = Get-TargetResource @testParameters + It 'Should return the same values as passed as parameters (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + + $result = Get-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose $result.IsSingleInstance | Should -Be 'Yes' - $result.Name | Should -Be $testParameters.Name + $result.Name | Should -Be $Name $result.IsActive | Should -Be $true } } @@ -64,53 +91,70 @@ try Context 'When the system is not in the desired present state' { BeforeEach { Mock ` - -CommandName Get-CimInstance ` + -CommandName Get-PowerPlans ` -MockWith { - return New-Object Object | - Add-Member -MemberType NoteProperty -Name IsActive -Value $false -PassThru -Force - } ` + return @( + [PSCustomObject]@{ + Name = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Power saver' + Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + IsActive = $true + } + ) + } ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should return an inactive plan' { - $result = Get-TargetResource @testParameters + It 'Should return an inactive plan (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + + $result = Get-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose $result.IsSingleInstance | Should -Be 'Yes' - $result.Name | Should -Be $testParameters.Name + $result.Name | Should -Be $Name $result.IsActive | Should -Be $false } } - Context 'When the Get-CimInstance cannot retrive information about power plans' { + Context 'When the preferred plan does not exist' { BeforeEach { Mock ` - -CommandName Get-CimInstance ` - -MockWith { throw } ` + -CommandName Get-PowerPlans ` + -MockWith { + return [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + } + } ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should throw the expected error' { - $errorRecord = Get-InvalidOperationRecord ` - -Message ($LocalizedData.PowerPlanCimError -f 'Win32_PowerPlan') - - { Get-TargetResource @testParameters } | Should -Throw $errorRecord - } - } + It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { - Context 'When the preferred plan does not exist' { - BeforeEach { - Mock ` - -CommandName Get-CimInstance ` - -ModuleName $script:DSCResourceName ` - -Verifiable - } + Param( + [String] + $Name + ) - It 'Should throw the expected error' { $errorRecord = Get-InvalidOperationRecord ` - -Message ($LocalizedData.PowerPlanNotFound -f $testParameters.Name) + -Message ($LocalizedData.PowerPlanNotFound -f $Name) - { Get-TargetResource @testParameters } | Should -Throw $errorRecord + { Get-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose } | Should -Throw $errorRecord } } @@ -119,67 +163,73 @@ try Describe "$($script:DSCResourceName)\Set-TargetResource" { BeforeEach { - $testParameters = @{ - IsSingleInstance = 'Yes' - Name = 'High performance' - Verbose = $true - } - Mock ` - -CommandName Invoke-CimMethod ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Get-PowerPlans ` + -MockWith { + return @( + [PSCustomObject]@{ + Name = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Power saver' + Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + IsActive = $true + } + ) + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable Mock ` - -CommandName Get-CimInstance ` - -MockWith { - return New-Object ` - -TypeName Microsoft.Management.Infrastructure.CimInstance ` - -ArgumentList @('Win32_PowerPlan', 'dummyNamespace') - } ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Invoke-Expression ` + -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` + -ModuleName $script:DSCResourceName ` + -Verifiable } Context 'When the system is not in the desired present state' { - It 'Should call the mocked function Invoke-CimMethod exactly once' { - Set-TargetResource @testParameters + It 'Should call powercfg.exe /S only once (power plan specified as )' -TestCases $testCases { - Assert-MockCalled -CommandName Invoke-CimMethod -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName - } - } + Param( + [String] + $Name + ) - Context 'When the Get-CimInstance cannot retrive information about power plans' { - BeforeEach { - Mock ` - -CommandName Get-CimInstance ` - -MockWith { throw } ` - -ModuleName $script:DSCResourceName ` - -Verifiable - } - - It 'Should throw the expected error' { - $errorRecord = Get-InvalidOperationRecord ` - -Message ($LocalizedData.PowerPlanCimError -f 'Win32_PowerPlan') + Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose - { Set-TargetResource @testParameters } | Should -Throw $errorRecord + Assert-MockCalled -CommandName Invoke-Expression -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName } } - Context 'When the Invoke-CimMethod throws an error' { + + Context 'When the powercfg.exe throws an error' { BeforeEach { Mock ` - -CommandName Invoke-CimMethod ` - -MockWith { throw 'Failed to set value' } ` + -CommandName Invoke-Expression ` + -MockWith { Throw 'powercfg : Invalid Parameters -- try "/?" for help' } ` + -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should catch the correct error thrown by Invoke-CimMethod' { + It 'Should catch the correct error thrown (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + $errorRecord = Get-InvalidOperationRecord ` - -Message ($LocalizedData.PowerPlanWasUnableToBeSet -f $testParameters.Name, 'Failed to set value') + -Message ($LocalizedData.PowerPlanWasUnableToBeSet -f $Name, 'powercfg : Invalid Parameters -- try "/?" for help') - { Set-TargetResource @testParameters } | Should -Throw $errorRecord + { Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose} | Should -Throw $errorRecord } } @@ -187,45 +237,79 @@ try } Describe "$($script:DSCResourceName)\Test-TargetResource" { - BeforeEach { - $testParameters = @{ - IsSingleInstance = 'Yes' - Name = 'High performance' - Verbose = $true - } - } - Context 'When the system is in the desired present state' { BeforeEach { Mock ` - -CommandName Get-CimInstance ` + -CommandName Get-PowerPlans ` -MockWith { - return New-Object Object | - Add-Member -MemberType NoteProperty -Name IsActive -Value $true -PassThru -Force + return @( + [PSCustomObject]@{ + Name = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + IsActive = $true + }, + [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Power saver' + Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + IsActive = $false + } + ) } ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should return the the state as present ($true)' { - Test-TargetResource @testParameters | Should -Be $true + It 'Should return the the state as present ($true) (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + + Test-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose | Should -Be $true } } Context 'When the system is not in the desired state' { BeforeEach { Mock ` - -CommandName Get-CimInstance ` + -CommandName Get-PowerPlans ` -MockWith { - return New-Object Object | - Add-Member -MemberType NoteProperty -Name IsActive -Value $false -PassThru -Force + return @( + [PSCustomObject]@{ + Name = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + }, + [PSCustomObject]@{ + Name = 'Power saver' + Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + IsActive = $true + } + ) } ` -ModuleName $script:DSCResourceName ` -Verifiable } - It 'Should return the the state as absent ($false)' { - Test-TargetResource @testParameters | Should -Be $false + It 'Should return the the state as absent ($false) (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + + Test-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose | Should -Be $false } } From ba8c22bcb00041d49cac6afe11a7bc14303d3394 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Fri, 11 Jan 2019 22:22:49 +0100 Subject: [PATCH 03/17] Update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec71f0b..50a867b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # Versions ## Unreleased - +- PowerPlan: + - Added support to specify the desired power plan either as name or guid. Fixes [Issue #59](https://github.com/PowerShell/ComputerManagementDsc/issues/59) + - Changed the resource so it uses powercfg.exe instead of WMI/CIM to get und set power plans because WMI approach is not always working on all OS versions. + (The WMI approach does for example not work on Windows Server 2012 R2 Core, Nano Server, Windows Server 2019 and Windows 10) + Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) ## 6.1.0.0 - Updated LICENSE file to match the Microsoft Open Source Team standard. From a882d9b4dec17ce7cede19f96ab048c9dea27deb Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sat, 12 Jan 2019 16:04:34 +0100 Subject: [PATCH 04/17] Fix markdown errors in changelog --- CHANGELOG.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50a867b6..9af46041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,15 @@ # Versions ## Unreleased + - PowerPlan: - - Added support to specify the desired power plan either as name or guid. Fixes [Issue #59](https://github.com/PowerShell/ComputerManagementDsc/issues/59) - - Changed the resource so it uses powercfg.exe instead of WMI/CIM to get und set power plans because WMI approach is not always working on all OS versions. - (The WMI approach does for example not work on Windows Server 2012 R2 Core, Nano Server, Windows Server 2019 and Windows 10) - Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) + - Added support to specify the desired power plan either as name or guid. + Fixes [Issue #59](https://github.com/PowerShell/ComputerManagementDsc/issues/59) + - Changed the resource so it uses powercfg.exe instead of WMI/CIM. + (Workaround fo rServer 2012R2 Core, Nano Server, Server 2019 and Windows 10) + Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) + and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) + ## 6.1.0.0 - Updated LICENSE file to match the Microsoft Open Source Team standard. From 87c9843c67f17d0a184899b675985dcfcdf1b0b6 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sat, 12 Jan 2019 16:29:11 +0100 Subject: [PATCH 05/17] Remove -PesterMaximumVersion 4.0.8 from appveyor.yml --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 61454424..1ab4075a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ install: $moduleName = 'ComputerManagementDsc' $mainModuleFolder = "Modules\$moduleName" Import-Module "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" - Invoke-AppveyorInstallTask -PesterMaximumVersion 4.0.8 + Invoke-AppveyorInstallTask #---------------------------------# # build configuration # From e06288977db00c9794ae3e74d5912f42d5008f0e Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sat, 12 Jan 2019 18:07:24 +0100 Subject: [PATCH 06/17] Add testcase for Set function when plan does not exist --- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index 48020ae8..22ca674f 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -189,7 +189,7 @@ try Mock ` -CommandName Invoke-Expression ` - -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` + -ParameterFilter {$Command -eq 'powercfg.exe /S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'} ` -ModuleName $script:DSCResourceName ` -Verifiable } @@ -208,17 +208,36 @@ try } } - - Context 'When the powercfg.exe throws an error' { + Context 'When the preferred plan does not exist' { BeforeEach { Mock ` - -CommandName Invoke-Expression ` - -MockWith { Throw 'powercfg : Invalid Parameters -- try "/?" for help' } ` - -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` + -CommandName Get-PowerPlans ` + -MockWith { + return [PSCustomObject]@{ + Name = 'Balanced' + Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + IsActive = $false + } + } ` -ModuleName $script:DSCResourceName ` -Verifiable } + It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { + + Param( + [String] + $Name + ) + + $errorRecord = Get-InvalidOperationRecord ` + -Message ($LocalizedData.PowerPlanNotFound -f $Name) + + { Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose } | Should -Throw $errorRecord + } + } + + Context 'When the powercfg.exe throws an invalid parameters error' { It 'Should catch the correct error thrown (power plan specified as )' -TestCases $testCases { Param( @@ -226,6 +245,13 @@ try $Name ) + Mock ` + -CommandName Invoke-Expression ` + -MockWith { Throw 'powercfg : Invalid Parameters -- try "/?" for help' } ` + -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` + -ModuleName $script:DSCResourceName ` + -Verifiable + $errorRecord = Get-InvalidOperationRecord ` -Message ($LocalizedData.PowerPlanWasUnableToBeSet -f $Name, 'powercfg : Invalid Parameters -- try "/?" for help') From 533e7b3399d7c527a5bda61bea06bbaa84f8ec3f Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Tue, 22 Jan 2019 01:34:34 +0100 Subject: [PATCH 07/17] Add Get-PowerPlan (replaces Get-PowerPlans) and Set-PowerPlan --- CHANGELOG.md | 8 +- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 20 +- .../en-US/MSFT_PowerPlan.strings.psd1 | 1 - .../ComputerManagementDsc.Common.psm1 | 75 +++- .../ComputerManagementDsc.Common.strings.psd1 | 1 + .../ComputerManagementDsc.Common.Tests.ps1 | 338 +++++++++++++++--- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 136 ++----- 7 files changed, 399 insertions(+), 180 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af46041..892d9e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,10 +5,10 @@ - PowerPlan: - Added support to specify the desired power plan either as name or guid. Fixes [Issue #59](https://github.com/PowerShell/ComputerManagementDsc/issues/59) - - Changed the resource so it uses powercfg.exe instead of WMI/CIM. - (Workaround fo rServer 2012R2 Core, Nano Server, Server 2019 and Windows 10) - Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) - and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) + - Changed the resource so it uses powercfg.exe instead of WMI/CIM + (Workaround fo rServer 2012R2 Core, Nano Server, Server 2019 and Windows 10). + Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) + and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) ## 6.1.0.0 diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 00f92f90..1da716ae 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -46,7 +46,7 @@ function Get-TargetResource $Name ) - $plan = Get-PowerPlans | Where-Object{($_.Name -eq $Name) -or ($_.Guid -eq $Name)} + $plan = Get-PowerPlan -Name $Name if ($plan) { @@ -104,28 +104,16 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.PowerPlanIsBeingActivated -f $Name) - $plan = Get-PowerPlans | Where-Object{($_.Name -eq $Name) -or ($_.Guid -eq $Name)} + $plan = Get-PowerPlan -Name $Name if($plan) { - try - { - Invoke-Expression -Command "powercfg.exe /S $($plan.Guid)" -ErrorVariable powerCfgError - if ($powerCfgError) - { - Throw $powerCfgError - } - } - catch - { - New-InvalidOperationException ` - -Message ($script:localizedData.PowerPlanWasUnableToBeSet -f $Name, $_.Exception.Message) - } + Set-PowerPlan -Guid $plan.Guid } else { New-InvalidOperationException ` - -Message ($script:localizedData.PowerPlanNotFound -f $Name) + -Message ($script:localizedData.PowerPlanNotFound -f $Name) } } diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 index 951d8789..137cba74 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/en-US/MSFT_PowerPlan.strings.psd1 @@ -6,5 +6,4 @@ ConvertFrom-StringData @' PowerPlanNotFound = Unable to find the power plan '{0}'. PowerPlanIsBeingActivated = Activating power plan '{0}'. PowerPlanIsBeingValidated = Validating power plan '{0}'. - PowerPlanWasUnableToBeSet = Unable to set the power plan '{0}' to the active plan. Error message: {1} '@ diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 1c3a0a4b..69178526 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -493,24 +493,48 @@ function Set-TimeZoneUsingDotNet <# .SYNOPSIS - This function gets all power plans/schemes available on the machine using the powercfg.exe utility and returns a custom object for each plan - It exists because the WMI classes are not available on all platforms (e.g on Server 2012 R2 core or nano server) + This function gets a power plans/schemes specified by its friendly name or GUID. + It returns a custom object with the properties of the power plan or + nothing if the powerplan does not exist on the computer. + + .PARAMETER Name + Friendly name or GUID of a power plan to get. .NOTES - This function is used by the PowerPlan resource + The powercfg.exe utility is used here because the Win32_PowerPLan class has + issues on some platforms (e.g Server 2012 R2 core or Nano Server). + This function is used by the PowerPlan resource. #> -function Get-PowerPlans { +function Get-PowerPlan { [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name ) - $powercfgOutPut = Invoke-Expression -Command 'powercfg.exe /l' + # Set to stop so that the errors from powercfg.exe are terminating + $ErrorActionPreference = 'Stop' + + try { + $powercfgOutPut = & powercfg.exe /L + } + catch { + New-InvalidOperationException -ErrorRecord $_ + } + + if(($null -eq $powercfgOutPut) -or ('' -eq $powercfgOutPut)) + { + New-InvalidOperationException -Message $script:localizedData.UnableToGetPowerPlans + } $allPlans = @() foreach($line in $powercfgOutPut) { + if($line -match "^.*?:[ ]*(?'guid'.*?)[ ]*\((?'name'.*?)\)") { $plan = [PSCustomObject]@{ @@ -528,9 +552,45 @@ function Get-PowerPlans { } } - return $allPlans + $selectedPlan = $allPlans | Where-Object -FilterScript { + ($_.Name -eq $Name) -or + ($_.Guid -eq $Name) + } + + $selectedPlan } +<# + .SYNOPSIS + This function activates the desired power plan (specified by its GUID). + + .PARAMETER Guid + GUID of a power plan to activate. + + .NOTES + The powercfg.exe utility is used here because the Win32_PowerPLan class has + issues on some platforms (e.g Server 2012 R2 core or Nano Server). + This function is used by the PowerPlan resource. +#> +function Set-PowerPlan { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Guid] + $Guid + ) + + # Set to stop so that the errors from powercfg.exe are terminating + $ErrorActionPreference = 'Stop' + + try { + & powercfg.exe /S $Guid + } + catch { + New-InvalidOperationException -ErrorRecord $_ + } +} Export-ModuleMember -Function ` Test-DscParameterState, ` @@ -540,4 +600,5 @@ Export-ModuleMember -Function ` Test-TimeZoneId, ` Set-TimeZoneId, ` Set-TimeZoneUsingDotNet, ` - Get-PowerPlans + Get-PowerPlan, ` + Set-PowerPlan diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 index d3721f36..a47d45df 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 @@ -17,4 +17,5 @@ ConvertFrom-StringData @' SettingTimeZoneMessage = Setting time zone to '{0}' using {1}. TimeZoneUpdatedMessage = Time zone has been updated to '{0}'. AddingSetTimeZoneDotNetTypeMessage = Adding .NET Set time zone Type. + UnableToGetPowerPlans = Unable to get available power plans with powercfg.exe /l. Unexpected empty output from powercfg.exe. '@ diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 651d0317..6921d0ce 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -691,74 +691,326 @@ try } } - Describe 'ComputerManagementDsc.Common\Get-PowerPlans' { - Context 'When only the "Balanced" power plan is available (default on Windows 10 for example)' { + Describe 'ComputerManagementDsc.Common\Get-PowerPlan' { + + $testCases =@( + # Power plan as name specified + @{ + Type = 'Friendly Name' + Name = 'High performance' + }, + # Power plan as Guid specified + @{ + Type = 'Guid' + Name = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + ) + + Context 'When the specified power plan is not available on the computer' { Mock ` - -CommandName Invoke-Expression ` - -MockWith { - return @( - "Existing Power Schemes (* Active)" - "-----------------------------------" - "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" - ) - } ` - -ParameterFilter {$Command -eq 'powercfg.exe /l'} + -CommandName powercfg.exe ` + -MockWith { + return @( + "Existing Power Schemes (* Active)" + "-----------------------------------" + "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" + ) + } ` + -Verifiable - It 'Should not throw exception' { - {Get-PowerPlans} | Should -Not -Throw + It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + {Get-PowerPlan -Name $Name} | Should -Not -Throw } - It 'Should return an object of type PSCustomObject' { - Get-PowerPlans | Should -BeOfType [PSCustomObject] + It 'Should return nothing (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + Get-PowerPlan -Name $Name | Should -HaveCount 0 + Get-PowerPlan -Name $Name | Should -Be $null } - It 'Should return only one object' { - Get-PowerPlans | Should -HaveCount 1 + It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { + param + ( + [String] + $Name + ) + + Get-PowerPlan -Name $Name + Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It } - It 'Should be the active plan (property "IsActive" of the returned object should be $true)' { - (Get-PowerPlans).IsActive | Should -Be $true + Assert-VerifiableMock + } + + Context 'When the specified power plan is available on the computer and not active' { + + Mock ` + -CommandName powercfg.exe ` + -MockWith { + return @( + "Existing Power Schemes (* Active)" + "-----------------------------------" + "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" + "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance)" + "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" + ) + } ` + -Verifiable + + It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + {Get-PowerPlan -Name $Name} | Should -Not -Throw } - It 'Should return a Object with the property "Name" with the value "Balanced"' { - (Get-PowerPlans).Name | Should -Be 'Balanced' + It 'Should return one object of type PSCustomObject (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $powerPlan = Get-PowerPlan -Name $Name + $powerPlan | Should -BeOfType [PSCustomObject] + $powerPlan | Should -HaveCount 1 } + + It 'Should not be the active plan (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $powerPlan = Get-PowerPlan -Name $Name + $powerPlan.IsActive | Should -Be $false + } + + It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { + param + ( + [String] + $Name + ) + + Get-PowerPlan -Name $Name + Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It + } + + Assert-VerifiableMock } - Context 'When three power plans are available (default on server OS)' { + Context 'When the specified power plan is available on the computer and is active' { + Mock ` - -CommandName Invoke-Expression ` - -MockWith { - return @( - "Existing Power Schemes (* Active)" - "-----------------------------------" - "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" - "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance)" - "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" - ) - } ` - -ParameterFilter {$Command -eq 'powercfg.exe /l'} + -CommandName powercfg.exe ` + -MockWith { + return @( + "Existing Power Schemes (* Active)" + "-----------------------------------" + "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced)" + "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance) *" + "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" + ) + } ` + -Verifiable - It 'Should not throw exception' { - {Get-PowerPlans} | Should -Not -Throw + It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + {Get-PowerPlan -Name $Name} | Should -Not -Throw + } + + It 'Should return one object of type PSCustomObject (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $powerPlan = Get-PowerPlan -Name $Name + $powerPlan | Should -BeOfType [PSCustomObject] + $powerPlan | Should -HaveCount 1 + } + + It 'Should not be the active plan (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $powerPlan = Get-PowerPlan -Name $Name + $powerPlan.IsActive | Should -Be $true } - It 'Should return an object of type PSCustomObject' { - Get-PowerPlans | Should -BeOfType [PSCustomObject] + It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { + param + ( + [String] + $Name + ) + + Get-PowerPlan -Name $Name + Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It } - It 'Should return three objects' { - Get-PowerPlans | Should -HaveCount 3 + Assert-VerifiableMock + } + + Context 'When the powercfg.exe throws an invalid parameters error' { + + $errorRecord = [System.Management.Automation.ErrorRecord]::new( + [System.Management.Automation.RemoteException]::new('Invalid Parameters -- try "/?" for help'), + 'NativeCommandError', + 'NotSpecified', + 'Invalid Parameters -- try "/?" for help' + ) + + Mock ` + -CommandName powercfg.exe ` + -MockWith { + throw $errorRecord + } ` + -Verifiable + + It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $invalidOperationRecord = Get-InvalidOperationRecord ` + -ErrorRecord $errorRecord + + {Get-PowerPlan -Name $Name} | Should -Throw $invalidOperationRecord } - It 'Should be only have one plan marked as active' { - Get-PowerPlans | Where-Object{$_.IsActive -eq $true} | Should -HaveCount 1 + Assert-VerifiableMock + } + + Context 'When powercfg.exe /L returns nothing' { + + Mock ` + -CommandName powercfg.exe ` + -Verifiable + + It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $invalidOperationRecord = Get-InvalidOperationRecord ` + -Message $localizedData.UnableToGetPowerPlans + + {Get-PowerPlan -Name $Name} | Should -Throw $invalidOperationRecord } - It 'Should be have the plan "Balanced" set as active' { - (Get-PowerPlans | Where-Object{$_.IsActive -eq $true}).Name | Should -Be 'Balanced' + Assert-VerifiableMock + } + } + + Describe 'ComputerManagementDsc.Common\Set-PowerPlan' { + Context 'When the specified power plan cloud be activated successfully' { + + Mock ` + -CommandName powercfg.exe ` + -Verifiable + + [Guid]$powerPlanGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + + It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + {Set-PowerPlan -Guid $powerPlanGuid} | Should -Not -Throw } + + It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + Set-PowerPlan -Guid $powerPlanGuid + Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It + } + + Assert-VerifiableMock } + + Context 'When the powercfg.exe throws an error while setting the specified power plan' { + $errorRecord = [System.Management.Automation.ErrorRecord]::new( + [System.Management.Automation.RemoteException]::new('Invalid Parameters -- try "/?" for help'), + 'NativeCommandError', + 'NotSpecified', + 'Invalid Parameters -- try "/?" for help' + ) + + [Guid]$powerPlanGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + + Mock ` + -CommandName powercfg.exe ` + -MockWith { + throw $errorRecord + } ` + -Verifiable + + It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + $invalidOperationRecord = Get-InvalidOperationRecord ` + -ErrorRecord $errorRecord + + {Set-PowerPlan -Guid $powerPlanGuid} | Should -Throw $invalidOperationRecord + } + + Assert-VerifiableMock + } + } } } diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index 22ca674f..464a812d 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -33,13 +33,13 @@ try } $testCases =@( - #Power plan as name specified + # Power plan as name specified @{ Type = 'Name' Name = 'High performance' }, - #Power plan as Guid specified + # Power plan as Guid specified @{ Type = 'Guid' Name = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' @@ -50,23 +50,13 @@ try Context 'When the system is in the desired present state' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` + -CommandName Get-PowerPlan ` -MockWith { return @( [PSCustomObject]@{ Name = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' IsActive = $true - }, - [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Power saver' - Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - IsActive = $false } ) } ` @@ -76,7 +66,8 @@ try It 'Should return the same values as passed as parameters (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) @@ -91,23 +82,13 @@ try Context 'When the system is not in the desired present state' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` + -CommandName Get-PowerPlan ` -MockWith { return @( [PSCustomObject]@{ Name = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Power saver' - Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - IsActive = $true } ) } ` @@ -117,7 +98,8 @@ try It 'Should return an inactive plan (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) @@ -132,21 +114,15 @@ try Context 'When the preferred plan does not exist' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` - -MockWith { - return [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - } - } ` + -CommandName Get-PowerPlan ` -ModuleName $script:DSCResourceName ` -Verifiable } It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) @@ -156,31 +132,20 @@ try { Get-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose } | Should -Throw $errorRecord } - } Assert-VerifiableMock } - + } Describe "$($script:DSCResourceName)\Set-TargetResource" { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` + -CommandName Get-PowerPlan ` -MockWith { return @( [PSCustomObject]@{ Name = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Power saver' - Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - IsActive = $true } ) } ` @@ -188,44 +153,38 @@ try -Verifiable Mock ` - -CommandName Invoke-Expression ` - -ParameterFilter {$Command -eq 'powercfg.exe /S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'} ` + -CommandName Set-PowerPlan ` -ModuleName $script:DSCResourceName ` -Verifiable } Context 'When the system is not in the desired present state' { - It 'Should call powercfg.exe /S only once (power plan specified as )' -TestCases $testCases { + It 'Should call Set-PowerPlan once (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose - Assert-MockCalled -CommandName Invoke-Expression -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName + Assert-MockCalled -CommandName Set-PowerPlan -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName } } Context 'When the preferred plan does not exist' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` - -MockWith { - return [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - } - } ` + -CommandName Get-PowerPlan ` -ModuleName $script:DSCResourceName ` -Verifiable } It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) @@ -237,52 +196,19 @@ try } } - Context 'When the powercfg.exe throws an invalid parameters error' { - It 'Should catch the correct error thrown (power plan specified as )' -TestCases $testCases { - - Param( - [String] - $Name - ) - - Mock ` - -CommandName Invoke-Expression ` - -MockWith { Throw 'powercfg : Invalid Parameters -- try "/?" for help' } ` - -ParameterFilter {$Command -like 'powercfg.exe /S *'} ` - -ModuleName $script:DSCResourceName ` - -Verifiable - - $errorRecord = Get-InvalidOperationRecord ` - -Message ($LocalizedData.PowerPlanWasUnableToBeSet -f $Name, 'powercfg : Invalid Parameters -- try "/?" for help') - - { Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose} | Should -Throw $errorRecord - } - } - Assert-VerifiableMock } - Describe "$($script:DSCResourceName)\Test-TargetResource" { Context 'When the system is in the desired present state' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` + -CommandName Get-PowerPlan ` -MockWith { return @( [PSCustomObject]@{ Name = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' IsActive = $true - }, - [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Power saver' - Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - IsActive = $false } ) } ` @@ -292,7 +218,8 @@ try It 'Should return the the state as present ($true) (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) @@ -304,23 +231,13 @@ try Context 'When the system is not in the desired state' { BeforeEach { Mock ` - -CommandName Get-PowerPlans ` + -CommandName Get-PowerPlan ` -MockWith { return @( [PSCustomObject]@{ Name = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Balanced' - Guid = [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - IsActive = $false - }, - [PSCustomObject]@{ - Name = 'Power saver' - Guid = [Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - IsActive = $true } ) } ` @@ -330,7 +247,8 @@ try It 'Should return the the state as absent ($false) (power plan specified as )' -TestCases $testCases { - Param( + param + ( [String] $Name ) From 34c42d9c4fd905e4a3c40eec2fe43a6a04382656 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Tue, 22 Jan 2019 15:31:53 +0100 Subject: [PATCH 08/17] Add functions to quey and set power plans with native Windows APIs --- .../ComputerManagementDsc.Common.psm1 | 310 +++++++++++++++--- .../ComputerManagementDsc.Common.strings.psd1 | 5 +- 2 files changed, 273 insertions(+), 42 deletions(-) diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 69178526..346ed751 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -493,102 +493,329 @@ function Set-TimeZoneUsingDotNet <# .SYNOPSIS - This function gets a power plans/schemes specified by its friendly name or GUID. - It returns a custom object with the properties of the power plan or - nothing if the powerplan does not exist on the computer. + This function gets all available power plans/schemes or + a specific power plans specified by its friendly name. + The function returns an array with one or more hashtable(s) + containing the friendly name and GUID of the power plan(s). - .PARAMETER Name - Friendly name or GUID of a power plan to get. + .PARAMETER PowerPlanFriendlyName + Friendly name a power plan to get. + When not specified the function will return all available power plans. .NOTES - The powercfg.exe utility is used here because the Win32_PowerPLan class has - issues on some platforms (e.g Server 2012 R2 core or Nano Server). + This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs + because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. + (e.g Server 2012 R2 core or Nano Server). This function is used by the PowerPlan resource. #> function Get-PowerPlan { [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String] - $Name + $PowerPlanFriendlyName ) - # Set to stop so that the errors from powercfg.exe are terminating $ErrorActionPreference = 'Stop' - try { - $powercfgOutPut = & powercfg.exe /L + # Define C# signature of PowerEnumerate function + $powerEnumerateDefinition = @' + [DllImport("powrprof.dll", CharSet = CharSet.Unicode)] + public static extern uint PowerEnumerate( + IntPtr RootPowerKey, + IntPtr SchemeGuid, + IntPtr SubGroupOfPowerSetting, + int AccessFlags, + uint Index, + IntPtr rBuffer, + ref uint BufferSize + ); +'@ + + # Create powerprof object with the static method PowerEnumerate + $powrprof = Add-Type ` + -MemberDefinition $powerEnumerateDefinition ` + -Name 'powrprof' ` + -Namespace 'Win32Functions' ` + -PassThru + + $index = 0 + $returnCode = 0 + $guids = @() + + # PowerEnumerate returns the GUID of the powerplan(s). Guid = 16 Bytes. + $bufferSize = 16 + + # The PowerEnumerate function returns only one guid at a time. + # So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. + while ($returnCode -eq 0) + { + try { + # Allocate buffer + $readBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([int]$bufferSize) + + # Get Guid of the power plan using the native PowerEnumerate function + $returnCode = $powrprof::PowerEnumerate([System.IntPtr]::Zero,[System.IntPtr]::Zero,[System.IntPtr]::Zero,16,$index,$readBuffer,[ref]$bufferSize) + + # Create a managed Guid object form the unmanaged memory block + $planGuid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($readBuffer, [System.Type][guid]) + + # ReturnCode 259 from the native function means no more data + if ($ReturnCode -eq 259) { + break + } + + # Check for non 0 return codes / errors form the native function + if ($returnCode -ne 0) + { + # Create a Win32Exception object out of the return code + $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) + New-InvalidOperationException ` + -Message ($script:localizedData.UnableToEnumeratingPowerSchemes -f $win32Exception.NativeErrorCode, $win32Exception.Message) + } + + $guids += $planGuid + } + finally { + # Free up memory + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($readBuffer) + } + $index++ } - catch { - New-InvalidOperationException -ErrorRecord $_ + + # Now get the friendly name for each power plan so we can filter on name if needed. + $allPowerPlans = @() + foreach($planGuid in $guids) + { + $planFriendlyName = Get-PowerPlanFriendlyName -PowerPlanGuid $planGuid + $powerPlan = @{ + FriendlyName = $planFriendlyName + Guid = $planGuid + } + $allPowerPlans += $powerPlan } - if(($null -eq $powercfgOutPut) -or ('' -eq $powercfgOutPut)) + # If a friendly name is specified filter on it + if($PSBoundParameters.ContainsKey('PowerPlanFriendlyName')){ + $selectedPowerPlan = $allPowerPlans | Where-Object -FilterScript { + $_.FriendlyName -eq $PowerPlanFriendlyName + } + + return $selectedPowerPlan + } + else { - New-InvalidOperationException -Message $script:localizedData.UnableToGetPowerPlans + return $allPowerPlans } +} + +<# + .SYNOPSIS + This function gets the friendly name of a power plan specified by its GUID. - $allPlans = @() + .PARAMETER PowerPlanGuid + The GUID of a power plan. - foreach($line in $powercfgOutPut) + .NOTES + This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs + because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. + (e.g Server 2012 R2 core or Nano Server). + This function is used by the Get-PowerPlan function. +#> +function Get-PowerPlanFriendlyName +{ + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Guid] + $PowerPlanGuid + ) + + $ErrorActionPreference = 'Stop' + + # Define C# signature of PowerReadFriendlyName function + $MethodDefinition = @' + [DllImport("powrprof.dll", CharSet = CharSet.Unicode)] + public static extern uint PowerReadFriendlyName( + IntPtr RootPowerKey, + Guid SchemeGuid, + IntPtr SubGroupOfPowerSettingGuid, + IntPtr PowerSettingGuid, + IntPtr Buffer, + ref uint BufferSize + ); +'@ + + # Create powerprof object with the static method PowerReadFriendlyName. + $powerprof = Add-Type ` + -MemberDefinition $MethodDefinition ` + -Name 'powerprof' ` + -Namespace 'Win32Functions' ` + -PassThru + + # Define variable for buffer size which whe have frist to figure out. + $bufferSize = 0 + $returnCode = 0 + + try { + # Frist get needed buffer size by calling PowerReadFriendlyName + # with NULL value for 'Buffer' parameter to get the required buffer size. + $returnCode = $powerprof::PowerReadFriendlyName([System.IntPtr]::Zero, $PowerPlanGuid, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [ref]$bufferSize) - if($line -match "^.*?:[ ]*(?'guid'.*?)[ ]*\((?'name'.*?)\)") + if ($returnCode -eq 0) { - $plan = [PSCustomObject]@{ - Name = [String]$Matches.name - Guid = [Guid]$Matches.guid - IsActive = $false - } + try + { + # Now lets allocate the needed buffer size + $ptrName = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([int]$bufferSize) - if($line -match "\*$") + # Get the actual friendly name of the powerlan by calling PowerReadFriendlyName again. + # This time with the correct buffer size for the 'Buffer' parameter. + $returnCode = $powerprof::PowerReadFriendlyName([System.IntPtr]::Zero, $PowerPlanGuid, [System.IntPtr]::Zero, [System.IntPtr]::Zero, $ptrName, [ref]$bufferSize) + if ($returnCode -eq 0) + { + #Create a managed String object form the unmanned memory block. + $friendlyName = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptrName) + return $friendlyName + } + else + { + throw [ComponentModel.Win32Exception]::new([int]$returnCode) + } + } + finally { - $plan.IsActive = $true + # Make sure allocated memory is freed up again. + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($ptrName) } - - $allPlans += $plan + } + else + { + throw [ComponentModel.Win32Exception]::new([int]$returnCode) } } - - $selectedPlan = $allPlans | Where-Object -FilterScript { - ($_.Name -eq $Name) -or - ($_.Guid -eq $Name) + catch + { + New-InvalidOperationException ` + -Message ($script:localizedData.UnableToGetPowerSchemeFriendlyName -f $PowerPlanGuid, $_.Exception.NativeErrorCode, $_.Exception.Message) } +} + +<# + .SYNOPSIS + This function gets the GUID of the currently active power plan. - $selectedPlan + .NOTES + This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs + because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. + (e.g Server 2012 R2 core or Nano Server). + This function is used by the PowerPlan resource. +#> +function Get-ActivePowerPlan +{ + [CmdletBinding()] + param + ( + ) + + $ErrorActionPreference = 'Stop' + + # Define C# signature of PowerGetActiveScheme function + $powerGetActiveSchemeDefinition = @' + [DllImport("powrprof.dll", CharSet = CharSet.Unicode)] + public static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid); +'@ + + $returnCode = 0 + + # Create powerprof object with the static method PowerGetActiveScheme + $powrprof = Add-Type ` + -MemberDefinition $powerGetActiveSchemeDefinition ` + -Name 'powrprof' ` + -Namespace 'Win32Functions' ` + -PassThru + + try + { + # Get the GUID of the active power scheme + $activeSchemeGuid = [System.IntPtr]::Zero + $returnCode = $powrprof::PowerGetActiveScheme([System.IntPtr]::Zero, [ref]$activeSchemeGuid) + + # Check for non 0 return codes / errors form the native function + if ($returnCode -ne 0) + { + # Create a Win32Exception object out of the return code + $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) + New-InvalidOperationException ` + -Message ($script:localizedData.FailedToGetActivePowerScheme -f $win32Exception.NativeErrorCode, $win32Exception.Message) + } + + # Create a managed Guid object form the unmanaged memory block and return it + return [System.Runtime.InteropServices.Marshal]::PtrToStructure($activeSchemeGuid, [System.Type][guid]) + } + finally + { + # Make sure allocated memory is freed up again. + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($activeSchemeGuid) + } } <# .SYNOPSIS - This function activates the desired power plan (specified by its GUID). + This function activates a specific power plan (specified by its GUID). .PARAMETER Guid GUID of a power plan to activate. .NOTES - The powercfg.exe utility is used here because the Win32_PowerPLan class has - issues on some platforms (e.g Server 2012 R2 core or Nano Server). + This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs + because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. + (e.g Server 2012 R2 core or Nano Server). This function is used by the PowerPlan resource. #> -function Set-PowerPlan { +function Set-ActivePowerPlan { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Guid] - $Guid + $PowerPlanGuid ) # Set to stop so that the errors from powercfg.exe are terminating $ErrorActionPreference = 'Stop' + # Define C# signature of PowerSetActiveScheme function + $powerSetActiveSchemeDefinition = @' + [DllImport("powrprof.dll", CharSet = CharSet.Auto)] + public static extern uint PowerSetActiveScheme( + IntPtr RootPowerKey, + Guid SchemeGuid + ); +'@ + + # Create powerprof object with the static method PowerSetActiveScheme. + $powrprof = Add-Type -MemberDefinition $powerSetActiveSchemeDefinition -Name 'powrprof' -Namespace 'Win32Functions' -PassThru + try { - & powercfg.exe /S $Guid + + # Set the active power scheme with the native function + $returnCode = $powrprof::PowerSetActiveScheme([System.IntPtr]::Zero,$PowerPlanGuid) + + # Check for non 0 return codes / errors form the native function + if ($returnCode -ne 0) + { + throw [ComponentModel.Win32Exception]::new([int]$returnCode) + } } catch { - New-InvalidOperationException -ErrorRecord $_ + New-InvalidOperationException ` + -Message ($script:localizedData.FailedToSetActivePowerScheme -f $PowerPlanGuid, $_.Exception.NativeErrorCode, $_.Exception.Message) } } @@ -601,4 +828,5 @@ Export-ModuleMember -Function ` Set-TimeZoneId, ` Set-TimeZoneUsingDotNet, ` Get-PowerPlan, ` - Set-PowerPlan + Get-ActivePowerPlan, ` + Set-ActivePowerPlan diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 index a47d45df..e40bbfa8 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 @@ -17,5 +17,8 @@ ConvertFrom-StringData @' SettingTimeZoneMessage = Setting time zone to '{0}' using {1}. TimeZoneUpdatedMessage = Time zone has been updated to '{0}'. AddingSetTimeZoneDotNetTypeMessage = Adding .NET Set time zone Type. - UnableToGetPowerPlans = Unable to get available power plans with powercfg.exe /l. Unexpected empty output from powercfg.exe. + UnableToEnumeratingPowerSchemes = Error occurred while enumerating power schemes. Win32 error code: {0} - {1} + UnableToGetPowerSchemeFriendlyName = Error occurred while getting the friendly name of the power scheme with the GUID {0}. Win32 error code: {1} - {2} + FailedToGetActivePowerScheme = Error occurred while getting active power scheme. Win32 error code: {0} - {1} + FailedToSetActivePowerScheme = Error occurred while activating power scheme with the GUID {0}. Win32 error code: {1} - {2} '@ From 131bafdb612e2e41352ca4a1b79d30e67e46dcbb Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Tue, 22 Jan 2019 16:17:08 +0100 Subject: [PATCH 09/17] Update resource for new Get-ActivePowerPlan and Set-ActivePowerPlan functions --- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 35 +++++++++++-------- .../ComputerManagementDsc.Common.psm1 | 28 +++++++-------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 1da716ae..a3a0680e 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -46,17 +46,30 @@ function Get-TargetResource $Name ) - $plan = Get-PowerPlan -Name $Name + $desiredPowerPlan = Get-PowerPlan -PowerPlan $Name + $activePowerPlan = Get-ActivePowerPlan - if ($plan) + if($desiredPowerPlan) { - if ($plan.IsActive) + if($activePowerPlan -eq $desiredPowerPlan.Guid) { - Write-Verbose -Message ($script:localizedData.PowerPlanIsActive -f $Name) + Write-Verbose -Message ($script:localizedData.PowerPlanIsActive -f $desiredPowerPlan.FriendlyName) + + return @{ + IsSingleInstance = $IsSingleInstance + Name = $Name + IsActive = $true + } } else { - Write-Verbose -Message ($script:localizedData.PowerPlanIsNotActive -f $Name) + Write-Verbose -Message ($script:localizedData.PowerPlanIsNotActive -f $desiredPowerPlan.FriendlyName) + + return @{ + IsSingleInstance = $IsSingleInstance + Name = $Name + IsActive = $false + } } } else @@ -64,12 +77,6 @@ function Get-TargetResource New-InvalidOperationException ` -Message ($script:localizedData.PowerPlanNotFound -f $Name) } - - return @{ - IsSingleInstance = $IsSingleInstance - Name = $Name - IsActive = $plan.IsActive - } } <# @@ -104,11 +111,11 @@ function Set-TargetResource Write-Verbose -Message ($script:localizedData.PowerPlanIsBeingActivated -f $Name) - $plan = Get-PowerPlan -Name $Name + $desiredPowerPlan = Get-PowerPlan -PowerPlan $Name - if($plan) + if($desiredPowerPlan) { - Set-PowerPlan -Guid $plan.Guid + Set-ActivePowerPlan -PowerPlanGuid $desiredPowerPlan.Guid } else { diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 346ed751..f8a376f0 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -493,13 +493,12 @@ function Set-TimeZoneUsingDotNet <# .SYNOPSIS - This function gets all available power plans/schemes or - a specific power plans specified by its friendly name. - The function returns an array with one or more hashtable(s) - containing the friendly name and GUID of the power plan(s). + This function gets all available power plans/schemes or a specific power plan + The function returns an array with one or more hashtable(s) containing + the friendly name and GUID of the power plan(s). .PARAMETER PowerPlanFriendlyName - Friendly name a power plan to get. + Friendly name or GUID of a power plan to get. When not specified the function will return all available power plans. .NOTES @@ -515,7 +514,7 @@ function Get-PowerPlan { [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.String] - $PowerPlanFriendlyName + $PowerPlan ) $ErrorActionPreference = 'Stop' @@ -586,28 +585,29 @@ function Get-PowerPlan { } # Now get the friendly name for each power plan so we can filter on name if needed. - $allPowerPlans = @() + $allAvailablePowerPlans = @() foreach($planGuid in $guids) { $planFriendlyName = Get-PowerPlanFriendlyName -PowerPlanGuid $planGuid - $powerPlan = @{ + $availablePowerPlan = @{ FriendlyName = $planFriendlyName Guid = $planGuid } - $allPowerPlans += $powerPlan + $allAvailablePowerPlans += $availablePowerPlan } - # If a friendly name is specified filter on it - if($PSBoundParameters.ContainsKey('PowerPlanFriendlyName')){ - $selectedPowerPlan = $allPowerPlans | Where-Object -FilterScript { - $_.FriendlyName -eq $PowerPlanFriendlyName + # If a specific power plan is specified filter for it. + if($PSBoundParameters.ContainsKey('PowerPlan')){ + $selectedPowerPlan = $allAvailablePowerPlans | Where-Object -FilterScript { + ($_.FriendlyName -eq $PowerPlan) -or + ($_.Guid -eq $PowerPlan) } return $selectedPowerPlan } else { - return $allPowerPlans + return $allAvailablePowerPlans } } From 1da0089e3533edc844e8b976c661d8373a889fe7 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Tue, 22 Jan 2019 16:44:33 +0100 Subject: [PATCH 10/17] Update unit tests for resouce --- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 2 +- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 108 ++++++++++++------ 2 files changed, 71 insertions(+), 39 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index a3a0680e..11d558c2 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -47,10 +47,10 @@ function Get-TargetResource ) $desiredPowerPlan = Get-PowerPlan -PowerPlan $Name - $activePowerPlan = Get-ActivePowerPlan if($desiredPowerPlan) { + $activePowerPlan = Get-ActivePowerPlan if($activePowerPlan -eq $desiredPowerPlan.Guid) { Write-Verbose -Message ($script:localizedData.PowerPlanIsActive -f $desiredPowerPlan.FriendlyName) diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index 464a812d..0ec9c8fb 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -52,16 +52,21 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @( - [PSCustomObject]@{ - Name = 'High performance' + return @{ + FriendlyName = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - IsActive = $true } - ) } ` -ModuleName $script:DSCResourceName ` -Verifiable + + Mock ` + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should return the same values as passed as parameters (power plan specified as )' -TestCases $testCases { @@ -84,16 +89,21 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @( - [PSCustomObject]@{ - Name = 'High performance' + return @{ + FriendlyName = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - IsActive = $false } - ) } ` -ModuleName $script:DSCResourceName ` -Verifiable + + Mock ` + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should return an inactive plan (power plan specified as )' -TestCases $testCases { @@ -139,27 +149,38 @@ try Describe "$($script:DSCResourceName)\Set-TargetResource" { BeforeEach { Mock ` - -CommandName Get-PowerPlan ` - -MockWith { - return @( - [PSCustomObject]@{ - Name = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - IsActive = $false - } - ) - } ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Get-PowerPlan ` + -MockWith { + return @{ + FriendlyName = 'High performance' + Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable Mock ` - -CommandName Set-PowerPlan ` + -CommandName Set-ActivePowerPlan ` -ModuleName $script:DSCResourceName ` -Verifiable } Context 'When the system is not in the desired present state' { - It 'Should call Set-PowerPlan once (power plan specified as )' -TestCases $testCases { + + It 'Should call Get-PowerPlan once (power plan specified as )' -TestCases $testCases { + + param + ( + [String] + $Name + ) + + Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose + + Assert-MockCalled -CommandName Get-PowerPlan -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName + } + + It 'Should call Set-ActivePowerPlan once (power plan specified as )' -TestCases $testCases { param ( @@ -169,16 +190,16 @@ try Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose - Assert-MockCalled -CommandName Set-PowerPlan -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName + Assert-MockCalled -CommandName Set-ActivePowerPlan -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName } } Context 'When the preferred plan does not exist' { BeforeEach { Mock ` - -CommandName Get-PowerPlan ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Get-PowerPlan ` + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { @@ -204,18 +225,24 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @( - [PSCustomObject]@{ - Name = 'High performance' + return @{ + FriendlyName = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - IsActive = $true } - ) } ` -ModuleName $script:DSCResourceName ` -Verifiable + + Mock ` + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable } + It 'Should return the the state as present ($true) (power plan specified as )' -TestCases $testCases { param @@ -233,13 +260,18 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @( - [PSCustomObject]@{ - Name = 'High performance' + return @{ + FriendlyName = 'High performance' Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - IsActive = $false } - ) + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable + + Mock ` + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' } ` -ModuleName $script:DSCResourceName ` -Verifiable From d6ff2867458771fa7c2644dac28b74d3efe7aaa9 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Thu, 24 Jan 2019 15:51:12 +0100 Subject: [PATCH 11/17] Remove obsolete unit tests --- .../ComputerManagementDsc.Common.Tests.ps1 | 322 ------------------ 1 file changed, 322 deletions(-) diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 6921d0ce..3b42866a 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -690,328 +690,6 @@ try } } } - - Describe 'ComputerManagementDsc.Common\Get-PowerPlan' { - - $testCases =@( - # Power plan as name specified - @{ - Type = 'Friendly Name' - Name = 'High performance' - }, - # Power plan as Guid specified - @{ - Type = 'Guid' - Name = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - } - ) - - Context 'When the specified power plan is not available on the computer' { - Mock ` - -CommandName powercfg.exe ` - -MockWith { - return @( - "Existing Power Schemes (* Active)" - "-----------------------------------" - "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" - ) - } ` - -Verifiable - - It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - {Get-PowerPlan -Name $Name} | Should -Not -Throw - } - - It 'Should return nothing (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - Get-PowerPlan -Name $Name | Should -HaveCount 0 - Get-PowerPlan -Name $Name | Should -Be $null - } - - It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { - param - ( - [String] - $Name - ) - - Get-PowerPlan -Name $Name - Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It - } - - Assert-VerifiableMock - } - - Context 'When the specified power plan is available on the computer and not active' { - - Mock ` - -CommandName powercfg.exe ` - -MockWith { - return @( - "Existing Power Schemes (* Active)" - "-----------------------------------" - "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced) *" - "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance)" - "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" - ) - } ` - -Verifiable - - It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - {Get-PowerPlan -Name $Name} | Should -Not -Throw - } - - It 'Should return one object of type PSCustomObject (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $powerPlan = Get-PowerPlan -Name $Name - $powerPlan | Should -BeOfType [PSCustomObject] - $powerPlan | Should -HaveCount 1 - } - - It 'Should not be the active plan (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $powerPlan = Get-PowerPlan -Name $Name - $powerPlan.IsActive | Should -Be $false - } - - It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { - param - ( - [String] - $Name - ) - - Get-PowerPlan -Name $Name - Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It - } - - Assert-VerifiableMock - } - - Context 'When the specified power plan is available on the computer and is active' { - - Mock ` - -CommandName powercfg.exe ` - -MockWith { - return @( - "Existing Power Schemes (* Active)" - "-----------------------------------" - "Power Scheme GUID: 381b4222-f694-41f0-9685-ff5bb260df2e (Balanced)" - "Power Scheme GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c (High performance) *" - "Power Scheme GUID: a1841308-3541-4fab-bc81-f71556f20b4a (Power saver)" - ) - } ` - -Verifiable - - It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - {Get-PowerPlan -Name $Name} | Should -Not -Throw - } - - It 'Should return one object of type PSCustomObject (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $powerPlan = Get-PowerPlan -Name $Name - $powerPlan | Should -BeOfType [PSCustomObject] - $powerPlan | Should -HaveCount 1 - } - - It 'Should not be the active plan (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $powerPlan = Get-PowerPlan -Name $Name - $powerPlan.IsActive | Should -Be $true - } - - It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { - param - ( - [String] - $Name - ) - - Get-PowerPlan -Name $Name - Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It - } - - Assert-VerifiableMock - } - - Context 'When the powercfg.exe throws an invalid parameters error' { - - $errorRecord = [System.Management.Automation.ErrorRecord]::new( - [System.Management.Automation.RemoteException]::new('Invalid Parameters -- try "/?" for help'), - 'NativeCommandError', - 'NotSpecified', - 'Invalid Parameters -- try "/?" for help' - ) - - Mock ` - -CommandName powercfg.exe ` - -MockWith { - throw $errorRecord - } ` - -Verifiable - - It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $invalidOperationRecord = Get-InvalidOperationRecord ` - -ErrorRecord $errorRecord - - {Get-PowerPlan -Name $Name} | Should -Throw $invalidOperationRecord - } - - Assert-VerifiableMock - } - - Context 'When powercfg.exe /L returns nothing' { - - Mock ` - -CommandName powercfg.exe ` - -Verifiable - - It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $invalidOperationRecord = Get-InvalidOperationRecord ` - -Message $localizedData.UnableToGetPowerPlans - - {Get-PowerPlan -Name $Name} | Should -Throw $invalidOperationRecord - } - - Assert-VerifiableMock - } - } - - Describe 'ComputerManagementDsc.Common\Set-PowerPlan' { - Context 'When the specified power plan cloud be activated successfully' { - - Mock ` - -CommandName powercfg.exe ` - -Verifiable - - [Guid]$powerPlanGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - - It 'Should not throw any exception (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - {Set-PowerPlan -Guid $powerPlanGuid} | Should -Not -Throw - } - - It 'Should call powercfg.exe once (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - Set-PowerPlan -Guid $powerPlanGuid - Assert-MockCalled -CommandName powercfg.exe -Exactly -Times 1 -Scope It - } - - Assert-VerifiableMock - } - - Context 'When the powercfg.exe throws an error while setting the specified power plan' { - $errorRecord = [System.Management.Automation.ErrorRecord]::new( - [System.Management.Automation.RemoteException]::new('Invalid Parameters -- try "/?" for help'), - 'NativeCommandError', - 'NotSpecified', - 'Invalid Parameters -- try "/?" for help' - ) - - [Guid]$powerPlanGuid = '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - - Mock ` - -CommandName powercfg.exe ` - -MockWith { - throw $errorRecord - } ` - -Verifiable - - It 'Should throw the correct error (power plan specified as )' -TestCases $testCases { - - param - ( - [String] - $Name - ) - - $invalidOperationRecord = Get-InvalidOperationRecord ` - -ErrorRecord $errorRecord - - {Set-PowerPlan -Guid $powerPlanGuid} | Should -Throw $invalidOperationRecord - } - - Assert-VerifiableMock - } - - } } } finally From 069714ce17255b9d9573df58fc9af2c67ab69874 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Thu, 24 Jan 2019 16:35:50 +0100 Subject: [PATCH 12/17] Update typenames to prevent conflicts --- .../ComputerManagementDsc.Common.psm1 | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index f8a376f0..26f426ef 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -533,10 +533,10 @@ function Get-PowerPlan { ); '@ - # Create powerprof object with the static method PowerEnumerate + # Create Win32PowerEnumerate object with the static method PowerEnumerate $powrprof = Add-Type ` -MemberDefinition $powerEnumerateDefinition ` - -Name 'powrprof' ` + -Name 'Win32PowerEnumerate' ` -Namespace 'Win32Functions' ` -PassThru @@ -650,10 +650,10 @@ function Get-PowerPlanFriendlyName ); '@ - # Create powerprof object with the static method PowerReadFriendlyName. + # Create Win32PowerReadFriendlyName object with the static method PowerReadFriendlyName. $powerprof = Add-Type ` -MemberDefinition $MethodDefinition ` - -Name 'powerprof' ` + -Name 'Win32PowerReadFriendlyName' ` -Namespace 'Win32Functions' ` -PassThru @@ -733,10 +733,10 @@ function Get-ActivePowerPlan $returnCode = 0 - # Create powerprof object with the static method PowerGetActiveScheme + # Create Win32PowerGetActiveScheme object with the static method PowerGetActiveScheme $powrprof = Add-Type ` -MemberDefinition $powerGetActiveSchemeDefinition ` - -Name 'powrprof' ` + -Name 'Win32PowerGetActiveScheme' ` -Namespace 'Win32Functions' ` -PassThru @@ -799,8 +799,8 @@ function Set-ActivePowerPlan { ); '@ - # Create powerprof object with the static method PowerSetActiveScheme. - $powrprof = Add-Type -MemberDefinition $powerSetActiveSchemeDefinition -Name 'powrprof' -Namespace 'Win32Functions' -PassThru + # Create Win32PowerSetActiveScheme object with the static method PowerSetActiveScheme. + $powrprof = Add-Type -MemberDefinition $powerSetActiveSchemeDefinition -Name 'Win32PowerSetActiveScheme' -Namespace 'Win32Functions' -PassThru try { From 6f2eb5246878e2bece7d3023402c71137a909db6 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Thu, 24 Jan 2019 16:41:26 +0100 Subject: [PATCH 13/17] Update changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 892d9e40..e43dce1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,8 @@ - PowerPlan: - Added support to specify the desired power plan either as name or guid. Fixes [Issue #59](https://github.com/PowerShell/ComputerManagementDsc/issues/59) - - Changed the resource so it uses powercfg.exe instead of WMI/CIM - (Workaround fo rServer 2012R2 Core, Nano Server, Server 2019 and Windows 10). + - Changed the resource so it uses Windows APIs instead of WMI/CIM + (Workaround for Server 2012R2 Core, Nano Server, Server 2019 and Windows 10). Fixes [Issue #155](https://github.com/PowerShell/ComputerManagementDsc/issues/155) and [Issue #65](https://github.com/PowerShell/ComputerManagementDsc/issues/65) From 30de7d97db2cddc7aa5c4bf69123cab5eef59112 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sat, 9 Mar 2019 22:24:49 +0100 Subject: [PATCH 14/17] improve formating based on review --- .../MSFT_PowerPlan/MSFT_PowerPlan.psm1 | 26 ++--- .../ComputerManagementDsc.Common.psm1 | 105 ++++++++++++------ 2 files changed, 81 insertions(+), 50 deletions(-) diff --git a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 index 11d558c2..4142c3bc 100644 --- a/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 +++ b/Modules/ComputerManagementDsc/DSCResources/MSFT_PowerPlan/MSFT_PowerPlan.psm1 @@ -48,29 +48,27 @@ function Get-TargetResource $desiredPowerPlan = Get-PowerPlan -PowerPlan $Name - if($desiredPowerPlan) + if ($desiredPowerPlan) { $activePowerPlan = Get-ActivePowerPlan - if($activePowerPlan -eq $desiredPowerPlan.Guid) + + if ($activePowerPlan -eq $desiredPowerPlan.Guid) { Write-Verbose -Message ($script:localizedData.PowerPlanIsActive -f $desiredPowerPlan.FriendlyName) - - return @{ - IsSingleInstance = $IsSingleInstance - Name = $Name - IsActive = $true - } + $isActive = $true } else { Write-Verbose -Message ($script:localizedData.PowerPlanIsNotActive -f $desiredPowerPlan.FriendlyName) + $isActive = $false + } - return @{ - IsSingleInstance = $IsSingleInstance - Name = $Name - IsActive = $false - } + return @{ + IsSingleInstance = $IsSingleInstance + Name = $Name + IsActive = $isActive } + } else { @@ -113,7 +111,7 @@ function Set-TargetResource $desiredPowerPlan = Get-PowerPlan -PowerPlan $Name - if($desiredPowerPlan) + if ($desiredPowerPlan) { Set-ActivePowerPlan -PowerPlanGuid $desiredPowerPlan.Guid } diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 26f426ef..f198c40e 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -497,18 +497,20 @@ function Set-TimeZoneUsingDotNet The function returns an array with one or more hashtable(s) containing the friendly name and GUID of the power plan(s). - .PARAMETER PowerPlanFriendlyName + .PARAMETER PowerPlan Friendly name or GUID of a power plan to get. When not specified the function will return all available power plans. .NOTES This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs - because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. - (e.g Server 2012 R2 core or Nano Server). + because the Win32_PowerPlan WMI class has issues on some platforms or is unavailable at all. + e.g Server 2012 R2 core or Nano Server. This function is used by the PowerPlan resource. #> -function Get-PowerPlan { +function Get-PowerPlan +{ [CmdletBinding()] + [OutputType([System.Collections.Hashtable[]])] param ( [Parameter(Mandatory = $false)] @@ -542,27 +544,31 @@ function Get-PowerPlan { $index = 0 $returnCode = 0 - $guids = @() + $guids = [System.Collections.ArrayList]::new() # PowerEnumerate returns the GUID of the powerplan(s). Guid = 16 Bytes. $bufferSize = 16 - # The PowerEnumerate function returns only one guid at a time. - # So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. + <# + The PowerEnumerate function returns only one guid at a time. + So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. + #> while ($returnCode -eq 0) { - try { + try + { # Allocate buffer - $readBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([int]$bufferSize) + $readBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Int32]$bufferSize) # Get Guid of the power plan using the native PowerEnumerate function $returnCode = $powrprof::PowerEnumerate([System.IntPtr]::Zero,[System.IntPtr]::Zero,[System.IntPtr]::Zero,16,$index,$readBuffer,[ref]$bufferSize) # Create a managed Guid object form the unmanaged memory block - $planGuid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($readBuffer, [System.Type][guid]) + $planGuid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($readBuffer, [System.Type][System.Guid]) # ReturnCode 259 from the native function means no more data - if ($ReturnCode -eq 259) { + if ($returnCode -eq 259) + { break } @@ -575,7 +581,7 @@ function Get-PowerPlan { -Message ($script:localizedData.UnableToEnumeratingPowerSchemes -f $win32Exception.NativeErrorCode, $win32Exception.Message) } - $guids += $planGuid + $null = $guids.Add($planGuid) } finally { # Free up memory @@ -585,8 +591,8 @@ function Get-PowerPlan { } # Now get the friendly name for each power plan so we can filter on name if needed. - $allAvailablePowerPlans = @() - foreach($planGuid in $guids) + $allAvailablePowerPlans = [System.Collections.ArrayList]::new() + foreach ($planGuid in $guids) { $planFriendlyName = Get-PowerPlanFriendlyName -PowerPlanGuid $planGuid $availablePowerPlan = @{ @@ -597,7 +603,8 @@ function Get-PowerPlan { } # If a specific power plan is specified filter for it. - if($PSBoundParameters.ContainsKey('PowerPlan')){ + if ($PSBoundParameters.ContainsKey('PowerPlan')) + { $selectedPowerPlan = $allAvailablePowerPlans | Where-Object -FilterScript { ($_.FriendlyName -eq $PowerPlan) -or ($_.Guid -eq $PowerPlan) @@ -620,13 +627,14 @@ function Get-PowerPlan { .NOTES This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs - because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. - (e.g Server 2012 R2 core or Nano Server). + because the Win32_PowerPlan WMI class has issues on some platforms or is unavailable at all. + e.g Server 2012 R2 core or Nano Server. This function is used by the Get-PowerPlan function. #> function Get-PowerPlanFriendlyName { [CmdletBinding()] + [OutputType([System.String])] param ( [Parameter(Mandatory = $true)] @@ -663,29 +671,46 @@ function Get-PowerPlanFriendlyName try { - # Frist get needed buffer size by calling PowerReadFriendlyName - # with NULL value for 'Buffer' parameter to get the required buffer size. - $returnCode = $powerprof::PowerReadFriendlyName([System.IntPtr]::Zero, $PowerPlanGuid, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [System.IntPtr]::Zero, [ref]$bufferSize) + <# + Frist get needed buffer size by calling PowerReadFriendlyName + with NULL value for 'Buffer' parameter to get the required buffer size. + #> + $returnCode = $powerprof::PowerReadFriendlyName( + [System.IntPtr]::Zero, + $PowerPlanGuid, + [System.IntPtr]::Zero, + [System.IntPtr]::Zero, + [System.IntPtr]::Zero, + [ref]$bufferSize) if ($returnCode -eq 0) { try { # Now lets allocate the needed buffer size - $ptrName = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([int]$bufferSize) + $ptrName = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Int32]$bufferSize) + + <# + Get the actual friendly name of the powerlan by calling PowerReadFriendlyName again. + This time with the correct buffer size for the 'Buffer' parameter. + #> + $returnCode = $powerprof::PowerReadFriendlyName( + [System.IntPtr]::Zero, + $PowerPlanGuid, + [System.IntPtr]::Zero, + [System.IntPtr]::Zero, + $ptrName, + [ref]$bufferSize) - # Get the actual friendly name of the powerlan by calling PowerReadFriendlyName again. - # This time with the correct buffer size for the 'Buffer' parameter. - $returnCode = $powerprof::PowerReadFriendlyName([System.IntPtr]::Zero, $PowerPlanGuid, [System.IntPtr]::Zero, [System.IntPtr]::Zero, $ptrName, [ref]$bufferSize) if ($returnCode -eq 0) { - #Create a managed String object form the unmanned memory block. + # Create a managed String object form the unmanged memory block. $friendlyName = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptrName) return $friendlyName } else { - throw [ComponentModel.Win32Exception]::new([int]$returnCode) + throw [ComponentModel.Win32Exception]::new([System.Int32]$returnCode) } } finally @@ -696,7 +721,7 @@ function Get-PowerPlanFriendlyName } else { - throw [ComponentModel.Win32Exception]::new([int]$returnCode) + throw [ComponentModel.Win32Exception]::new([System.Int32]$returnCode) } } catch @@ -712,13 +737,14 @@ function Get-PowerPlanFriendlyName .NOTES This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs - because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. - (e.g Server 2012 R2 core or Nano Server). + because the Win32_PowerPlan WMI class has issues on some platforms or is unavailable at all. + e.g Server 2012 R2 core or Nano Server. This function is used by the PowerPlan resource. #> function Get-ActivePowerPlan { [CmdletBinding()] + [OutputType([System.Guid])] param ( ) @@ -750,13 +776,13 @@ function Get-ActivePowerPlan if ($returnCode -ne 0) { # Create a Win32Exception object out of the return code - $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) + $win32Exception = ([ComponentModel.Win32Exception]::new([System.Int32]$returnCode)) New-InvalidOperationException ` -Message ($script:localizedData.FailedToGetActivePowerScheme -f $win32Exception.NativeErrorCode, $win32Exception.Message) } # Create a managed Guid object form the unmanaged memory block and return it - return [System.Runtime.InteropServices.Marshal]::PtrToStructure($activeSchemeGuid, [System.Type][guid]) + return [System.Runtime.InteropServices.Marshal]::PtrToStructure($activeSchemeGuid, [System.Type][System.Guid]) } finally { @@ -775,10 +801,11 @@ function Get-ActivePowerPlan .NOTES This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. - (e.g Server 2012 R2 core or Nano Server). + e.g Server 2012 R2 core or Nano Server. This function is used by the PowerPlan resource. #> -function Set-ActivePowerPlan { +function Set-ActivePowerPlan +{ [CmdletBinding()] param ( @@ -800,9 +827,14 @@ function Set-ActivePowerPlan { '@ # Create Win32PowerSetActiveScheme object with the static method PowerSetActiveScheme. - $powrprof = Add-Type -MemberDefinition $powerSetActiveSchemeDefinition -Name 'Win32PowerSetActiveScheme' -Namespace 'Win32Functions' -PassThru + $powrprof = Add-Type ` + -MemberDefinition $powerSetActiveSchemeDefinition ` + -Name 'Win32PowerSetActiveScheme' ` + -Namespace 'Win32Functions' ` + -PassThru - try { + try + { # Set the active power scheme with the native function $returnCode = $powrprof::PowerSetActiveScheme([System.IntPtr]::Zero,$PowerPlanGuid) @@ -813,7 +845,8 @@ function Set-ActivePowerPlan { throw [ComponentModel.Win32Exception]::new([int]$returnCode) } } - catch { + catch + { New-InvalidOperationException ` -Message ($script:localizedData.FailedToSetActivePowerScheme -f $PowerPlanGuid, $_.Exception.NativeErrorCode, $_.Exception.Message) } From 84808b9c0f9fe9da195b83b2f3b8b682fbd180e1 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sun, 10 Mar 2019 18:01:04 +0100 Subject: [PATCH 15/17] Add new function for enumerating power plans --- .../ComputerManagementDsc.Common.psm1 | 209 ++++++----- .../ComputerManagementDsc.Common.strings.psd1 | 4 + .../ComputerManagementDsc.Common.Tests.ps1 | 328 ++++++++++++++++++ 3 files changed, 452 insertions(+), 89 deletions(-) diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index f198c40e..49a7b63e 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -493,8 +493,8 @@ function Set-TimeZoneUsingDotNet <# .SYNOPSIS - This function gets all available power plans/schemes or a specific power plan - The function returns an array with one or more hashtable(s) containing + This function gets a specific power plan or all available power plans. + The function returns one or more hashtable(s) containing the friendly name and GUID of the power plan(s). .PARAMETER PowerPlan @@ -502,9 +502,6 @@ function Set-TimeZoneUsingDotNet When not specified the function will return all available power plans. .NOTES - This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs - because the Win32_PowerPlan WMI class has issues on some platforms or is unavailable at all. - e.g Server 2012 R2 core or Nano Server. This function is used by the PowerPlan resource. #> function Get-PowerPlan @@ -521,88 +518,10 @@ function Get-PowerPlan $ErrorActionPreference = 'Stop' - # Define C# signature of PowerEnumerate function - $powerEnumerateDefinition = @' - [DllImport("powrprof.dll", CharSet = CharSet.Unicode)] - public static extern uint PowerEnumerate( - IntPtr RootPowerKey, - IntPtr SchemeGuid, - IntPtr SubGroupOfPowerSetting, - int AccessFlags, - uint Index, - IntPtr rBuffer, - ref uint BufferSize - ); -'@ - - # Create Win32PowerEnumerate object with the static method PowerEnumerate - $powrprof = Add-Type ` - -MemberDefinition $powerEnumerateDefinition ` - -Name 'Win32PowerEnumerate' ` - -Namespace 'Win32Functions' ` - -PassThru - - $index = 0 - $returnCode = 0 - $guids = [System.Collections.ArrayList]::new() - - # PowerEnumerate returns the GUID of the powerplan(s). Guid = 16 Bytes. - $bufferSize = 16 - - <# - The PowerEnumerate function returns only one guid at a time. - So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. - #> - while ($returnCode -eq 0) - { - try - { - # Allocate buffer - $readBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Int32]$bufferSize) - - # Get Guid of the power plan using the native PowerEnumerate function - $returnCode = $powrprof::PowerEnumerate([System.IntPtr]::Zero,[System.IntPtr]::Zero,[System.IntPtr]::Zero,16,$index,$readBuffer,[ref]$bufferSize) + # Get all available power plan(s) as a hashtable with friendly name and GUID + $allAvailablePowerPlans = Get-PowerPlanUsingPInvoke - # Create a managed Guid object form the unmanaged memory block - $planGuid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($readBuffer, [System.Type][System.Guid]) - - # ReturnCode 259 from the native function means no more data - if ($returnCode -eq 259) - { - break - } - - # Check for non 0 return codes / errors form the native function - if ($returnCode -ne 0) - { - # Create a Win32Exception object out of the return code - $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) - New-InvalidOperationException ` - -Message ($script:localizedData.UnableToEnumeratingPowerSchemes -f $win32Exception.NativeErrorCode, $win32Exception.Message) - } - - $null = $guids.Add($planGuid) - } - finally { - # Free up memory - [System.Runtime.InteropServices.Marshal]::FreeHGlobal($readBuffer) - } - $index++ - } - - # Now get the friendly name for each power plan so we can filter on name if needed. - $allAvailablePowerPlans = [System.Collections.ArrayList]::new() - foreach ($planGuid in $guids) - { - $planFriendlyName = Get-PowerPlanFriendlyName -PowerPlanGuid $planGuid - $availablePowerPlan = @{ - FriendlyName = $planFriendlyName - Guid = $planGuid - } - $allAvailablePowerPlans += $availablePowerPlan - } - - # If a specific power plan is specified filter for it. + # If a specific power plan is specified filter for it otherwise return all if ($PSBoundParameters.ContainsKey('PowerPlan')) { $selectedPowerPlan = $allAvailablePowerPlans | Where-Object -FilterScript { @@ -781,7 +700,7 @@ function Get-ActivePowerPlan -Message ($script:localizedData.FailedToGetActivePowerScheme -f $win32Exception.NativeErrorCode, $win32Exception.Message) } - # Create a managed Guid object form the unmanaged memory block and return it + # Create a managed Guid object form the unmanged memory block and return it return [System.Runtime.InteropServices.Marshal]::PtrToStructure($activeSchemeGuid, [System.Type][System.Guid]) } finally @@ -791,6 +710,119 @@ function Get-ActivePowerPlan } } +<# + .SYNOPSIS + This function enumerates all available power plans/schemes. + The function returns one or more hashtable(s) containing + the friendly name and GUID of the power plan(s). + + .NOTES + This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs + because the Win32_PowerPlan WMI class has issues on some platforms or is unavailable at all. + e.g Server 2012 R2 core or Nano Server. + This function is used by the PowerPlan resource. +#> +function Get-PowerPlanUsingPInvoke +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable[]])] + param + ( + ) + + $ErrorActionPreference = 'Stop' + + Write-Verbose -Message ($script:localizedData.EnumeratingPowerPlans) + + # Define C# signature of PowerEnumerate function + $powerEnumerateDefinition = @' + [DllImport("powrprof.dll", CharSet = CharSet.Unicode)] + public static extern uint PowerEnumerate( + IntPtr RootPowerKey, + IntPtr SchemeGuid, + IntPtr SubGroupOfPowerSetting, + int AccessFlags, + uint Index, + IntPtr rBuffer, + ref uint BufferSize + ); +'@ + + # Create Win32PowerEnumerate object with the static method PowerEnumerate + $powrprof = Add-Type ` + -MemberDefinition $powerEnumerateDefinition ` + -Name 'Win32PowerEnumerate' ` + -Namespace 'Win32Functions' ` + -PassThru + + $index = 0 + $returnCode = 0 + $allAvailablePowerPlans = [System.Collections.ArrayList]::new() + + # PowerEnumerate returns the GUID of the powerplan(s). Guid = 16 Bytes. + $bufferSize = 16 + + <# + The PowerEnumerate function returns only one guid at a time. + So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. + #> + while ($returnCode -ne 259) + { + try + { + # Allocate buffer + $readBuffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Int32]$bufferSize) + + # Get Guid of the power plan using the native PowerEnumerate function + $returnCode = $powrprof::PowerEnumerate([System.IntPtr]::Zero, [System.IntPtr]::Zero, [System.IntPtr]::Zero, 16, $index, $readBuffer, [ref]$bufferSize) + + # Return Code 259 means no more data so we stop here. + if ($returnCode -eq 259) + { + break + } + + # Check for non 0 return codes / errors form the native function. + if ($returnCode -ne 0) + { + # Create a Win32Exception object out of the return code + $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) + New-InvalidOperationException ` + -Message ($script:localizedData.UnableToEnumeratingPowerSchemes -f $win32Exception.NativeErrorCode, $win32Exception.Message) + } + + # Create a managed Guid object form the unmanaged memory block + $planGuid = [System.Runtime.InteropServices.Marshal]::PtrToStructure($readBuffer, [System.Type][System.Guid]) + + Write-Verbose -Message ($script:localizedData.PowerPlanFound -f $planGuid) + + # Now get the friendly name of to the power plan + $planFriendlyName = Get-PowerPlanFriendlyName -PowerPlanGuid $planGuid + + Write-Verbose -Message ($script:localizedData.PowerPlanFriendlyNameFound -f $planFriendlyName) + + $null = $allAvailablePowerPlans.Add( + @{ + FriendlyName = $planFriendlyName + Guid = $planGuid + } + ) + + $index++ + } + finally + { + # Free up memory + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($readBuffer) + } + } + + Write-Verbose -Message ($script:localizedData.AllPowerPlansFound) + + return $allAvailablePowerPlans + +} + <# .SYNOPSIS This function activates a specific power plan (specified by its GUID). @@ -802,7 +834,7 @@ function Get-ActivePowerPlan This function uses Platform Invoke (P/Invoke) mechanism to call native Windows APIs because the Win32_PowerPlan WMI class has on some platforms issues or is unavailable at all. e.g Server 2012 R2 core or Nano Server. - This function is used by the PowerPlan resource. + This function is used by the Get-PowerPlan function respectively the PowerPlan resource. #> function Set-ActivePowerPlan { @@ -814,7 +846,6 @@ function Set-ActivePowerPlan $PowerPlanGuid ) - # Set to stop so that the errors from powercfg.exe are terminating $ErrorActionPreference = 'Stop' # Define C# signature of PowerSetActiveScheme function diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 index e40bbfa8..9ba00267 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/en-us/ComputerManagementDsc.Common.strings.psd1 @@ -21,4 +21,8 @@ ConvertFrom-StringData @' UnableToGetPowerSchemeFriendlyName = Error occurred while getting the friendly name of the power scheme with the GUID {0}. Win32 error code: {1} - {2} FailedToGetActivePowerScheme = Error occurred while getting active power scheme. Win32 error code: {0} - {1} FailedToSetActivePowerScheme = Error occurred while activating power scheme with the GUID {0}. Win32 error code: {1} - {2} + EnumeratingPowerPlans = Enumerating all available power plans/schemes on the system using native Win32 function 'PowerEnumerate'. + PowerPlanFound = Found power scheme {0}. Getting friendly name. + PowerPlanFriendlyNameFound = Friendly name is {0}. + AllPowerPlansFound = Enumerating of available power schemes done. '@ diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 3b42866a..7bd7957a 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -690,6 +690,334 @@ try } } } + + Describe 'ComputerManagementDsc.Common\Get-PowerPlan' { + Context 'Only one power plan is available and "PowerPlan" parameter is not specified' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + It 'Should not throw an exception' { + { Get-PowerPlan } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return exactly one hashtable' { + $result = Get-PowerPlan + $result | Should -BeOfType [System.Collections.Hashtable] + $result | Should -HaveCount 1 + } + + } + + Context 'Only one power plan is available and "PowerPlan" parameter is specified as Guid of the available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e' } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return a hashtable with the name and guid fo the power plan' { + $result = Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e' + $result | Should -BeOfType [System.Collections.Hashtable] + $result | Should -HaveCount 1 + $result.FriendlyName | Should -Be 'Balanced' + $result.guid | Should -Be '381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + Context 'Only one power plan is available and "PowerPlan" parameter is specified as Guid of a not available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return nothing' { + $result = Get-PowerPlan -PowerPlan '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + $result | Should -BeNullOrEmpty + } + } + + Context 'Only one power plan is available and "PowerPlan" parameter is specified as name of the available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan 'Balanced' } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return a hashtable with the name and guid fo the power plan' { + $result = Get-PowerPlan -PowerPlan 'Balanced' + $result | Should -BeOfType [System.Collections.Hashtable] + $result | Should -HaveCount 1 + $result.FriendlyName | Should -Be 'Balanced' + $result.guid | Should -Be '381b4222-f694-41f0-9685-ff5bb260df2e' + } + + + } + + Context 'Only one power plan is available and "PowerPlan" parameter is specified as name of a not available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan 'High performance' } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return nothing' { + $result = Get-PowerPlan -PowerPlan 'High performance' + $result | Should -BeNullOrEmpty + } + } + + Context 'Multiple power plans are available and "PowerPlan" parameter is not specified' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @( + @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + }, + @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + }, + @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + ) + } + + It 'Should not throw an exception' { + { Get-PowerPlan } | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return an array with all available plans' { + $result = Get-PowerPlan + $result | Should -HaveCount 3 + } + } + + Context 'Multiple power plans are available and "PowerPlan" parameter is specified as Guid of an available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @( + @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + }, + @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + }, + @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + ) + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e'} | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return a hashtable with the name and guid fo the power plan' { + $result = Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e' + $result | Should -BeOfType [System.Collections.Hashtable] + $result | Should -HaveCount 1 + $result.FriendlyName | Should -Be 'Balanced' + $result.guid | Should -Be '381b4222-f694-41f0-9685-ff5bb260df2e' + } + } + + Context 'Multiple power plans are available and "PowerPlan" parameter is specified as Guid of a not available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @( + @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + }, + @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + }, + @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + ) + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan '9c5e7fda-e8bf-4a96-9a85-a7e23a8c635c'} | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return nothing' { + $result = Get-PowerPlan -PowerPlan '9c5e7fda-e8bf-4a96-9a85-a7e23a8c635c' + $result | Should -BeNullOrEmpty + } + } + + Context 'Multiple power plans are available and "PowerPlan" parameter is specified as name of an available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @( + @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + }, + @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + }, + @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + ) + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan 'High performance'} | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return a hashtable with the name and guid fo the power plan' { + $result = Get-PowerPlan -PowerPlan 'High performance' + $result | Should -BeOfType [System.Collections.Hashtable] + $result | Should -HaveCount 1 + $result.FriendlyName | Should -Be 'High performance' + $result.guid | Should -Be '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + } + + Context 'Multiple power plans are available and "PowerPlan" parameter is specified as name of a not available plan' { + Mock ` + -CommandName Get-PowerPlanUsingPInvoke ` + -MockWith { + return @( + @{ + 'FriendlyName' = 'Balanced' + 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + }, + @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + }, + @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + ) + } + + It 'Should not throw an exception' { + { Get-PowerPlan -PowerPlan 'Some unavailable plan'} | Should -Not -Throw + } + + It 'Should call expected mocks' { + Assert-MockCalled ` + -CommandName Get-PowerPlanUsingPInvoke ` + -Exactly -Times 1 + } + + It 'Should return nothing' { + $result = Get-PowerPlan -PowerPlan 'Some unavailable plan' + $result | Should -BeNullOrEmpty + } + } + } } } finally From 7095e778276a3c64bb5b2658373b9be422c8dda3 Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Fri, 22 Mar 2019 17:23:31 -0700 Subject: [PATCH 16/17] Improve formating based on review --- .../ComputerManagementDsc.Common.psm1 | 15 ++- Tests/Unit/MSFT_PowerPlan.Tests.ps1 | 106 +++++++++--------- 2 files changed, 60 insertions(+), 61 deletions(-) diff --git a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 index 49a7b63e..5a47be09 100644 --- a/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 +++ b/Modules/ComputerManagementDsc/Modules/ComputerManagementDsc.Common/ComputerManagementDsc.Common.psm1 @@ -591,8 +591,8 @@ function Get-PowerPlanFriendlyName try { <# - Frist get needed buffer size by calling PowerReadFriendlyName - with NULL value for 'Buffer' parameter to get the required buffer size. + Frist get needed buffer size by calling PowerReadFriendlyName + with NULL value for 'Buffer' parameter to get the required buffer size. #> $returnCode = $powerprof::PowerReadFriendlyName( [System.IntPtr]::Zero, @@ -610,8 +610,8 @@ function Get-PowerPlanFriendlyName $ptrName = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([System.Int32]$bufferSize) <# - Get the actual friendly name of the powerlan by calling PowerReadFriendlyName again. - This time with the correct buffer size for the 'Buffer' parameter. + Get the actual friendly name of the powerlan by calling PowerReadFriendlyName again. + This time with the correct buffer size for the 'Buffer' parameter. #> $returnCode = $powerprof::PowerReadFriendlyName( [System.IntPtr]::Zero, @@ -763,8 +763,8 @@ function Get-PowerPlanUsingPInvoke $bufferSize = 16 <# - The PowerEnumerate function returns only one guid at a time. - So we have to loop here until error code 259 (no more data) is returned to get all power plan guids. + The PowerEnumerate function returns only one guid at a time. + So we have to loop here until error code 259 (no more data) is returned to get all power plan GUIDs. #> while ($returnCode -ne 259) { @@ -786,7 +786,7 @@ function Get-PowerPlanUsingPInvoke if ($returnCode -ne 0) { # Create a Win32Exception object out of the return code - $win32Exception = ([ComponentModel.Win32Exception]::new([int]$returnCode)) + $win32Exception = ([ComponentModel.Win32Exception]::new([System.Int32]$returnCode)) New-InvalidOperationException ` -Message ($script:localizedData.UnableToEnumeratingPowerSchemes -f $win32Exception.NativeErrorCode, $win32Exception.Message) } @@ -820,7 +820,6 @@ function Get-PowerPlanUsingPInvoke Write-Verbose -Message ($script:localizedData.AllPowerPlansFound) return $allAvailablePowerPlans - } <# diff --git a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 index 0ec9c8fb..e21c39a2 100644 --- a/Tests/Unit/MSFT_PowerPlan.Tests.ps1 +++ b/Tests/Unit/MSFT_PowerPlan.Tests.ps1 @@ -52,28 +52,27 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @{ - FriendlyName = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - } - } ` + return @{ + FriendlyName = 'High performance' + Guid = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + } ` -ModuleName $script:DSCResourceName ` -Verifiable Mock ` - -CommandName Get-ActivePowerPlan ` - -MockWith { - return [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should return the same values as passed as parameters (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) @@ -89,28 +88,28 @@ try Mock ` -CommandName Get-PowerPlan ` -MockWith { - return @{ - FriendlyName = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - } - } ` + return @{ + FriendlyName = 'High performance' + Guid = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + } ` -ModuleName $script:DSCResourceName ` -Verifiable Mock ` - -CommandName Get-ActivePowerPlan ` - -MockWith { - return [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' } ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should return an inactive plan (power plan specified as )' -TestCases $testCases { param ( - [String] + [System.String] $Name ) @@ -133,7 +132,7 @@ try param ( - [String] + [System.String] $Name ) @@ -143,35 +142,34 @@ try { Get-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose } | Should -Throw $errorRecord } - Assert-VerifiableMock - } + Assert-VerifiableMock + } } - Describe "$($script:DSCResourceName)\Set-TargetResource" { + + Describe "$($script:DSCResourceName)\Set-TargetReource" { BeforeEach { Mock ` -CommandName Get-PowerPlan ` -MockWith { return @{ FriendlyName = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + Guid = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } } ` -ModuleName $script:DSCResourceName ` -Verifiable Mock ` - -CommandName Set-ActivePowerPlan ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Set-ActivePowerPlan ` + -ModuleName $script:DSCResourceName ` + -Verifiable } Context 'When the system is not in the desired present state' { - It 'Should call Get-PowerPlan once (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) @@ -181,16 +179,21 @@ try } It 'Should call Set-ActivePowerPlan once (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) Set-TargetResource -Name $Name -IsSingleInstance 'Yes' -Verbose - Assert-MockCalled -CommandName Set-ActivePowerPlan -Exactly -Times 1 -Scope It -ModuleName $script:DSCResourceName + Assert-MockCalled ` + -CommandName Set-ActivePowerPlan ` + -Exactly ` + -Times 1 ` + -Scope It ` + -ModuleName $script:DSCResourceName ` + -ParameterFilter {$PowerPlanGuid -eq '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c'} } } @@ -203,10 +206,9 @@ try } It 'Should throw the expected error (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) @@ -227,27 +229,26 @@ try -MockWith { return @{ FriendlyName = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + Guid = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } } ` -ModuleName $script:DSCResourceName ` -Verifiable Mock ` - -CommandName Get-ActivePowerPlan ` - -MockWith { - return [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - } ` - -ModuleName $script:DSCResourceName ` - -Verifiable + -CommandName Get-ActivePowerPlan ` + -MockWith { + return [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } ` + -ModuleName $script:DSCResourceName ` + -Verifiable } It 'Should return the the state as present ($true) (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) @@ -262,26 +263,25 @@ try -MockWith { return @{ FriendlyName = 'High performance' - Guid = [Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + Guid = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } } ` -ModuleName $script:DSCResourceName ` -Verifiable - Mock ` + Mock ` -CommandName Get-ActivePowerPlan ` -MockWith { - return [Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' } ` -ModuleName $script:DSCResourceName ` -Verifiable } It 'Should return the the state as absent ($false) (power plan specified as )' -TestCases $testCases { - param ( - [String] + [System.String] $Name ) From 96360a40be5663600eaf206ae6e5e2171037d7fa Mon Sep 17 00:00:00 2001 From: Jonas Feller Date: Sat, 23 Mar 2019 18:58:51 -0700 Subject: [PATCH 17/17] move moked powerplans to reusable variables --- .../ComputerManagementDsc.Common.Tests.ps1 | 141 ++++++------------ 1 file changed, 47 insertions(+), 94 deletions(-) diff --git a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 index 7bd7957a..c8908dde 100644 --- a/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 +++ b/Tests/Unit/ComputerManagementDsc.Common.Tests.ps1 @@ -692,15 +692,27 @@ try } Describe 'ComputerManagementDsc.Common\Get-PowerPlan' { + $mockBalancedPowerPlan = @{ + FriendlyName = 'Balanced' + Guid = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + } + + $mockHighPerformancePowerPlan = @{ + 'FriendlyName' = 'High performance' + 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' + } + + $mockPowerSaverPowerPlan = @{ + 'FriendlyName' = 'Power saver' + 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' + } + Context 'Only one power plan is available and "PowerPlan" parameter is not specified' { Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return $mockBalancedPowerPlan } - } It 'Should not throw an exception' { { Get-PowerPlan } | Should -Not -Throw @@ -724,11 +736,8 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return $mockBalancedPowerPlan } - } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e' } | Should -Not -Throw @@ -753,11 +762,8 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return $mockBalancedPowerPlan } - } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan '8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' } | Should -Not -Throw @@ -779,11 +785,8 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return $mockBalancedPowerPlan } - } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan 'Balanced' } | Should -Not -Throw @@ -802,19 +805,14 @@ try $result.FriendlyName | Should -Be 'Balanced' $result.guid | Should -Be '381b4222-f694-41f0-9685-ff5bb260df2e' } - - } Context 'Only one power plan is available and "PowerPlan" parameter is specified as name of a not available plan' { Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' + return $mockBalancedPowerPlan } - } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan 'High performance' } | Should -Not -Throw @@ -837,18 +835,9 @@ try -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { return @( - @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - }, - @{ - 'FriendlyName' = 'High performance' - 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - }, - @{ - 'FriendlyName' = 'Power saver' - 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - } + $mockBalancedPowerPlan + $mockHighPerformancePowerPlan + $mockPowerSaverPowerPlan ) } @@ -872,21 +861,12 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @( - @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - }, - @{ - 'FriendlyName' = 'High performance' - 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - }, - @{ - 'FriendlyName' = 'Power saver' - 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - } - ) - } + return @( + $mockBalancedPowerPlan + $mockHighPerformancePowerPlan + $mockPowerSaverPowerPlan + ) + } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan '381b4222-f694-41f0-9685-ff5bb260df2e'} | Should -Not -Throw @@ -911,21 +891,12 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @( - @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - }, - @{ - 'FriendlyName' = 'High performance' - 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - }, - @{ - 'FriendlyName' = 'Power saver' - 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - } - ) - } + return @( + $mockBalancedPowerPlan + $mockHighPerformancePowerPlan + $mockPowerSaverPowerPlan + ) + } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan '9c5e7fda-e8bf-4a96-9a85-a7e23a8c635c'} | Should -Not -Throw @@ -947,21 +918,12 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @( - @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - }, - @{ - 'FriendlyName' = 'High performance' - 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - }, - @{ - 'FriendlyName' = 'Power saver' - 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - } - ) - } + return @( + $mockBalancedPowerPlan + $mockHighPerformancePowerPlan + $mockPowerSaverPowerPlan + ) + } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan 'High performance'} | Should -Not -Throw @@ -986,21 +948,12 @@ try Mock ` -CommandName Get-PowerPlanUsingPInvoke ` -MockWith { - return @( - @{ - 'FriendlyName' = 'Balanced' - 'Guid' = [System.Guid]'381b4222-f694-41f0-9685-ff5bb260df2e' - }, - @{ - 'FriendlyName' = 'High performance' - 'Guid' = [System.Guid]'8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c' - }, - @{ - 'FriendlyName' = 'Power saver' - 'Guid' = [System.Guid]'a1841308-3541-4fab-bc81-f71556f20b4a' - } - ) - } + return @( + $mockBalancedPowerPlan + $mockHighPerformancePowerPlan + $mockPowerSaverPowerPlan + ) + } It 'Should not throw an exception' { { Get-PowerPlan -PowerPlan 'Some unavailable plan'} | Should -Not -Throw