From 434d4124d66caf4c32c6df1037530a56db4b03c4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Mon, 12 Feb 2024 19:34:03 +0100 Subject: [PATCH 01/21] Add DSC class-based resource BootstrapPSResourceGet --- CHANGELOG.md | 4 + RequiredModules.psd1 | 2 + build.yaml | 8 + source/Classes/020.BootstrapPSResourceGet.ps1 | 291 ++++++++++++++++++ .../en-US/BootstrapPSResourceGet.strings.psd1 | 19 ++ source/prefix.ps1 | 2 + 6 files changed, 326 insertions(+) create mode 100644 source/Classes/020.BootstrapPSResourceGet.ps1 create mode 100644 source/en-US/BootstrapPSResourceGet.strings.psd1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e5576af..e91ab8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- DSC class-based resource `BootstrapPSResourceGet'. + ### Fixed - Reverted resolve dependency logic so that GitHub actions works. diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 2ed1c08..a9f8ba9 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -21,6 +21,7 @@ #xDscResourceDesigner = 'latest' # Build dependencies needed for using the module + 'DscResource.Base' = 'latest' #'DscResource.Common' = 'latest' 'DscResource.Common' = @{ Version = 'latest' @@ -28,6 +29,7 @@ AllowPrerelease = $true } } + # Analyzer rules 'DscResource.AnalyzerRules' = 'latest' 'Indented.ScriptAnalyzerRules' = 'latest' diff --git a/build.yaml b/build.yaml index 0075b7b..f4db8d6 100644 --- a/build.yaml +++ b/build.yaml @@ -121,6 +121,12 @@ NestedModule: AddToManifest: false Exclude: PSGetModuleInfo.xml + DscResource.Base: + CopyOnly: true + Path: ./output/RequiredModules/DscResource.Base + AddToManifest: false + Exclude: PSGetModuleInfo.xml + #################################################### # Pester Configuration (Sampler) # #################################################### @@ -143,6 +149,7 @@ Pester: OutputEncoding: ascii ExcludeFromCodeCoverage: - Modules/DscResource.Common + - Modules/DscResource.Base #################################################### # Pester Configuration (DscResource.Test) # @@ -166,6 +173,7 @@ DscTest: - output ExcludeModuleFile: - Modules/DscResource.Common + - Modules/DscResource.Base # Must exclude built module file because it should not be tested like MOF-based resources - PSResourceGet.Bootstrap.psm1 MainGitBranch: main diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 new file mode 100644 index 0000000..d6e8ee5 --- /dev/null +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -0,0 +1,291 @@ +<# + .SYNOPSIS + The `BootstrapPSResourceGet` DSC resource is used to bootstrap the module + Microsoft.PowerShell.PSResourceGet to the specified location. + + .DESCRIPTION + The `BootstrapPSResourceGet` DSC resource is used to bootstrap the module + Microsoft.PowerShell.PSResourceGet to the specified location. + + It supports two parameter sets: 'Destination' and 'Scope'. The 'Destination' + parameter set allows you to specify a specific location to save the module, + while the 'Scope' parameter set saves the module to the appropriate `$env:PSModulePath` + location based on the specified scope ('CurrentUser' or 'AllUsers'). + + The built-in parameter **PSDscRunAsCredential** can be used to run the resource + as another user. + + ## Requirements + + * Target machine must be running a operating system supporting running + class-based DSC resources. + * Target machine must support running Microsoft.PowerShell.PSResourceGet. + + ## Known issues + + All issues are not listed here, see [here for all open issues](https://github.com/viscalyx/PSResourceGet.Bootstrap/issues?q=is%3Aissue+is%3Aopen+in%3Atitle+BootstrapPSResourceGet). + + ### Property **Reasons** does not work with **PSDscRunAsCredential** + + When using the built-in parameter **PSDscRunAsCredential** the read-only + property **Reasons** will return empty values for the properties **Code** + and **Phrase. The built-in property **PSDscRunAsCredential** does not work + together with class-based resources that using advanced type like the parameter + **Reasons** have. + + .PARAMETER Destination + Specifies the destination path where the module should be saved. This parameter + is mandatory when using the 'Destination' parameter set. The path must be a valid + container. This parameter may not be used at the same time as the parameter Scope. + + .PARAMETER Scope + Specifies the scope for saving the module. This parameter is used when using the + 'Scope' parameter set. The valid values are 'CurrentUser' and 'AllUsers'. The + default value is 'CurrentUser'. This parameter may not be used at the same time + as the parameter Destination. + + .PARAMETER Version + Specifies the version of the Microsoft.PowerShell.PSResourceGet module to download. + If not specified, the latest version will be downloaded. + + .PARAMETER UseCompatibilityModule + Indicates whether to use the compatibility module. If this switch parameter is + present, the compatibility module will be downloaded. + + .PARAMETER CompatibilityModuleVersion + Specifies the version of the compatibility module to download. If not specified, + it will default to a minimum required range that includes previews. + + .PARAMETER Force + Forces the operation without prompting for confirmation. This is useful when + running the script in non-interactive mode. + + .PARAMETER ImportModule + Indicates whether to import the module after it has been downloaded. + + .PARAMETER Ensure + Specifies if the server audit should be present or absent. If set to `Present` + the audit will be added if it does not exist, or updated if the audit exist. + If `Absent` then the audit will be removed from the server. Defaults to + `Present`. + + .EXAMPLE + Invoke-DscResource -ModuleName PSResourceGet.Bootstrap -Name BootstrapPSResourceGet -Method Get -Property @{ + IsSingleInstance = 'Yes' + Scope = 'CurrentUser' + } + + This example shows how to call the resource using Invoke-DscResource. This + example bootstraps the Microsoft.PowerShell.PSResourceGet module, saving + it to the appropriate location based on the default scope ('CurrentUser'). + It will also save the compatibility module to the same location. +#> +[DscResource(RunAsCredential = 'Optional')] +class BootstrapPSResourceGet : ResourceBase +{ + [DscProperty(Key)] + [ValidateSet('Yes')] + [System.String] + $IsSingleInstance + + # The Destination is evaluated if exist in AssertProperties(). + [DscProperty()] + [System.String] + $Destination + + # The Scope is evaluated if exist in AssertProperties(). + [DscProperty()] + [ValidateSet('CurrentUser', 'AllUsers')] + [System.String] + $Scope + + # The Version is evaluated if exist in AssertProperties(). + [DscProperty()] + [System.String] + $Version + + # [DscProperty()] + # [Nullable[System.Boolean]] + # $UseCompatibilityModule + + # # The CompatibilityModuleVersion is evaluated if exist in AssertProperties(). + # [DscProperty()] + # [System.String] + # $CompatibilityModuleVersion + + # [DscProperty()] + # [Nullable[System.Boolean]] + # $ImportModule + + # [DscProperty()] + # [Ensure] + # $Ensure = [Ensure]::Present + + BootstrapPSResourceGet () : base () + { + # These properties will not be enforced. + $this.ExcludeDscProperties = @( + 'IsSingleInstance' + ) + } + + [BootstrapPSResourceGet] Get() + { + # Call the base method to return the properties. + return ([ResourceBase] $this).Get() + } + + [System.Boolean] Test() + { + # Call the base method to test all of the properties that should be enforced. + return ([ResourceBase] $this).Test() + } + + [void] Set() + { + # Call the base method to enforce the properties. + ([ResourceBase] $this).Set() + } + + <# + Base method Get() call this method to get the current state as a hashtable. + The parameter keyProperty will contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $keyProperty) + { + Write-Verbose -Message $this.localizedData.EvaluateModule + + $currentState = @{} + + # Need to find out how to evaluate state since there are no key properties for that. + $assignedDscProperties = $this | Get-DscProperty -HasValue -Attribute @( + 'Mandatory' + 'Optional' + ) + + $testModuleExistParameters = @{ + Name = 'Microsoft.PowerShell.PSResourceGet' + } + + if ($assignedDscProperties.Keys -contains 'Version') + { + $testModuleExistParameters.Version = $assignedDscProperties.Version + + $currentState.Version = '' + } + + # If it is scope wasn't specified, then destination was specified. + if ($assignedDscProperties.Keys -contains 'Scope') + { + Write-Verbose -Message ( + $this.localizedData.EvaluatingScope -f $assignedDscProperties.Scope + ) + + $currentState.Scope = '' + + $testModuleExistParameters.Scope = $assignedDscProperties.Scope + + if ((Test-ModuleExist @testModuleExistParameters -ErrorAction 'Stop')) + { + $currentState.Scope = $assignedDscProperties.Scope + + if ($assignedDscProperties.Keys -contains 'Version') + { + $currentState.Version = $assignedDscProperties.Version + } + } + } + else + { + Write-Verbose -Message ( + $this.localizedData.EvaluatingDestination -f $assignedDscProperties.Destination + ) + + $currentState.Destination = '' + + $testModuleExistParameters.Destination = $assignedDscProperties.Destination + + if ((Test-ModuleExist @testModuleExistParameters -ErrorAction 'Stop')) + { + $currentState.Destination = $assignedDscProperties.Destination + + if ($assignedDscProperties.Keys -contains 'Version') + { + $currentState.Version = $assignedDscProperties.Version + } + } + } + + return $currentState + } + + <# + Base method Set() call this method with the properties that should be + enforced are not in desired state. It is not called if all properties + are in desired state. The variable $property contain the properties + that are not in desired state. + #> + hidden [void] Modify([System.Collections.Hashtable] $property) + { + Start-PSResourceGetBootstrap @property -Force -ErrorAction 'Stop' + } + + <# + Base method Assert() call this method with the properties that was assigned + a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $property) + { + # The properties Scope and Destination are mutually exclusive. + $assertBoundParameterParameters = @{ + BoundParameterList = $property + MutuallyExclusiveList1 = @( + 'Scope' + ) + MutuallyExclusiveList2 = @( + 'Destination' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + if ($property.Keys -contains 'Scope') + { + $scopeModulePath = Get-PSModulePath -Scope $property.Scope + + if (-not (Test-Path -Path $scopeModulePath)) + { + $errorMessage = $this.localizedData.ScopePathInvalid -f $property.Scope, $scopeModulePath + + New-InvalidArgumentException -ArgumentName 'Scope' -Message $errorMessage + } + } + + if ($property.Keys -contains 'Destination') + { + if (-not (Test-Path -Path $property.Destination)) + { + $errorMessage = $this.localizedData.DestinationInvalid -f $property.Destination + + New-InvalidArgumentException -ArgumentName 'Destination' -Message $errorMessage + } + } + + if ($property.Keys -contains 'Version') + { + $isValidVersion = ( + # From https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + $property.Version -match '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$' -or + # Need to support the Nuget range syntax as well. + $property.Version -match '^[\[(][0-9\.\,]*[\])]$' + ) + + if (-not $isValidVersion) + { + $errorMessage = $this.localizedData.VersionInvalid -f $property.Version + + New-InvalidArgumentException -ArgumentName 'Version' -Message $errorMessage + } + } + } +} diff --git a/source/en-US/BootstrapPSResourceGet.strings.psd1 b/source/en-US/BootstrapPSResourceGet.strings.psd1 new file mode 100644 index 0000000..0997422 --- /dev/null +++ b/source/en-US/BootstrapPSResourceGet.strings.psd1 @@ -0,0 +1,19 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource BootstrapPSResourceGet. +#> + +# cSpell: ignore BPSRG +ConvertFrom-StringData @' + ## Strings overrides for the ResourceBase's default strings. + # None + + ## Strings directly used by the derived class BootstrapPSResourceGet. + EvaluateModule = Evaluate if the module Microsoft.PowerShell.PSResourceGet is present. (BPSRG0001) + DestinationInvalid = The destination path '{0}' does not exist. (BPSRG0002) + ScopePathInvalid = The path '{1}' that was returned for the scope '{0}' does not exist. (BPSRG0003) + EvaluatingScope = Evaluation if module is present in the scope '{0}'. (BPSRG0004) + EvaluatingDestination = Evaluating if module is present in the destination path '{0}'. (BPSRG0006) + VersionInvalid = The version '{0}' is not a valid semantic version or one of the supported NuGet version ranges. (BPSRG0007) +'@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 index d643005..ebd1f0e 100644 --- a/source/prefix.ps1 +++ b/source/prefix.ps1 @@ -1,3 +1,5 @@ +using module .\Modules\DscResource.Base + $script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' Import-Module -Name $script:dscResourceCommonModulePath From 2fc8099b56f03cbd917f48672c1dd2a9c0da826a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 08:42:19 +0100 Subject: [PATCH 02/21] Fix resource code --- source/Classes/020.BootstrapPSResourceGet.ps1 | 54 +++++-------------- source/Enum/SingleInstance.ps1 | 4 ++ .../en-US/BootstrapPSResourceGet.strings.psd1 | 1 + 3 files changed, 19 insertions(+), 40 deletions(-) create mode 100644 source/Enum/SingleInstance.ps1 diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index d6e8ee5..08daeeb 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -33,6 +33,10 @@ together with class-based resources that using advanced type like the parameter **Reasons** have. + .PARAMETER IsSingleInstance + Specifies that only a single instance of the resource can exist in one and + the same configuration. Must always be set to the value `Yes`. + .PARAMETER Destination Specifies the destination path where the module should be saved. This parameter is mandatory when using the 'Destination' parameter set. The path must be a valid @@ -48,27 +52,6 @@ Specifies the version of the Microsoft.PowerShell.PSResourceGet module to download. If not specified, the latest version will be downloaded. - .PARAMETER UseCompatibilityModule - Indicates whether to use the compatibility module. If this switch parameter is - present, the compatibility module will be downloaded. - - .PARAMETER CompatibilityModuleVersion - Specifies the version of the compatibility module to download. If not specified, - it will default to a minimum required range that includes previews. - - .PARAMETER Force - Forces the operation without prompting for confirmation. This is useful when - running the script in non-interactive mode. - - .PARAMETER ImportModule - Indicates whether to import the module after it has been downloaded. - - .PARAMETER Ensure - Specifies if the server audit should be present or absent. If set to `Present` - the audit will be added if it does not exist, or updated if the audit exist. - If `Absent` then the audit will be removed from the server. Defaults to - `Present`. - .EXAMPLE Invoke-DscResource -ModuleName PSResourceGet.Bootstrap -Name BootstrapPSResourceGet -Method Get -Property @{ IsSingleInstance = 'Yes' @@ -84,10 +67,14 @@ class BootstrapPSResourceGet : ResourceBase { [DscProperty(Key)] - [ValidateSet('Yes')] - [System.String] + [SingleInstance] $IsSingleInstance + # [DscProperty(Key)] + # [ValidateSet('Yes')] + # [System.String] + # $IsSingleInstance + # The Destination is evaluated if exist in AssertProperties(). [DscProperty()] [System.String] @@ -104,23 +91,6 @@ class BootstrapPSResourceGet : ResourceBase [System.String] $Version - # [DscProperty()] - # [Nullable[System.Boolean]] - # $UseCompatibilityModule - - # # The CompatibilityModuleVersion is evaluated if exist in AssertProperties(). - # [DscProperty()] - # [System.String] - # $CompatibilityModuleVersion - - # [DscProperty()] - # [Nullable[System.Boolean]] - # $ImportModule - - # [DscProperty()] - # [Ensure] - # $Ensure = [Ensure]::Present - BootstrapPSResourceGet () : base () { # These properties will not be enforced. @@ -227,6 +197,10 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $property) { + Write-Verbose -Message $this.localizedData.Bootstrapping + + Write-Debug -Message "Start-PSResourceGetBootstrap Parameters:`n$($property | Out-String)" + Start-PSResourceGetBootstrap @property -Force -ErrorAction 'Stop' } diff --git a/source/Enum/SingleInstance.ps1 b/source/Enum/SingleInstance.ps1 new file mode 100644 index 0000000..a38ed77 --- /dev/null +++ b/source/Enum/SingleInstance.ps1 @@ -0,0 +1,4 @@ +enum SingleInstance +{ + Yes +} diff --git a/source/en-US/BootstrapPSResourceGet.strings.psd1 b/source/en-US/BootstrapPSResourceGet.strings.psd1 index 0997422..db12388 100644 --- a/source/en-US/BootstrapPSResourceGet.strings.psd1 +++ b/source/en-US/BootstrapPSResourceGet.strings.psd1 @@ -16,4 +16,5 @@ ConvertFrom-StringData @' EvaluatingScope = Evaluation if module is present in the scope '{0}'. (BPSRG0004) EvaluatingDestination = Evaluating if module is present in the destination path '{0}'. (BPSRG0006) VersionInvalid = The version '{0}' is not a valid semantic version or one of the supported NuGet version ranges. (BPSRG0007) + Bootstrapping = Bootstrapping the module Microsoft.PowerShell.PSResourceGet. (BPSRG0008) '@ From 8c505c126ae8fe07bcfaa952feffca386ba2f9ab Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 12:34:18 +0100 Subject: [PATCH 03/21] Add integration tests --- azure-pipelines.yml | 42 +++++++ ...otstrapPSResourceGet.Integration.Tests.ps1 | 112 ++++++++++++++++++ .../DSC_BootstrapPSResourceGet.config.ps1 | 42 +++++++ 3 files changed, 196 insertions(+) create mode 100644 tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 create mode 100644 tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 28a481d..8f7afde 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -350,6 +350,48 @@ stages: testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' testRunTitle: 'Integration ($(Agent.JobName))' + - job: Test_Integration_DSC + displayName: 'Integration DSC' + strategy: + matrix: + WIN2019: + JOB_VMIMAGE: 'windows-2019' + PWSH: false + WIN2022: + JOB_VMIMAGE: 'windows-2022' + PWSH: false + pool: + vmImage: $(JOB_VMIMAGE) + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - task: PowerShell@2 + name: test + displayName: 'Run Integration Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test -CodeCoverageThreshold 0 -PesterPath 'tests/Integration' -PesterTag 'DSC'" + pwsh: $(PWSH) + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration ($(Agent.JobName))' + - job: Test_Integration_Bootstrap_Script displayName: 'Integration Bootstrap Script' dependsOn: diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 new file mode 100644 index 0000000..c8128aa --- /dev/null +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -0,0 +1,112 @@ +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Justification = 'Suppressing this rule because Script Analyzer does not understand Pester syntax.')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } + + <# + Need to define that variables here to be used in the Pester Discover to + build the ForEach-blocks. + #> + $script:dscResourceFriendlyName = 'BootstrapPSResourceGet' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" +} + +BeforeAll { + # Need to define the variables here which will be used in Pester Run. + $script:dscModuleName = 'PSResourceGet.Bootstrap' + $script:dscResourceFriendlyName = 'BootstrapPSResourceGet' + $script:dscResourceName = "DSC_$($script:dscResourceFriendlyName)" + + $script:testEnvironment = Initialize-TestEnvironment ` + -DSCModuleName $script:dscModuleName ` + -DSCResourceName $script:dscResourceName ` + -ResourceType 'Class' ` + -TestType 'Integration' + + $configFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configFile +} + +AfterAll { + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} + +Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + Context ('When using configuration <_>') -ForEach @( + "$($script:dscResourceName)_CurrentUser_Config" + ) { + BeforeAll { + $configurationName = $_ + } + + AfterEach { + Wait-ForIdleLcm + } + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + # The variable $ConfigurationData was dot-sourced above. + ConfigurationData = $ConfigurationData + } + + & $configurationName @configurationParameters + + $startDscConfigurationParameters = @{ + Path = $TestDrive + ComputerName = 'localhost' + Wait = $true + Verbose = $true + Force = $true + ErrorAction = 'Stop' + } + + Start-DscConfiguration @startDscConfigurationParameters + } | Should -Not -Throw + } + + It 'Should be able to call Get-DscConfiguration without throwing' { + { + $script:currentConfiguration = Get-DscConfiguration -Verbose -ErrorAction Stop + } | Should -Not -Throw + } + + It 'Should have set the resource and all the parameters should match' { + $resourceCurrentState = $script:currentConfiguration | Where-Object -FilterScript { + $_.ConfigurationName -eq $configurationName ` + -and $_.ResourceId -eq $resourceId + } + + #$resourceCurrentState.IsSingleInstance | Should -Be 'Yes' + $resourceCurrentState.Scope | Should -Be 'CurrentUser' + } + + It 'Should return ''True'' when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } +} diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 new file mode 100644 index 0000000..12cc16a --- /dev/null +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -0,0 +1,42 @@ +#region HEADER +# Integration Test Config Template Version: 1.2.0 +#endregion + +$configFile = [System.IO.Path]::ChangeExtension($MyInvocation.MyCommand.Path, 'json') +if (Test-Path -Path $configFile) +{ + <# + Allows reading the configuration data from a JSON file, + for real testing scenarios outside of the CI. + #> + $ConfigurationData = Get-Content -Path $configFile | ConvertFrom-Json +} +else +{ + $ConfigurationData = @{ + AllNodes = @( + @{ + NodeName = 'localhost' + CertificateFile = $env:DscPublicCertificatePath + } + ) + } +} + +<# + .SYNOPSIS + Bootstraps Microsoft.PowerShell.PSResourceGet to scope CurrentUser. +#> +Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config +{ + Import-DscResource -ModuleName 'PSResourceGet.Bootstrap' + + node $AllNodes.NodeName + { + PSResourceGetBootstrap 'Integration_Test' + { + IsSingleInstance = 'Yes' + Scope = 'CurrentUser' + } + } +} From f0922cd5e028ee32063937e44fd9c9c01428e9ce Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 19:31:12 +0100 Subject: [PATCH 04/21] Switch parameter --- source/Classes/020.BootstrapPSResourceGet.ps1 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index 08daeeb..bce38d1 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -66,15 +66,15 @@ [DscResource(RunAsCredential = 'Optional')] class BootstrapPSResourceGet : ResourceBase { - [DscProperty(Key)] - [SingleInstance] - $IsSingleInstance - # [DscProperty(Key)] - # [ValidateSet('Yes')] - # [System.String] + # [SingleInstance] # $IsSingleInstance + [DscProperty(Key)] + [ValidateSet('Yes')] + [System.String] + $IsSingleInstance + # The Destination is evaluated if exist in AssertProperties(). [DscProperty()] [System.String] From ac7f753b026679280fe406b3659a96ed41883d44 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 19:37:59 +0100 Subject: [PATCH 05/21] Fix integ test --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8f7afde..064f05a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -340,7 +340,7 @@ stages: displayName: 'Run Integration Test' inputs: filePath: './build.ps1' - arguments: "-Tasks test -CodeCoverageThreshold 0 -PesterPath 'tests/Integration' -PesterExcludeTag 'BootstrapScript'" + arguments: "-Tasks test -CodeCoverageThreshold 0 -PesterPath 'tests/Integration' -PesterExcludeTag @('BootstrapScript','DSC')" pwsh: $(PWSH) - task: PublishTestResults@2 displayName: 'Publish Test Results' From d940fc1c3ca2d4941953e6e1b9f52cb9047a8c5b Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 19:51:58 +0100 Subject: [PATCH 06/21] Rename parameter --- source/Classes/020.BootstrapPSResourceGet.ps1 | 2 +- .../DSC_BootstrapPSResourceGet.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index bce38d1..8c0b0c0 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -84,7 +84,7 @@ class BootstrapPSResourceGet : ResourceBase [DscProperty()] [ValidateSet('CurrentUser', 'AllUsers')] [System.String] - $Scope + $ModuleScope # The Version is evaluated if exist in AssertProperties(). [DscProperty()] diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index c8128aa..dd755d9 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -102,7 +102,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { } #$resourceCurrentState.IsSingleInstance | Should -Be 'Yes' - $resourceCurrentState.Scope | Should -Be 'CurrentUser' + $resourceCurrentState.ModuleScope | Should -Be 'CurrentUser' } It 'Should return ''True'' when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 index 12cc16a..f972c67 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -36,7 +36,7 @@ Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config PSResourceGetBootstrap 'Integration_Test' { IsSingleInstance = 'Yes' - Scope = 'CurrentUser' + ModuleScope = 'CurrentUser' } } } From 8f6d0464878faa0c4a77959dfd7309a0631fa00d Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 20:03:08 +0100 Subject: [PATCH 07/21] Fix localization --- azure-pipelines.yml | 7 ------- source/Classes/020.BootstrapPSResourceGet.ps1 | 4 ++-- .../DSC_BootstrapPSResourceGet.Integration.Tests.ps1 | 2 +- tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 064f05a..03d06aa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -328,13 +328,6 @@ stages: buildType: 'current' artifactName: $(buildArtifactName) targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' - # - task: PowerShell@2 - # name: configureWinRM - # displayName: 'Configure WinRM' - # inputs: - # targetType: 'inline' - # script: 'winrm quickconfig -quiet' - # pwsh: false - task: PowerShell@2 name: test displayName: 'Run Integration Test' diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index 8c0b0c0..d131ee0 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -84,14 +84,14 @@ class BootstrapPSResourceGet : ResourceBase [DscProperty()] [ValidateSet('CurrentUser', 'AllUsers')] [System.String] - $ModuleScope + $Scope # The Version is evaluated if exist in AssertProperties(). [DscProperty()] [System.String] $Version - BootstrapPSResourceGet () : base () + BootstrapPSResourceGet () : base ($PSScriptRoot) { # These properties will not be enforced. $this.ExcludeDscProperties = @( diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index dd755d9..c8128aa 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -102,7 +102,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { } #$resourceCurrentState.IsSingleInstance | Should -Be 'Yes' - $resourceCurrentState.ModuleScope | Should -Be 'CurrentUser' + $resourceCurrentState.Scope | Should -Be 'CurrentUser' } It 'Should return ''True'' when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 index f972c67..12cc16a 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -36,7 +36,7 @@ Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config PSResourceGetBootstrap 'Integration_Test' { IsSingleInstance = 'Yes' - ModuleScope = 'CurrentUser' + Scope = 'CurrentUser' } } } From 3f3cffd13b54424fc44dd7335a14d0e763a919cd Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 20:04:00 +0100 Subject: [PATCH 08/21] Fix correct config --- tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 index 12cc16a..b6606d9 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -33,7 +33,7 @@ Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config node $AllNodes.NodeName { - PSResourceGetBootstrap 'Integration_Test' + BootstrapPSResourceGet 'Integration_Test' { IsSingleInstance = 'Yes' Scope = 'CurrentUser' From 4cc265805ac4a052ec80f057c62d848ca25de6ab Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 20:15:16 +0100 Subject: [PATCH 09/21] Rename parameter --- source/Classes/020.BootstrapPSResourceGet.ps1 | 42 ++++++++++--------- ...otstrapPSResourceGet.Integration.Tests.ps1 | 2 +- .../DSC_BootstrapPSResourceGet.config.ps1 | 2 +- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index d131ee0..eb55a31 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -9,8 +9,9 @@ It supports two parameter sets: 'Destination' and 'Scope'. The 'Destination' parameter set allows you to specify a specific location to save the module, - while the 'Scope' parameter set saves the module to the appropriate `$env:PSModulePath` - location based on the specified scope ('CurrentUser' or 'AllUsers'). + while the 'ModuleScope' parameter set saves the module to the appropriate + `$env:PSModulePath` location based on the specified scope ('CurrentUser' + or 'AllUsers'). The built-in parameter **PSDscRunAsCredential** can be used to run the resource as another user. @@ -40,11 +41,12 @@ .PARAMETER Destination Specifies the destination path where the module should be saved. This parameter is mandatory when using the 'Destination' parameter set. The path must be a valid - container. This parameter may not be used at the same time as the parameter Scope. + container. This parameter may not be used at the same time as the parameter + `ModuleScope`. - .PARAMETER Scope + .PARAMETER ModuleScope Specifies the scope for saving the module. This parameter is used when using the - 'Scope' parameter set. The valid values are 'CurrentUser' and 'AllUsers'. The + 'ModuleScope' parameter set. The valid values are 'CurrentUser' and 'AllUsers'. The default value is 'CurrentUser'. This parameter may not be used at the same time as the parameter Destination. @@ -55,7 +57,7 @@ .EXAMPLE Invoke-DscResource -ModuleName PSResourceGet.Bootstrap -Name BootstrapPSResourceGet -Method Get -Property @{ IsSingleInstance = 'Yes' - Scope = 'CurrentUser' + ModuleScope = 'CurrentUser' } This example shows how to call the resource using Invoke-DscResource. This @@ -80,11 +82,11 @@ class BootstrapPSResourceGet : ResourceBase [System.String] $Destination - # The Scope is evaluated if exist in AssertProperties(). + # The ModuleScope is evaluated if exist in AssertProperties(). [DscProperty()] [ValidateSet('CurrentUser', 'AllUsers')] [System.String] - $Scope + $ModuleScope # The Version is evaluated if exist in AssertProperties(). [DscProperty()] @@ -144,20 +146,20 @@ class BootstrapPSResourceGet : ResourceBase $currentState.Version = '' } - # If it is scope wasn't specified, then destination was specified. - if ($assignedDscProperties.Keys -contains 'Scope') + # If it is ModuleScope wasn't specified, then destination was specified. + if ($assignedDscProperties.Keys -contains 'ModuleScope') { Write-Verbose -Message ( - $this.localizedData.EvaluatingScope -f $assignedDscProperties.Scope + $this.localizedData.EvaluatingScope -f $assignedDscProperties.ModuleScope ) - $currentState.Scope = '' + $currentState.ModuleScope = '' - $testModuleExistParameters.Scope = $assignedDscProperties.Scope + $testModuleExistParameters.ModuleScope = $assignedDscProperties.ModuleScope if ((Test-ModuleExist @testModuleExistParameters -ErrorAction 'Stop')) { - $currentState.Scope = $assignedDscProperties.Scope + $currentState.ModuleScope = $assignedDscProperties.ModuleScope if ($assignedDscProperties.Keys -contains 'Version') { @@ -210,11 +212,11 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $property) { - # The properties Scope and Destination are mutually exclusive. + # The properties ModuleScope and Destination are mutually exclusive. $assertBoundParameterParameters = @{ BoundParameterList = $property MutuallyExclusiveList1 = @( - 'Scope' + 'ModuleScope' ) MutuallyExclusiveList2 = @( 'Destination' @@ -223,15 +225,15 @@ class BootstrapPSResourceGet : ResourceBase Assert-BoundParameter @assertBoundParameterParameters - if ($property.Keys -contains 'Scope') + if ($property.Keys -contains 'ModuleScope') { - $scopeModulePath = Get-PSModulePath -Scope $property.Scope + $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope if (-not (Test-Path -Path $scopeModulePath)) { - $errorMessage = $this.localizedData.ScopePathInvalid -f $property.Scope, $scopeModulePath + $errorMessage = $this.localizedData.ScopePathInvalid -f $property.ModuleScope, $scopeModulePath - New-InvalidArgumentException -ArgumentName 'Scope' -Message $errorMessage + New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage } } diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index c8128aa..dd755d9 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -102,7 +102,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { } #$resourceCurrentState.IsSingleInstance | Should -Be 'Yes' - $resourceCurrentState.Scope | Should -Be 'CurrentUser' + $resourceCurrentState.ModuleScope | Should -Be 'CurrentUser' } It 'Should return ''True'' when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 index b6606d9..ee5fac9 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -36,7 +36,7 @@ Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config BootstrapPSResourceGet 'Integration_Test' { IsSingleInstance = 'Yes' - Scope = 'CurrentUser' + ModuleScope = 'CurrentUser' } } } From 8c3098c397909c73aab133fc3f6136253cd63e6a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Tue, 13 Feb 2024 20:36:22 +0100 Subject: [PATCH 10/21] Fix code --- source/Classes/020.BootstrapPSResourceGet.ps1 | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index eb55a31..2dcaa7f 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -82,7 +82,12 @@ class BootstrapPSResourceGet : ResourceBase [System.String] $Destination - # The ModuleScope is evaluated if exist in AssertProperties(). + <# + The ModuleScope is evaluated if exist in AssertProperties(). + + The name Scope could not be used as it is a reserved keyword in + PowerShell DSC, if used it throws an error when parsing a configuration. + #> [DscProperty()] [ValidateSet('CurrentUser', 'AllUsers')] [System.String] @@ -155,7 +160,7 @@ class BootstrapPSResourceGet : ResourceBase $currentState.ModuleScope = '' - $testModuleExistParameters.ModuleScope = $assignedDscProperties.ModuleScope + $testModuleExistParameters.Scope = $assignedDscProperties.ModuleScope if ((Test-ModuleExist @testModuleExistParameters -ErrorAction 'Stop')) { @@ -175,7 +180,7 @@ class BootstrapPSResourceGet : ResourceBase $currentState.Destination = '' - $testModuleExistParameters.Destination = $assignedDscProperties.Destination + $testModuleExistParameters.Path = $assignedDscProperties.Destination if ((Test-ModuleExist @testModuleExistParameters -ErrorAction 'Stop')) { From 260cb533a97e3cb7dc490304467db5b8a33a0792 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 17 Feb 2024 16:46:25 +0100 Subject: [PATCH 11/21] Improve resource and add unit tests --- .../001.PSResourceGetBootstrapReason.ps1 | 22 + source/Classes/020.BootstrapPSResourceGet.ps1 | 75 +- source/Enum/Scope.ps1 | 6 + .../en-US/BootstrapPSResourceGet.strings.psd1 | 2 + .../Classes/BootstrapPSResourceGet.Tests.ps1 | 956 ++++++++++++++++++ 5 files changed, 1046 insertions(+), 15 deletions(-) create mode 100644 source/Classes/001.PSResourceGetBootstrapReason.ps1 create mode 100644 source/Enum/Scope.ps1 create mode 100644 tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 diff --git a/source/Classes/001.PSResourceGetBootstrapReason.ps1 b/source/Classes/001.PSResourceGetBootstrapReason.ps1 new file mode 100644 index 0000000..ebf9ba8 --- /dev/null +++ b/source/Classes/001.PSResourceGetBootstrapReason.ps1 @@ -0,0 +1,22 @@ +<# + .SYNOPSIS + The reason a property of a DSC resource is not in desired state. + + .DESCRIPTION + A DSC resource can have a read-only property `Reasons` that the compliance + part (audit via Azure Policy) of Azure AutoManage Machine Configuration + uses. The property Reasons holds an array of PSResourceGetBootstrapReason. + Each PSResourceGetBootstrapReason explains why a property of a DSC resource + is not in desired state. +#> + +class PSResourceGetBootstrapReason +{ + [DscProperty()] + [System.String] + $Code + + [DscProperty()] + [System.String] + $Phrase +} diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index 2dcaa7f..86e0b0e 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -50,6 +50,10 @@ default value is 'CurrentUser'. This parameter may not be used at the same time as the parameter Destination. + The value 'None' is not allowed to use in a configuration, it is used internally + and used in the output from method `Get()` to indicate that the module is not in + any scope. + .PARAMETER Version Specifies the version of the Microsoft.PowerShell.PSResourceGet module to download. If not specified, the latest version will be downloaded. @@ -62,19 +66,34 @@ This example shows how to call the resource using Invoke-DscResource. This example bootstraps the Microsoft.PowerShell.PSResourceGet module, saving - it to the appropriate location based on the default scope ('CurrentUser'). - It will also save the compatibility module to the same location. + it to the appropriate location based on the scope `'CurrentUser'`. + + .EXAMPLE + Invoke-DscResource -ModuleName PSResourceGet.Bootstrap -Name BootstrapPSResourceGet -Method Get -Property @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + Version = '1.0.2' + } + + This example shows how to call the resource using Invoke-DscResource. This + example bootstraps the Microsoft.PowerShell.PSResourceGet module with version + 1.0.2, saving it to the appropriate location based on the scope `'CurrentUser'`. + + .EXAMPLE + Invoke-DscResource -ModuleName PSResourceGet.Bootstrap -Name BootstrapPSResourceGet -Method Get -Property @{ + IsSingleInstance = 'Yes' + Destination = '/path/to/destination' + } + + This example shows how to call the resource using Invoke-DscResource. This + example bootstraps the Microsoft.PowerShell.PSResourceGet module, saving it + to the path specified in the parameter `Destination`. #> [DscResource(RunAsCredential = 'Optional')] class BootstrapPSResourceGet : ResourceBase { - # [DscProperty(Key)] - # [SingleInstance] - # $IsSingleInstance - [DscProperty(Key)] - [ValidateSet('Yes')] - [System.String] + [SingleInstance] $IsSingleInstance # The Destination is evaluated if exist in AssertProperties(). @@ -85,12 +104,11 @@ class BootstrapPSResourceGet : ResourceBase <# The ModuleScope is evaluated if exist in AssertProperties(). - The name Scope could not be used as it is a reserved keyword in + The parameter name Scope could not be used as it is a reserved keyword in PowerShell DSC, if used it throws an error when parsing a configuration. #> [DscProperty()] - [ValidateSet('CurrentUser', 'AllUsers')] - [System.String] + [Nullable[Scope]] $ModuleScope # The Version is evaluated if exist in AssertProperties(). @@ -98,6 +116,10 @@ class BootstrapPSResourceGet : ResourceBase [System.String] $Version + [DscProperty(NotConfigurable)] + [PSResourceGetBootstrapReason[]] + $Reasons + BootstrapPSResourceGet () : base ($PSScriptRoot) { # These properties will not be enforced. @@ -132,7 +154,9 @@ class BootstrapPSResourceGet : ResourceBase { Write-Verbose -Message $this.localizedData.EvaluateModule - $currentState = @{} + $currentState = @{ + IsSingleInstance = [SingleInstance]::Yes + } # Need to find out how to evaluate state since there are no key properties for that. $assignedDscProperties = $this | Get-DscProperty -HasValue -Attribute @( @@ -148,7 +172,7 @@ class BootstrapPSResourceGet : ResourceBase { $testModuleExistParameters.Version = $assignedDscProperties.Version - $currentState.Version = '' + $currentState.Version = $null } # If it is ModuleScope wasn't specified, then destination was specified. @@ -158,7 +182,7 @@ class BootstrapPSResourceGet : ResourceBase $this.localizedData.EvaluatingScope -f $assignedDscProperties.ModuleScope ) - $currentState.ModuleScope = '' + $currentState.ModuleScope = 'None' $testModuleExistParameters.Scope = $assignedDscProperties.ModuleScope @@ -178,7 +202,7 @@ class BootstrapPSResourceGet : ResourceBase $this.localizedData.EvaluatingDestination -f $assignedDscProperties.Destination ) - $currentState.Destination = '' + $currentState.Destination = $null $testModuleExistParameters.Path = $assignedDscProperties.Destination @@ -206,6 +230,13 @@ class BootstrapPSResourceGet : ResourceBase { Write-Verbose -Message $this.localizedData.Bootstrapping + if ($property.Keys -contains 'ModuleScope') + { + $property.Scope = $property.ModuleScope + + $property.Remove('ModuleScope') + } + Write-Debug -Message "Start-PSResourceGetBootstrap Parameters:`n$($property | Out-String)" Start-PSResourceGetBootstrap @property -Force -ErrorAction 'Stop' @@ -230,8 +261,22 @@ class BootstrapPSResourceGet : ResourceBase Assert-BoundParameter @assertBoundParameterParameters + if ($property.Keys -notcontains 'ModuleScope' -and $property.Keys -notcontains 'Destination') + { + $errorMessage = $this.localizedData.MissingRequiredParameter + + New-InvalidArgumentException -ArgumentName 'ModuleScope, Destination' -Message $errorMessage + } + if ($property.Keys -contains 'ModuleScope') { + if ($property.ModuleScope -eq [Scope]::None) + { + $errorMessage = $this.localizedData.ScopeNoneNotAllowed + + New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage + } + $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope if (-not (Test-Path -Path $scopeModulePath)) diff --git a/source/Enum/Scope.ps1 b/source/Enum/Scope.ps1 new file mode 100644 index 0000000..0b5f8ab --- /dev/null +++ b/source/Enum/Scope.ps1 @@ -0,0 +1,6 @@ +enum Scope +{ + None = 0 + CurrentUser + AllUsers +} diff --git a/source/en-US/BootstrapPSResourceGet.strings.psd1 b/source/en-US/BootstrapPSResourceGet.strings.psd1 index db12388..df54f88 100644 --- a/source/en-US/BootstrapPSResourceGet.strings.psd1 +++ b/source/en-US/BootstrapPSResourceGet.strings.psd1 @@ -17,4 +17,6 @@ ConvertFrom-StringData @' EvaluatingDestination = Evaluating if module is present in the destination path '{0}'. (BPSRG0006) VersionInvalid = The version '{0}' is not a valid semantic version or one of the supported NuGet version ranges. (BPSRG0007) Bootstrapping = Bootstrapping the module Microsoft.PowerShell.PSResourceGet. (BPSRG0008) + ScopeNoneNotAllowed = The value 'None' is not allowed, it is used internally and returned in the output from Get(). (BPSRG0009) + MissingRequiredParameter = At least one of the parameters 'ModuleScope' or 'Destination' bust be specified. (BPSRG0010) '@ diff --git a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 new file mode 100644 index 0000000..6c1e99b --- /dev/null +++ b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 @@ -0,0 +1,956 @@ +<# + .SYNOPSIS + Unit test for BootstrapPSResourceGet DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +BeforeDiscovery { + try + { + if (-not (Get-Module -Name 'DscResource.Test')) + { + # Assumes dependencies has been resolved, so if this module is not available, run 'noop' task. + if (-not (Get-Module -Name 'DscResource.Test' -ListAvailable)) + { + # Redirect all streams to $null, except the error stream (stream 2) + & "$PSScriptRoot/../../../build.ps1" -Tasks 'noop' 2>&1 4>&1 5>&1 6>&1 > $null + } + + # If the dependencies has not been resolved, this will throw an error. + Import-Module -Name 'DscResource.Test' -Force -ErrorAction 'Stop' + } + } + catch [System.IO.FileNotFoundException] + { + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -ResolveDependency -Tasks build" first.' + } +} + +BeforeAll { + $script:dscModuleName = 'PSResourceGet.Bootstrap' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName +} + +AfterAll { + $PSDefaultParameterValues.Remove('InModuleScope:ModuleName') + $PSDefaultParameterValues.Remove('Mock:ModuleName') + $PSDefaultParameterValues.Remove('Should:ModuleName') + + # Unload the module being tested so that it doesn't impact any other tests. + Get-Module -Name $script:dscModuleName -All | Remove-Module -Force +} + +Describe 'BootstrapPSResourceGet' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [BootstrapPSResourceGet]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [BootstrapPSResourceGet]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [BootstrapPSResourceGet]::new() + $instance.GetType().Name | Should -Be 'BootstrapPSResourceGet' + } + } + } +} + +Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + } + + <# + This mocks the method GetCurrentState() and AssertProperties(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockBootstrapPSResourceGetInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.Get() + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + $currentState.Destination | Should -BeNullOrEmpty + $currentState.Version | Should -BeNullOrEmpty + $currentState.Reasons | Should -BeNullOrEmpty + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } + + <# + This mocks the method GetCurrentState() and AssertProperties(). + + Method Get() will call the base method Get() which will + call back to the derived class method GetCurrentState() + to get the result to return from the derived method Get(). + #> + $script:mockBootstrapPSResourceGetInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'None' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.Get() + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.Destination | Should -BeNullOrEmpty + $currentState.Version | Should -BeNullOrEmpty + + $currentState.Reasons | Should -HaveCount 1 + $currentState.Reasons.Code | Should -Be 'BootstrapPSResourceGet:BootstrapPSResourceGet:ModuleScope' + $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was "None"' + } + } + } +} + +Describe 'BootstrapPSResourceGet\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } | + # Mock method Modify which is called by the base method Set(). + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { + $script:mockMethodModifyCallCount += 1 + } -PassThru + } + } + + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockMethodModifyCallCount = 0 + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should not call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance | + <# + Mock parent method Compare() and child method AssertProperties() + which is called by the base method Set() + #> + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'ModuleScope' + ExpectedValue = [Scope]::CurrentUser + ActualValue = [Scope]::None + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 1 + } + } + } +} + +Describe 'BootstrapPSResourceGet\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'ModuleScope' + ExpectedValue = [Scope]::CurrentUser + ActualValue = [Scope]::None + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Test() | Should -BeFalse + } + } + } +} + +Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When specifying the value CurrentScope for parameter ModuleScope' { + Context 'When PSResourceGet is missing' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $false + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::None) + } + } + } + + Context 'When PSResourceGet exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::CurrentUser + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $true + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + } + } + } + } + + Context 'When specifying the value AllUsers for parameter ModuleScope' { + Context 'When PSResourceGet is missing' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::AllUsers + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $false + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::None) + } + } + } + + Context 'When PSResourceGet exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = [Scope]::AllUsers + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $true + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.ModuleScope | Should -Be ([Scope]::AllUsers) + } + } + } + } + + Context 'When specifying a path for parameter Destination' { + Context 'When PSResourceGet is missing' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $false + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -BeNullOrEmpty + $currentState.ModuleScope | Should -BeNullOrEmpty + } + } + } + + Context 'When PSResourceGet exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $true + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -Be $TestDrive + $currentState.ModuleScope | Should -BeNullOrEmpty + } + } + } + } + + Context 'When specifying both parameters Destination and Version' { + Context 'When PSResourceGet is missing or have wrong version' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + Version = '1.0.0' + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $false + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -BeNullOrEmpty + $currentState.ModuleScope | Should -BeNullOrEmpty + $currentState.Version | Should -BeNullOrEmpty + } + } + } + + Context 'When PSResourceGet exist with the correct version' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + Version = '1.0.0' + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $true + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -Be $TestDrive + $currentState.ModuleScope | Should -BeNullOrEmpty + $currentState.Version | Should -Be '1.0.0' + } + } + } + } + + Context 'When specifying both parameters ModuleScope and Version' { + Context 'When PSResourceGet is missing or have wrong version' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + Version = '1.0.0' + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $false + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -BeNullOrEmpty + $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.Version | Should -BeNullOrEmpty + } + } + } + + Context 'When PSResourceGet exist with the correct version' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + Version = '1.0.0' + } + } + + Mock -CommandName Test-ModuleExist -MockWith { + return $true + } + } + + It 'Should return the correct values' { + InModuleScope -ScriptBlock { + $currentState = $script:mockBootstrapPSResourceGetInstance.GetCurrentState( + @{ + IsSingleInstance = 'Yes' + } + ) + + $currentState.IsSingleInstance | Should -Be 'Yes' + $currentState.Destination | Should -BeNullOrEmpty + $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + $currentState.Version | Should -Be '1.0.0' + } + } + } + } +} + +Describe 'BootstrapPSResourceGet\Modify()' -Tag 'Modify' { + Context 'When specifying parameter ModuleScope' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + } | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Start-PSResourceGetBootstrap + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Modify( + # This is the properties not in desired state. + @{ + ModuleScope = 'CurrentUser' + } + ) + + Should -Invoke -CommandName Start-PSResourceGetBootstrap -Exactly -Times 1 -Scope It + } + } + } + } + + Context 'When specifying both parameters ModuleScope and Version' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + Version = '1.0.0' + } | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Start-PSResourceGetBootstrap + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Modify( + # This is the properties not in desired state. + @{ + ModuleScope = 'CurrentUser' + Version = '1.0.0' + } + ) + + Should -Invoke -CommandName Start-PSResourceGetBootstrap -Exactly -Times 1 -Scope It + } + } + } + } + + Context 'When specifying parameter Destination' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + } | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Start-PSResourceGetBootstrap + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Modify( + # This is the properties not in desired state. + @{ + Destination = $TestDrive + } + ) + + Should -Invoke -CommandName Start-PSResourceGetBootstrap -Exactly -Times 1 -Scope It + } + } + } + } + + Context 'When specifying both parameters ModuleScope and Version' { + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + Version = '1.0.0' + } | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } -PassThru + } + + Mock -CommandName Start-PSResourceGetBootstrap + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance.Modify( + # This is the properties not in desired state. + @{ + Destination = $TestDrive + Version = '1.0.0' + } + ) + + Should -Invoke -CommandName Start-PSResourceGetBootstrap -Exactly -Times 1 -Scope It + } + } + } + } +} + +Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { + Context 'When passing parameter ModuleScope with the value ''None''' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'None' + } + } + } + + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopeNoneNotAllowed + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { + $script:mockBootstrapPSResourceGetInstance.AssertProperties( + @{ + ModuleScope = 'None' + } + ) + } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + <# + These tests just check for the string localized ID. Since the error is part + of a command outside of SqlServerDsc, a small changes to the localized + string should not fail these tests. + #> + Context 'When passing mutually exclusive parameters' { + Context 'When passing ModuleScope and ModuleScope' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + Destination = $TestDrive + } + } + } + + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $mockBootstrapPSResourceGetInstance.AssertProperties( + @{ + ModuleScope = 'CurrentUser' + Destination = $TestDrive + } + ) + } | Should -Throw -ExpectedMessage '*DRC0010*' + } + } + } + } + + Context 'When passing parameter ModuleScope and the scope''s path does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'CurrentUser' + } + } + + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When passing parameter Destination and the path does not exist' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + } + } + + Mock -CommandName Test-Path -MockWith { + return $false + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive + + $mockErrorMessage += ' (Parameter ''Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive + + $mockErrorMessage += ' (Parameter ''Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive + + $mockErrorMessage += ' (Parameter ''Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When passing parameter Version and the version is not valid' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + Destination = $TestDrive + Version = '1.0.0:-WrongTag' + } + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' + + $mockErrorMessage += ' (Parameter ''Version'')' + + { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' + + $mockErrorMessage += ' (Parameter ''Version'')' + + { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' + + $mockErrorMessage += ' (Parameter ''Version'')' + + { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } + + Context 'When neither of the parameters ModuleScope or Destination are specified' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + } + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter + + $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter + + $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter + + $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + + { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } +} From 5ac2541d3714313a3ff66ca9db144266b33638b2 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 17 Feb 2024 20:54:23 +0100 Subject: [PATCH 12/21] Fix enum that does not work --- source/Classes/020.BootstrapPSResourceGet.ps1 | 27 ++-- source/Enum/Scope.ps1 | 6 - .../en-US/BootstrapPSResourceGet.strings.psd1 | 2 +- .../Classes/BootstrapPSResourceGet.Tests.ps1 | 118 ++++++++++-------- 4 files changed, 85 insertions(+), 68 deletions(-) delete mode 100644 source/Enum/Scope.ps1 diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index 86e0b0e..b4125e3 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -50,10 +50,6 @@ default value is 'CurrentUser'. This parameter may not be used at the same time as the parameter Destination. - The value 'None' is not allowed to use in a configuration, it is used internally - and used in the output from method `Get()` to indicate that the module is not in - any scope. - .PARAMETER Version Specifies the version of the Microsoft.PowerShell.PSResourceGet module to download. If not specified, the latest version will be downloaded. @@ -102,13 +98,15 @@ class BootstrapPSResourceGet : ResourceBase $Destination <# - The ModuleScope is evaluated if exist in AssertProperties(). + The ModuleScope is evaluated in AssertProperties(). This parameter cannot + use the ValidateSet() attribute since it is not possible to set a null value, + unless it is set to [ValidateSet('CurrentUser', 'AllUsers', $null)] . The parameter name Scope could not be used as it is a reserved keyword in PowerShell DSC, if used it throws an error when parsing a configuration. #> [DscProperty()] - [Nullable[Scope]] + [System.String] $ModuleScope # The Version is evaluated if exist in AssertProperties(). @@ -182,7 +180,7 @@ class BootstrapPSResourceGet : ResourceBase $this.localizedData.EvaluatingScope -f $assignedDscProperties.ModuleScope ) - $currentState.ModuleScope = 'None' + $currentState.ModuleScope = $null $testModuleExistParameters.Scope = $assignedDscProperties.ModuleScope @@ -270,9 +268,20 @@ class BootstrapPSResourceGet : ResourceBase if ($property.Keys -contains 'ModuleScope') { - if ($property.ModuleScope -eq [Scope]::None) + <# + It is not possible to set a null value to the parameter ModuleScope + when it has a [ValidateSet()] unless it would be set to + [ValidateSet('CurrentUser', 'AllUsers', $null)]. But that would + give a strange output if giving the wrong value to the parameter: + E.g. + + 'The argument "CurrentUser2" does not belong to the set + "CurrentUser,AllUsers," specified by the ValidateSet + attribute.' + #> + if ($property.ModuleScope -notin ('CurrentUser', 'AllUsers')) { - $errorMessage = $this.localizedData.ScopeNoneNotAllowed + $errorMessage = $this.localizedData.ModuleScopeInvalid -f $property.ModuleScope New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage } diff --git a/source/Enum/Scope.ps1 b/source/Enum/Scope.ps1 deleted file mode 100644 index 0b5f8ab..0000000 --- a/source/Enum/Scope.ps1 +++ /dev/null @@ -1,6 +0,0 @@ -enum Scope -{ - None = 0 - CurrentUser - AllUsers -} diff --git a/source/en-US/BootstrapPSResourceGet.strings.psd1 b/source/en-US/BootstrapPSResourceGet.strings.psd1 index df54f88..22446ad 100644 --- a/source/en-US/BootstrapPSResourceGet.strings.psd1 +++ b/source/en-US/BootstrapPSResourceGet.strings.psd1 @@ -17,6 +17,6 @@ ConvertFrom-StringData @' EvaluatingDestination = Evaluating if module is present in the destination path '{0}'. (BPSRG0006) VersionInvalid = The version '{0}' is not a valid semantic version or one of the supported NuGet version ranges. (BPSRG0007) Bootstrapping = Bootstrapping the module Microsoft.PowerShell.PSResourceGet. (BPSRG0008) - ScopeNoneNotAllowed = The value 'None' is not allowed, it is used internally and returned in the output from Get(). (BPSRG0009) MissingRequiredParameter = At least one of the parameters 'ModuleScope' or 'Destination' bust be specified. (BPSRG0010) + ModuleScopeInvalid = The module scope '{0}' is not a valid module scope. The value must be one of "CurrentUser" or "AllUsers". (BPSRG0011) '@ diff --git a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 index 6c1e99b..fa5907d 100644 --- a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 +++ b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 @@ -92,7 +92,7 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -106,7 +106,7 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { $currentState = $script:mockBootstrapPSResourceGetInstance.Get() $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + $currentState.ModuleScope | Should -Be 'CurrentUser' $currentState.Destination | Should -BeNullOrEmpty $currentState.Version | Should -BeNullOrEmpty $currentState.Reasons | Should -BeNullOrEmpty @@ -119,7 +119,7 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } <# @@ -133,7 +133,7 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { return [System.Collections.Hashtable] @{ IsSingleInstance = 'Yes' - ModuleScope = 'None' + ModuleScope = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -147,13 +147,13 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { $currentState = $script:mockBootstrapPSResourceGetInstance.Get() $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.ModuleScope | Should -BeNullOrEmpty $currentState.Destination | Should -BeNullOrEmpty $currentState.Version | Should -BeNullOrEmpty $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons.Code | Should -Be 'BootstrapPSResourceGet:BootstrapPSResourceGet:ModuleScope' - $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was "None"' + $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was null' } } } @@ -164,7 +164,7 @@ Describe 'BootstrapPSResourceGet\Set()' -Tag 'Set' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } | # Mock method Modify which is called by the base method Set(). Add-Member -Force -MemberType 'ScriptMethod' -Name 'Modify' -Value { @@ -213,8 +213,8 @@ Describe 'BootstrapPSResourceGet\Set()' -Tag 'Set' { Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { return @{ Property = 'ModuleScope' - ExpectedValue = [Scope]::CurrentUser - ActualValue = [Scope]::None + ExpectedValue = 'CurrentUser' + ActualValue = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -238,7 +238,7 @@ Describe 'BootstrapPSResourceGet\Test()' -Tag 'Test' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } } } @@ -272,8 +272,8 @@ Describe 'BootstrapPSResourceGet\Test()' -Tag 'Test' { Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { return @{ Property = 'ModuleScope' - ExpectedValue = [Scope]::CurrentUser - ActualValue = [Scope]::None + ExpectedValue = 'CurrentUser' + ActualValue = $null } } -PassThru | Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { @@ -297,7 +297,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } } @@ -315,7 +315,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { ) $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.ModuleScope | Should -BeNullOrEmpty } } } @@ -325,7 +325,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::CurrentUser + ModuleScope = 'CurrentUser' } } @@ -343,7 +343,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { ) $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + $currentState.ModuleScope | Should -Be 'CurrentUser' } } } @@ -355,7 +355,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::AllUsers + ModuleScope = 'AllUsers' } } @@ -373,7 +373,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { ) $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.ModuleScope | Should -BeNullOrEmpty } } } @@ -383,7 +383,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ IsSingleInstance = 'Yes' - ModuleScope = [Scope]::AllUsers + ModuleScope = 'AllUsers' } } @@ -401,7 +401,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { ) $currentState.IsSingleInstance | Should -Be 'Yes' - $currentState.ModuleScope | Should -Be ([Scope]::AllUsers) + $currentState.ModuleScope | Should -Be ('AllUsers') } } } @@ -557,7 +557,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.IsSingleInstance | Should -Be 'Yes' $currentState.Destination | Should -BeNullOrEmpty - $currentState.ModuleScope | Should -Be ([Scope]::None) + $currentState.ModuleScope | Should -BeNullOrEmpty $currentState.Version | Should -BeNullOrEmpty } } @@ -588,7 +588,7 @@ Describe 'BootstrapPSResourceGet\GetCurrentState()' -Tag 'GetCurrentState' { $currentState.IsSingleInstance | Should -Be 'Yes' $currentState.Destination | Should -BeNullOrEmpty - $currentState.ModuleScope | Should -Be ([Scope]::CurrentUser) + $currentState.ModuleScope | Should -Be 'CurrentUser' $currentState.Version | Should -Be '1.0.0' } } @@ -723,40 +723,13 @@ Describe 'BootstrapPSResourceGet\Modify()' -Tag 'Modify' { } Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { - Context 'When passing parameter ModuleScope with the value ''None''' { - BeforeAll { - InModuleScope -ScriptBlock { - $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ - IsSingleInstance = 'Yes' - ModuleScope = 'None' - } - } - } - - It 'Should throw the correct error' { - InModuleScope -ScriptBlock { - $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopeNoneNotAllowed - - $mockErrorMessage += ' (Parameter ''ModuleScope'')' - - { - $script:mockBootstrapPSResourceGetInstance.AssertProperties( - @{ - ModuleScope = 'None' - } - ) - } | Should -Throw -ExpectedMessage $mockErrorMessage - } - } - } - <# These tests just check for the string localized ID. Since the error is part - of a command outside of SqlServerDsc, a small changes to the localized - string should not fail these tests. + of a command outside of PSResourceGet.Bootstrap a small change to the + localized string should not fail these tests. #> Context 'When passing mutually exclusive parameters' { - Context 'When passing ModuleScope and ModuleScope' { + Context 'When passing ModuleScope and Destination' { BeforeAll { InModuleScope -ScriptBlock { $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ @@ -953,4 +926,45 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { } } } + + Context 'When an invalid value is passed in ModuleScope' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockBootstrapPSResourceGetInstance = [BootstrapPSResourceGet] @{ + IsSingleInstance = 'Yes' + ModuleScope = 'InvalidScope' + } + } + } + + It 'Should throw the correct error for Get()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Set()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + + It 'Should throw the correct error for Test()' { + InModuleScope -ScriptBlock { + $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' + + $mockErrorMessage += ' (Parameter ''ModuleScope'')' + + { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage + } + } + } } From 236411ef1e7041c2d175e04beb721a3c4c452285 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 10:48:24 +0100 Subject: [PATCH 13/21] Fix HQRM and unit test --- .vscode/analyzersettings.psd1 | 1 + tests/QA/ScriptAnalyzer.Tests.ps1 | 1 + tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 | 11 ++++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.vscode/analyzersettings.psd1 b/.vscode/analyzersettings.psd1 index 84b661c..651df87 100644 --- a/.vscode/analyzersettings.psd1 +++ b/.vscode/analyzersettings.psd1 @@ -77,6 +77,7 @@ TypeNotFound - Because classes in the project cannot be found unless built. RequiresModuleInvalid - Because 'using module' in prefix.ps1 cannot be resolved as source file. + DscResourceInvalidKeyProperty - Because the enum type are not defined in the source file, only built module. #> ExcludeRules = @() diff --git a/tests/QA/ScriptAnalyzer.Tests.ps1 b/tests/QA/ScriptAnalyzer.Tests.ps1 index 7b32f5e..a68c805 100644 --- a/tests/QA/ScriptAnalyzer.Tests.ps1 +++ b/tests/QA/ScriptAnalyzer.Tests.ps1 @@ -83,6 +83,7 @@ Describe 'Script Analyzer Rules' { $parseErrorTypes = @( 'TypeNotFound' 'RequiresModuleInvalid' + 'DscResourceInvalidKeyProperty' ) # Filter out reported parse errors that is unable to be resolved in source files diff --git a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 index fa5907d..9936559 100644 --- a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 +++ b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 @@ -153,7 +153,16 @@ Describe 'BootstrapPSResourceGet\Get()' -Tag 'Get' { $currentState.Reasons | Should -HaveCount 1 $currentState.Reasons.Code | Should -Be 'BootstrapPSResourceGet:BootstrapPSResourceGet:ModuleScope' - $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was null' + + # The output is different between Windows PowerShell and PowerShell. + if ($IsCoreCLR) + { + $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was null' + } + else + { + $currentState.Reasons.Phrase | Should -Be 'The property ModuleScope should be "CurrentUser", but was ""' + } } } } From 648ae2c146474a1242a86863446b26a01e33bd17 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 11:01:06 +0100 Subject: [PATCH 14/21] Add debug messages --- source/Classes/020.BootstrapPSResourceGet.ps1 | 14 ++++++++++++++ ...SC_BootstrapPSResourceGet.Integration.Tests.ps1 | 1 + 2 files changed, 15 insertions(+) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index b4125e3..fb22594 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -150,6 +150,8 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $keyProperty) { + Write-Debug -Message "Enter GetCurrentState. Parameters:`n$($keyProperty | Out-String)" + Write-Verbose -Message $this.localizedData.EvaluateModule $currentState = @{ @@ -215,6 +217,9 @@ class BootstrapPSResourceGet : ResourceBase } } + Write-Debug -Message 'Exit GetCurrentState' + Write-Verbose -Message 'Exit GetCurrentState' -Verbose + return $currentState } @@ -226,6 +231,8 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $property) { + Write-Debug -Message "Enter Modify. Parameters:`n$($property | Out-String)" + Write-Verbose -Message $this.localizedData.Bootstrapping if ($property.Keys -contains 'ModuleScope') @@ -238,6 +245,8 @@ class BootstrapPSResourceGet : ResourceBase Write-Debug -Message "Start-PSResourceGetBootstrap Parameters:`n$($property | Out-String)" Start-PSResourceGetBootstrap @property -Force -ErrorAction 'Stop' + + Write-Debug -Message 'Exit Modify' } <# @@ -246,6 +255,9 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $property) { + Write-Debug -Message "Enter AssertProperties. Parameters:`n$($property | Out-String)" + Write-Verbose -Message "Enter AssertProperties. Parameters:`n$($property | Out-String)" -Verbose + # The properties ModuleScope and Destination are mutually exclusive. $assertBoundParameterParameters = @{ BoundParameterList = $property @@ -322,5 +334,7 @@ class BootstrapPSResourceGet : ResourceBase New-InvalidArgumentException -ArgumentName 'Version' -Message $errorMessage } } + + Write-Debug -Message 'Exit AssertProperties' } } diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index dd755d9..865311a 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -83,6 +83,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { Verbose = $true Force = $true ErrorAction = 'Stop' + Debug = $true } Start-DscConfiguration @startDscConfigurationParameters From b8f187ea4b6e20dba796a2c1377b3b4f525c3c38 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 11:39:52 +0100 Subject: [PATCH 15/21] DEBUG 1 --- source/Classes/020.BootstrapPSResourceGet.ps1 | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index fb22594..34181fa 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -150,7 +150,9 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $keyProperty) { - Write-Debug -Message "Enter GetCurrentState. Parameters:`n$($keyProperty | Out-String)" + Write-Debug -Message ( + 'Enter GetCurrentState. Parameters: {0}' -f ($keyProperty | ConvertTo-Json -Compress) + ) Write-Verbose -Message $this.localizedData.EvaluateModule @@ -218,7 +220,6 @@ class BootstrapPSResourceGet : ResourceBase } Write-Debug -Message 'Exit GetCurrentState' - Write-Verbose -Message 'Exit GetCurrentState' -Verbose return $currentState } @@ -231,7 +232,9 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] Modify([System.Collections.Hashtable] $property) { - Write-Debug -Message "Enter Modify. Parameters:`n$($property | Out-String)" + Write-Debug -Message ( + 'Enter Modify. Parameters: {0}' -f ($property | ConvertTo-Json -Compress) + ) Write-Verbose -Message $this.localizedData.Bootstrapping @@ -255,8 +258,9 @@ class BootstrapPSResourceGet : ResourceBase #> hidden [void] AssertProperties([System.Collections.Hashtable] $property) { - Write-Debug -Message "Enter AssertProperties. Parameters:`n$($property | Out-String)" - Write-Verbose -Message "Enter AssertProperties. Parameters:`n$($property | Out-String)" -Verbose + Write-Debug -Message ( + 'Enter AssertProperties. Parameters: {0}' -f ($property | ConvertTo-Json -Compress) + ) # The properties ModuleScope and Destination are mutually exclusive. $assertBoundParameterParameters = @{ @@ -298,9 +302,20 @@ class BootstrapPSResourceGet : ResourceBase New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage } + Write-Verbose -Message "Evaluating if module is present in the scope '$($property.ModuleScope)'" -Verbose $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope - if (-not (Test-Path -Path $scopeModulePath)) + Write-Verbose -Message ( + '[Environment]::GetFolderPath(''MyDocuments''): {0}' -f [Environment]::GetFolderPath('MyDocuments') + ) + + Write-Verbose -Message ( + '$IsCoreCLR: {0}' -f $IsCoreCLR + ) + + Write-Verbose -Message "The path that was returned for the scope '$($property.ModuleScope)' is '$scopeModulePath'" -Verbose + + if ([System.String]::IsNullOrEmpty($scopeModulePath) -or -not (Test-Path -Path $scopeModulePath)) { $errorMessage = $this.localizedData.ScopePathInvalid -f $property.ModuleScope, $scopeModulePath @@ -310,7 +325,7 @@ class BootstrapPSResourceGet : ResourceBase if ($property.Keys -contains 'Destination') { - if (-not (Test-Path -Path $property.Destination)) + if ([System.String]::IsNullOrEmpty($property.Destination) -or -not (Test-Path -Path $property.Destination)) { $errorMessage = $this.localizedData.DestinationInvalid -f $property.Destination From 035638cff8a7732146e26b51dd3f6594c24583f4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 11:56:47 +0100 Subject: [PATCH 16/21] DEBUG 2 --- source/Classes/020.BootstrapPSResourceGet.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index 34181fa..a709819 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -306,12 +306,12 @@ class BootstrapPSResourceGet : ResourceBase $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope Write-Verbose -Message ( - '[Environment]::GetFolderPath(''MyDocuments''): {0}' -f [Environment]::GetFolderPath('MyDocuments') + 'MyDocuments: {0}' -f [Environment]::GetFolderPath('MyDocuments') ) - Write-Verbose -Message ( - '$IsCoreCLR: {0}' -f $IsCoreCLR - ) + # Write-Verbose -Message ( + # '$IsCoreCLR: {0}' -f (if( $IsCoreCLR) { 'True' } else { 'False' }) + # ) Write-Verbose -Message "The path that was returned for the scope '$($property.ModuleScope)' is '$scopeModulePath'" -Verbose From 8dec2ad7620ec49f99612974ae33b7cbd60a0071 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 12:07:27 +0100 Subject: [PATCH 17/21] DEBUG 3 --- source/Classes/020.BootstrapPSResourceGet.ps1 | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index a709819..cf4687c 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -302,18 +302,19 @@ class BootstrapPSResourceGet : ResourceBase New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage } - Write-Verbose -Message "Evaluating if module is present in the scope '$($property.ModuleScope)'" -Verbose - $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope + Write-Verbose -Message "DEBUG: Running Get-PSModulePath: '$($property.ModuleScope)'" -Verbose Write-Verbose -Message ( - 'MyDocuments: {0}' -f [Environment]::GetFolderPath('MyDocuments') - ) + 'DEBUG: MyDocuments: {0}' -f [Environment]::GetFolderPath('MyDocuments') + ) -Verbose - # Write-Verbose -Message ( - # '$IsCoreCLR: {0}' -f (if( $IsCoreCLR) { 'True' } else { 'False' }) - # ) + Write-Verbose -Message ( + 'DEBUG: $IsCoreCLR: {0}' -f $global:IsCoreCLR + ) -Verbose + + $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope - Write-Verbose -Message "The path that was returned for the scope '$($property.ModuleScope)' is '$scopeModulePath'" -Verbose + Write-Verbose -Message "DEBUG: The path that was returned for the scope '$($property.ModuleScope)' is '$scopeModulePath'" -Verbose if ([System.String]::IsNullOrEmpty($scopeModulePath) -or -not (Test-Path -Path $scopeModulePath)) { From a6ee3dd3e421bc9b5626fa543bbb130816c056b4 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 16:57:12 +0100 Subject: [PATCH 18/21] DEBUG 5 --- source/Classes/020.BootstrapPSResourceGet.ps1 | 2 +- .../DSC_BootstrapPSResourceGet.Integration.Tests.ps1 | 6 +++--- tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index cf4687c..db85026 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -309,7 +309,7 @@ class BootstrapPSResourceGet : ResourceBase ) -Verbose Write-Verbose -Message ( - 'DEBUG: $IsCoreCLR: {0}' -f $global:IsCoreCLR + 'DEBUG: $IsCoreCLR: {0}' -f $script:IsCoreCLR ) -Verbose $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index 865311a..788bd3b 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -56,7 +56,7 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { } Context ('When using configuration <_>') -ForEach @( - "$($script:dscResourceName)_CurrentUser_Config" + "$($script:dscResourceName)_AllUsers_Config" ) { BeforeAll { $configurationName = $_ @@ -102,8 +102,8 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { -and $_.ResourceId -eq $resourceId } - #$resourceCurrentState.IsSingleInstance | Should -Be 'Yes' - $resourceCurrentState.ModuleScope | Should -Be 'CurrentUser' + $resourceCurrentState.IsSingleInstance | Should -Be 'Yes' + $resourceCurrentState.ModuleScope | Should -Be 'AllUsers' } It 'Should return ''True'' when Test-DscConfiguration is run' { diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 index ee5fac9..bb0b83a 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.config.ps1 @@ -25,9 +25,9 @@ else <# .SYNOPSIS - Bootstraps Microsoft.PowerShell.PSResourceGet to scope CurrentUser. + Bootstraps Microsoft.PowerShell.PSResourceGet to scope AllUsers. #> -Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config +Configuration DSC_BootstrapPSResourceGet_AllUsers_Config { Import-DscResource -ModuleName 'PSResourceGet.Bootstrap' @@ -36,7 +36,7 @@ Configuration DSC_BootstrapPSResourceGet_CurrentUser_Config BootstrapPSResourceGet 'Integration_Test' { IsSingleInstance = 'Yes' - ModuleScope = 'CurrentUser' + ModuleScope = 'AllUsers' } } } From 52d51b8ad26cb1dd094b051fbeda168df0b4a3ac Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 17:18:10 +0100 Subject: [PATCH 19/21] Fix unit test --- .../Classes/BootstrapPSResourceGet.Tests.ps1 | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 index 9936559..dcfcfcf 100644 --- a/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 +++ b/tests/Unit/Classes/BootstrapPSResourceGet.Tests.ps1 @@ -782,7 +782,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -792,7 +792,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -802,7 +802,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ScopePathInvalid -f 'CurrentUser', (Get-PSModulePath -Scope 'CurrentUser') - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -827,7 +827,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive - $mockErrorMessage += ' (Parameter ''Destination'')' + $mockErrorMessage += "*Parameter*Destination*" { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -837,7 +837,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive - $mockErrorMessage += ' (Parameter ''Destination'')' + $mockErrorMessage += "*Parameter*Destination*" { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -847,7 +847,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.DestinationInvalid -f $TestDrive - $mockErrorMessage += ' (Parameter ''Destination'')' + $mockErrorMessage += "*Parameter*Destination*" { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -869,7 +869,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' - $mockErrorMessage += ' (Parameter ''Version'')' + $mockErrorMessage += "*Parameter*Version*" { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -879,7 +879,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' - $mockErrorMessage += ' (Parameter ''Version'')' + $mockErrorMessage += "*Parameter*Version*" { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -889,7 +889,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.VersionInvalid -f '1.0.0:-WrongTag' - $mockErrorMessage += ' (Parameter ''Version'')' + $mockErrorMessage += "*Parameter*Version*" { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -909,7 +909,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter - $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + $mockErrorMessage += "*Parameter*ModuleScope, Destination*" { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -919,7 +919,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter - $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + $mockErrorMessage += "*Parameter*ModuleScope, Destination*" { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -929,7 +929,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.MissingRequiredParameter - $mockErrorMessage += ' (Parameter ''ModuleScope, Destination'')' + $mockErrorMessage += "*Parameter*ModuleScope, Destination*" { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -950,7 +950,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Get() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -960,7 +960,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Set() } | Should -Throw -ExpectedMessage $mockErrorMessage } @@ -970,7 +970,7 @@ Describe 'BootstrapPSResourceGet\AssertProperties()' -Tag 'AssertProperties' { InModuleScope -ScriptBlock { $mockErrorMessage = $script:mockBootstrapPSResourceGetInstance.localizedData.ModuleScopeInvalid -f 'InvalidScope' - $mockErrorMessage += ' (Parameter ''ModuleScope'')' + $mockErrorMessage += "*Parameter*ModuleScope*" { $script:mockBootstrapPSResourceGetInstance.Test() } | Should -Throw -ExpectedMessage $mockErrorMessage } From 1eb2577de1e8279b0cf773cf959585d46e91ce0a Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 17:42:13 +0100 Subject: [PATCH 20/21] Remove debug messages --- source/Classes/020.BootstrapPSResourceGet.ps1 | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/source/Classes/020.BootstrapPSResourceGet.ps1 b/source/Classes/020.BootstrapPSResourceGet.ps1 index db85026..973e2e4 100644 --- a/source/Classes/020.BootstrapPSResourceGet.ps1 +++ b/source/Classes/020.BootstrapPSResourceGet.ps1 @@ -302,20 +302,8 @@ class BootstrapPSResourceGet : ResourceBase New-InvalidArgumentException -ArgumentName 'ModuleScope' -Message $errorMessage } - Write-Verbose -Message "DEBUG: Running Get-PSModulePath: '$($property.ModuleScope)'" -Verbose - - Write-Verbose -Message ( - 'DEBUG: MyDocuments: {0}' -f [Environment]::GetFolderPath('MyDocuments') - ) -Verbose - - Write-Verbose -Message ( - 'DEBUG: $IsCoreCLR: {0}' -f $script:IsCoreCLR - ) -Verbose - $scopeModulePath = Get-PSModulePath -Scope $property.ModuleScope - Write-Verbose -Message "DEBUG: The path that was returned for the scope '$($property.ModuleScope)' is '$scopeModulePath'" -Verbose - if ([System.String]::IsNullOrEmpty($scopeModulePath) -or -not (Test-Path -Path $scopeModulePath)) { $errorMessage = $this.localizedData.ScopePathInvalid -f $property.ModuleScope, $scopeModulePath From 17e6d85ed938a58768dcd48b2a9e92809e8ba007 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 18 Feb 2024 17:43:13 +0100 Subject: [PATCH 21/21] Remove debug messages --- .../Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 index 788bd3b..9e319ac 100644 --- a/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 +++ b/tests/Integration/DSC_BootstrapPSResourceGet.Integration.Tests.ps1 @@ -83,7 +83,6 @@ Describe "$($script:dscResourceName)_Integration" -Tag @('DSC') { Verbose = $true Force = $true ErrorAction = 'Stop' - Debug = $true } Start-DscConfiguration @startDscConfigurationParameters