From e312353ec3f04d645aaa2bdfb619821828a08949 Mon Sep 17 00:00:00 2001 From: Nick G Date: Sun, 11 Dec 2022 13:14:29 -0500 Subject: [PATCH] BREAKING CHANGE: PSResourceRepository: Resource to manage PowerShell Package Repositories (#395) --- CHANGELOG.md | 4 + README.md | 12 + appveyor.yml | 69 ++ azure-pipelines.yml | 2 +- build.yaml | 1 + source/Classes/010.ResourceBase.ps1 | 260 +++++ source/Classes/020.PSResourceRepository.ps1 | 292 ++++++ source/ComputerManagementDsc.psd1 | 5 +- source/ComputerManagementDsc.psm1 | 1 + source/Enum/1.Ensure.ps1 | 9 + .../1-Register_PSGallery_Present.ps1 | 21 + .../2-Register_PSRepository_Present.ps1 | 25 + .../3-Repository_Absent.ps1 | 20 + source/Private/ConvertFrom-CompareResult.ps1 | 44 + source/Private/ConvertTo-Reason.ps1 | 119 +++ source/Private/Get-ClassName.ps1 | 55 + source/Private/Get-DscProperty.ps1 | 153 +++ source/Private/Get-LocalizedDataRecursive.ps1 | 87 ++ .../Test-ResourceDscPropertyIsAssigned.ps1 | 40 + .../Private/Test-ResourceHasDscProperty.ps1 | 62 ++ .../en-US/ComputerManagementDsc.strings.psd1 | 9 + .../en-US/PSResourceRepository.strings.psd1 | 18 + source/en-US/ResourceBase.strings.psd1 | 17 + source/prefix.ps1 | 7 + .../Classes/PSResourceRepository.config.ps1 | 89 ++ ...PSResourceRepository.integration.tests.ps1 | 236 +++++ .../Classes/PSResourceRepository.Tests.ps1 | 963 ++++++++++++++++++ 27 files changed, 2618 insertions(+), 2 deletions(-) create mode 100644 appveyor.yml create mode 100644 source/Classes/010.ResourceBase.ps1 create mode 100644 source/Classes/020.PSResourceRepository.ps1 create mode 100644 source/ComputerManagementDsc.psm1 create mode 100644 source/Enum/1.Ensure.ps1 create mode 100644 source/Examples/Resources/PSResourceRepository/1-Register_PSGallery_Present.ps1 create mode 100644 source/Examples/Resources/PSResourceRepository/2-Register_PSRepository_Present.ps1 create mode 100644 source/Examples/Resources/PSResourceRepository/3-Repository_Absent.ps1 create mode 100644 source/Private/ConvertFrom-CompareResult.ps1 create mode 100644 source/Private/ConvertTo-Reason.ps1 create mode 100644 source/Private/Get-ClassName.ps1 create mode 100644 source/Private/Get-DscProperty.ps1 create mode 100644 source/Private/Get-LocalizedDataRecursive.ps1 create mode 100644 source/Private/Test-ResourceDscPropertyIsAssigned.ps1 create mode 100644 source/Private/Test-ResourceHasDscProperty.ps1 create mode 100644 source/en-US/ComputerManagementDsc.strings.psd1 create mode 100644 source/en-US/PSResourceRepository.strings.psd1 create mode 100644 source/en-US/ResourceBase.strings.psd1 create mode 100644 source/prefix.ps1 create mode 100644 tests/Integration/Classes/PSResourceRepository.config.ps1 create mode 100644 tests/Integration/Classes/PSResourceRepository.integration.tests.ps1 create mode 100644 tests/Unit/Classes/PSResourceRepository.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index eede59e7..ebbf10ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,16 +12,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- PSResourceRepository + - New class-based resource to manage PowerShell Resource Repositories - Fixes [Issue #393](https://github.com/dsccommunity/ComputerManagementDsc/issues/393) - Computer - Support Options Parameter for domain join - Fixes [Issue #234](https://github.com/dsccommunity/ComputerManagementDsc/issues/234). - When joining a computer to a domain, existing AD computer objects will be deleted - Fixes [Issue #55](https://github.com/dsccommunity/ComputerManagementDsc/issues/55), [Issue #58](https://github.com/dsccommunity/ComputerManagementDsc/issues/58). ### Changed +- BREAKING CHANGE: Windows Management Framework 5.0 is required. - ComputerManagementDsc - The resource names were removed from the property `DscResourcesToExport` in the module manifest in the source folder as the built module is automatically updated with this information by the pipeline - Fixes [Issue #396](https://github.com/dsccommunity/ComputerManagementDsc/issues/396). + - Moved the build step of the pipeline to a Windows build worker when running in Azure DevOps. ## [8.5.0] - 2021-09-13 diff --git a/README.md b/README.md index ffbea5f6..f3b14933 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ The **ComputerManagementDsc** module contains the following resources: predictably handle the condition. - **PowerPlan**: This resource allows specifying a power plan to activate. - **PowerShellExecutionPolicy**: Specifies the desired PowerShell execution policy. +- **PSResourceRepository**: This resource manages PowerShellGet repositories. - **RemoteDesktopAdmin**: This resource will manage the remote desktop administration settings on a computer. - **ScheduledTask**: This resource is used to define basic run once or recurring @@ -74,3 +75,14 @@ This project has adopted [this code of conduct](CODE_OF_CONDUCT.md). For a full list of resources in ComputerManagementDsc and examples on their use, check out the [ComputerManagementDsc wiki](https://github.com/dsccommunity/ComputerManagementDsc/wiki). + +## Requirements +### Windows Management Framework 5.0 + +Required because this module now implements class-based resources. +Class-based resources can only work on computers with Windows +Management Framework 5.0 or above. + +### PSResourceRepository + +The resource `PSResourceRepository` requires that the PowerShell modules `PowerShellGet` and `PackageManagement` are already present on the target computer. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..a0cb621b --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,69 @@ +# HOW TO DEBUG: See start of each build run's output how to connect with RDP to the build server for debugging. +# See section on_finish last in this file on how to pause build and to keep RDP open. +# Look for each "DEBUG:" comment below how to change + +version: 1.0.{build} + +# Do not build on full releases. +skip_tags: true + +# See https://www.appveyor.com/docs/windows-images-software +# DEBUG: for debug purpose, comment and un-comment images as needed. +image: +- Visual Studio 2019 # Windows Server 2019 +#- Visual Studio 2017 # Windows Server 2016 +#- Visual Studio 2013 # Windows Server 2012 R2 + +environment: + Dummy: AnyValue + # DEBUG: Un-comment this to get the same password for the RDP session for each build + #APPVEYOR_RDP_PASSWORD: D5c1234! + +# DEBUG: If running on own AppVeyor project, comment the if-block below to run on all branches. +init: +- ps: | + # Only run for pull requests + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } + + iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + +# DEBUG: If running on own AppVeyor project, comment the if-block below to run on all branches. +install: +- ps: | + # Only run for pull requests + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } + + winrm quickconfig -quiet + +# DEBUG: If running on own AppVeyor project, comment the if-block below to run on all branches. +build_script: +- pwsh: | + # Only run for pull requests + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } + + # Build the module + ./build.ps1 -ResolveDependency -tasks build + +# DEBUG: If running on own AppVeyor project, comment the if-block below to run on all branches. +test_script: +- ps: | + # Only run for pull requests + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } + + ./build.ps1 -Tasks test -PesterScript 'tests/Integration' -CodeCoverageThreshold 0 + +deploy: off + +# DEBUG: Un-comment the line "$blockRdp = $true" so that build worker is kept up all of the 60 minutes. +# DEBUG: If running on own AppVeyor project, comment the if-block below to run on all branches. +on_finish: +- ps: | + # Only run for pull requests + if (-not $env:APPVEYOR_PULL_REQUEST_NUMBER) { Write-Host -ForegroundColor 'Yellow' -Object 'Not a pull request, skipping.'; return } + + <# + These two lines can also be added in one or more places somewhere in the integration tests to pause the test run. Continue + running the tests by deleting the file on the desktop that was created by "enable-rdp.ps1" when $blockRdp is $true. + #> + #$blockRdp = $true + #iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 83ce0a15..d0c66aa7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -24,7 +24,7 @@ stages: - job: Package_Module displayName: 'Package Module' pool: - vmImage: 'ubuntu-latest' + vmImage: 'windows-latest' steps: - pwsh: | dotnet tool install --global GitVersion.Tool diff --git a/build.yaml b/build.yaml index fc032085..a5ffa612 100644 --- a/build.yaml +++ b/build.yaml @@ -6,6 +6,7 @@ CopyPaths: - en-US - DSCResources - Modules +Prefix: prefix.ps1 Encoding: UTF8 VersionedOutputDirectory: true diff --git a/source/Classes/010.ResourceBase.ps1 b/source/Classes/010.ResourceBase.ps1 new file mode 100644 index 00000000..881c81fe --- /dev/null +++ b/source/Classes/010.ResourceBase.ps1 @@ -0,0 +1,260 @@ +<# + .SYNOPSIS + A class with methods that are equal for all class-based resources. + + .DESCRIPTION + A class with methods that are equal for all class-based resources. + + .NOTES + This class should be able to be inherited by all DSC resources. This class + shall not contain any DSC properties, neither shall it contain anything + specific to only a single resource. +#> + +class ResourceBase +{ + # Property for holding localization strings + hidden [System.Collections.Hashtable] $localizedData = @{} + + # Property for derived class to set properties that should not be enforced. + hidden [System.String[]] $ExcludeDscProperties = @() + + # Default constructor + ResourceBase() + { + <# + TODO: When this fails, for example when the localized string file is missing + the LCM returns the error 'Failed to create an object of PowerShell + class SqlDatabasePermission' instead of the actual error that occurred. + #> + $this.localizedData = Get-LocalizedDataRecursive -ClassName ($this | Get-ClassName -Recurse) + } + + [ResourceBase] Get() + { + $this.Assert() + + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.GetCurrentState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $getCurrentStateResult = $this.GetCurrentState($keyProperty) + + $dscResourceObject = [System.Activator]::CreateInstance($this.GetType()) + + # Set values returned from the derived class' GetCurrentState(). + foreach ($propertyName in $this.PSObject.Properties.Name) + { + if ($propertyName -in @($getCurrentStateResult.Keys)) + { + $dscResourceObject.$propertyName = $getCurrentStateResult.$propertyName + } + } + + $keyPropertyAddedToCurrentState = $false + + # Set key property values unless it was returned from the derived class' GetCurrentState(). + foreach ($propertyName in $keyProperty.Keys) + { + if ($propertyName -notin @($getCurrentStateResult.Keys)) + { + # Add the key value to the instance to be returned. + $dscResourceObject.$propertyName = $this.$propertyName + + $keyPropertyAddedToCurrentState = $true + } + } + + if (($this | Test-ResourceHasDscProperty -Name 'Ensure') -and -not $getCurrentStateResult.ContainsKey('Ensure')) + { + # Evaluate if we should set Ensure property. + if ($keyPropertyAddedToCurrentState) + { + <# + A key property was added to the current state, assume its because + the object did not exist in the current state. Set Ensure to Absent. + #> + $dscResourceObject.Ensure = [Ensure]::Absent + $getCurrentStateResult.Ensure = [Ensure]::Absent + } + else + { + $dscResourceObject.Ensure = [Ensure]::Present + $getCurrentStateResult.Ensure = [Ensure]::Present + } + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare($getCurrentStateResult, @()) + + <# + Return the correct values for Reasons property if the derived DSC resource + has such property and it hasn't been already set by GetCurrentState(). + #> + if (($this | Test-ResourceHasDscProperty -Name 'Reasons') -and -not $getCurrentStateResult.ContainsKey('Reasons')) + { + # Always return an empty array if all properties are in desired state. + $dscResourceObject.Reasons = $propertiesNotInDesiredState | + ConvertTo-Reason -ResourceName $this.GetType().Name + } + + # Return properties. + return $dscResourceObject + } + + [void] Set() + { + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.SetDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $propertiesToModify = $propertiesNotInDesiredState | ConvertFrom-CompareResult + + $propertiesToModify.Keys | + ForEach-Object -Process { + Write-Verbose -Message ($this.localizedData.SetProperty -f $_, $propertiesToModify.$_) + } + + <# + Call the Modify() method with the properties that should be enforced + and was not in desired state. + #> + $this.Modify($propertiesToModify) + } + else + { + Write-Verbose -Message $this.localizedData.NoPropertiesToSet + } + } + + [System.Boolean] Test() + { + # Get all key properties. + $keyProperty = $this | Get-DscProperty -Type 'Key' + + Write-Verbose -Message ($this.localizedData.TestDesiredState -f $this.GetType().Name, ($keyProperty | ConvertTo-Json -Compress)) + + $this.Assert() + + $isInDesiredState = $true + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + $propertiesNotInDesiredState = $this.Compare() + + if ($propertiesNotInDesiredState) + { + $isInDesiredState = $false + } + + if ($isInDesiredState) + { + Write-Verbose $this.localizedData.InDesiredState + } + else + { + Write-Verbose $this.localizedData.NotInDesiredState + } + + return $isInDesiredState + } + + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state, or $null if all enforced properties are in + desired state. + + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare() + { + # Get the current state, all properties except Read properties . + $currentState = $this.Get() | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') + + return $this.Compare($currentState, @()) + } + + <# + Returns a hashtable containing all properties that should be enforced and + are not in desired state, or $null if all enforced properties are in + desired state. + + This method should normally not be overridden. + #> + hidden [System.Collections.Hashtable[]] Compare([System.Collections.Hashtable] $currentState, [System.String[]] $excludeProperties) + { + # Get the desired state, all assigned properties that has an non-null value. + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue + + $CompareDscParameterState = @{ + CurrentValues = $currentState + DesiredValues = $desiredState + Properties = $desiredState.Keys + ExcludeProperties = ($excludeProperties + $this.ExcludeDscProperties) | Select-Object -Unique + IncludeValue = $true + # This is needed to sort complex types. + SortArrayValues = $true + } + + <# + Returns all enforced properties not in desires state, or $null if + all enforced properties are in desired state. + #> + return (Compare-DscParameterState @CompareDscParameterState) + } + + # This method should normally not be overridden. + hidden [void] Assert() + { + # Get the properties that has a non-null value and is not of type Read. + $desiredState = $this | Get-DscProperty -Type @('Key', 'Mandatory', 'Optional') -HasValue + + $this.AssertProperties($desiredState) + } + + <# + This method can be overridden if resource specific property asserts are + needed. The parameter properties will contain the properties that was + assigned a value. + #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('AvoidEmptyNamedBlocks', '')] + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the properties that should be enforced and that are not in desired + state. + #> + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.ModifyMethodNotImplemented + } + + <# + This method must be overridden by a resource. The parameter properties will + contain the key properties. + #> + hidden [System.Collections.Hashtable] GetCurrentState([System.Collections.Hashtable] $properties) + { + throw $this.localizedData.GetCurrentStateMethodNotImplemented + } +} diff --git a/source/Classes/020.PSResourceRepository.ps1 b/source/Classes/020.PSResourceRepository.ps1 new file mode 100644 index 00000000..e6808cf9 --- /dev/null +++ b/source/Classes/020.PSResourceRepository.ps1 @@ -0,0 +1,292 @@ +<# + .SYNOPSIS + A class for configuring PowerShell Repositories. + + .PARAMETER Ensure + If the repository should be present or absent on the server + being configured. Default values is 'Present'. + + .PARAMETER Name + Specifies the name of the repository to manage. + + .PARAMETER SourceLocation + Specifies the URI for discovering and installing modules from + this repository. A URI can be a NuGet server feed, HTTP, HTTPS, + FTP or file location. + + .PARAMETER Credential + Specifies credentials of an account that has rights to register a repository. + + .PARAMETER ScriptSourceLocation + Specifies the URI for the script source location. + + .PARAMETER PublishLocation + Specifies the URI of the publish location. For example, for + NuGet-based repositories, the publish location is similar + to http://someNuGetUrl.com/api/v2/Packages. + + .PARAMETER ScriptPublishLocation + Specifies the URI for the script publish location. + + .PARAMETER Proxy + Specifies the URI of the proxy to connect to this PSResourceRepository. + + .PARAMETER ProxyCredential + Specifies the Credential to connect to the PSResourceRepository proxy. + + .PARAMETER InstallationPolicy + Specifies the installation policy. Valid values are 'Trusted' + or 'Untrusted'. The default value is 'Untrusted'. + + .PARAMETER PackageManagementProvider + Specifies a OneGet package provider. Default value is 'NuGet'. + + .PARAMETER Default + Specifies whether to set the default properties for the default PSGallery PSRepository. + Default may only be used in conjunction with a PSRepositoryResource named PSGallery. + The properties SourceLocation, ScriptSourceLocation, PublishLocation, ScriptPublishLocation, Credential, + and PackageManagementProvider may not be used in conjunction with Default. + When the Default parameter is used, properties are not enforced when PSGallery properties are changed outside of Dsc. + + .EXAMPLE + Invoke-DscResource -ModuleName ComputerManagementDsc -Name PSResourceRepository -Method Get -Property @{ + Name = 'PSTestRepository' + SourceLocation = 'https://www.nuget.org/api/v2' + ScriptSourceLocation = 'https://www.nuget.org/api/v2/package/' + PublishLocation = 'https://www.nuget.org/api/v2/items/psscript/' + ScriptPublishLocation = 'https://www.nuget.org/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'NuGet' + } + This example shows how to call the resource using Invoke-DscResource. +#> +[DscResource()] +class PSResourceRepository : ResourceBase +{ + [DscProperty()] + [Ensure] + $Ensure = [Ensure]::Present + + [DscProperty(Key)] + [System.String] + $Name + + [DscProperty()] + [System.String] + $SourceLocation + + [DscProperty()] + [PSCredential] + $Credential + + [DscProperty()] + [System.String] + $ScriptSourceLocation + + [DscProperty()] + [System.String] + $PublishLocation + + [DscProperty()] + [System.String] + $ScriptPublishLocation + + [DscProperty()] + [System.String] + $Proxy + + [DscProperty()] + [pscredential] + $ProxyCredential + + [DscProperty()] + [ValidateSet('Untrusted', 'Trusted')] + [System.String] + $InstallationPolicy + + [DscProperty()] + [System.String] + $PackageManagementProvider + + [DscProperty()] + [Nullable[System.Boolean]] + $Default + + PSResourceRepository () : base () + { + # These properties will not be enforced. + $this.ExcludeDscProperties = @( + 'Name', + 'Default' + ) + } + + [PSResourceRepository] Get() + { + return ([ResourceBase]$this).Get() + } + + [void] Set() + { + ([ResourceBase]$this).Set() + } + + [Boolean] Test() + { + return ([ResourceBase] $this).Test() + } + + hidden [void] Modify([System.Collections.Hashtable] $properties) + { + $params = @{ + Name = $this.Name + } + + if ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq 'Absent' -and $this.Ensure -eq 'Absent') + { + # Ensure was not in desired state so the repository should be removed + Write-Verbose -Message ($this.localizedData.RemoveExistingRepository -f $this.Name) + + Unregister-PSRepository @params + + return + } + elseif ($properties.ContainsKey('Ensure') -and $properties.Ensure -eq 'Present' -and $this.Ensure -eq 'Present') + { + # Ensure was not in desired state so the repository should be created + $register = $true + + } + else + { + # Repository exist but one or more properties are not in desired state + $register = $false + } + + foreach ($key in $properties.Keys.Where({ $_ -ne 'Ensure' })) + { + $params[$key] = $properties.$key + } + + if ($register) + { + if ($this.Name -eq 'PSGallery') + { + Write-Verbose -Message ($this.localizedData.RegisterDefaultRepository -f $this.Name) + + Register-PSRepository -Default + + #* The user may have specified Proxy & Proxy Credential, or InstallationPolicy params + Set-PSRepository @params + } + else + { + if ([System.String]::IsNullOrEmpty($this.SourceLocation)) + { + $errorMessage = $this.LocalizedData.SourceLocationRequiredForRegistration + + New-InvalidArgumentException -ArgumentName 'SourceLocation' -Message $errorMessage + } + + if ($params.Keys -notcontains 'SourceLocation') + { + $params['SourceLocation'] = $this.SourceLocation + } + + Write-Verbose -Message ($this.localizedData.RegisterRepository -f $this.Name, $this.SourceLocation) + + Register-PSRepository @params + } + } + else + { + Write-Verbose -Message ($this.localizedData.UpdateRepository -f $this.Name, $this.SourceLocation) + + Set-PSRepository @params + } + } + + hidden [System.Collections.Hashtable] GetCurrentState ([System.Collections.Hashtable] $properties) + { + $returnValue = @{ + Ensure = [Ensure]::Absent + Name = $this.Name + } + + Write-Verbose -Message ($this.localizedData.GetTargetResourceMessage -f $this.Name) + + $repository = Get-PSRepository -Name $this.Name -ErrorAction SilentlyContinue + + if ($repository) + { + $returnValue.Ensure = [Ensure]::Present + $returnValue.SourceLocation = $repository.SourceLocation + $returnValue.ScriptSourceLocation = $repository.ScriptSourceLocation + $returnValue.PublishLocation = $repository.PublishLocation + $returnValue.ScriptPublishLocation = $repository.ScriptPublishLocation + $returnValue.Proxy = $repository.Proxy + $returnValue.ProxyCredential = $repository.ProxyCredental + $returnValue.InstallationPolicy = $repository.InstallationPolicy + $returnValue.PackageManagementProvider = $repository.PackageManagementProvider + } + else + { + Write-Verbose -Message ($this.localizedData.RepositoryNotFound -f $this.Name) + } + + return $returnValue + } + + <# + The parameter properties will contain the properties that was + assigned a value. + #> + hidden [void] AssertProperties([System.Collections.Hashtable] $properties) + { + Assert-Module -ModuleName PowerShellGet + Assert-Module -ModuleName PackageManagement + + $assertBoundParameterParameters = @{ + BoundParameterList = $properties + MutuallyExclusiveList1 = @( + 'Default' + ) + MutuallyExclusiveList2 = @( + 'SourceLocation' + 'PackageSourceLocation' + 'ScriptPublishLocation' + 'ScriptSourceLocation' + 'Credential' + 'PackageManagementProvider' + ) + } + + Assert-BoundParameter @assertBoundParameterParameters + + if ($this.Name -eq 'PSGallery') + { + if (-not $this.Default -and $this.Ensure -eq 'Present') + { + $errorMessage = $this.localizedData.NoDefaultSettingsPSGallery + + New-InvalidArgumentException -ArgumentName 'Default' -Message $errorMessage + } + } + else + { + if ($this.Default) + { + $errorMessage = $this.localizedData.DefaultSettingsNoPSGallery + + New-InvalidArgumentException -ArgumentName 'Default' -Message $errorMessage + } + } + + if ($this.ProxyCredential -and (-not $this.Proxy)) + { + $errorMessage = $this.localizedData.ProxyCredentialPassedWithoutProxyUri + + New-InvalidArgumentException -ArgumentName 'ProxyCredential' -Message $errorMessage + } + } +} diff --git a/source/ComputerManagementDsc.psd1 b/source/ComputerManagementDsc.psd1 index 2dda04f6..d29c9572 100644 --- a/source/ComputerManagementDsc.psd1 +++ b/source/ComputerManagementDsc.psd1 @@ -1,4 +1,7 @@ @{ + # Script module or binary module file associated with this manifest. + RootModule = 'ComputerManagementDsc.psm1' + # Version number of this module. moduleVersion = '0.0.1' @@ -18,7 +21,7 @@ Description = 'DSC resources for configuration of a Windows computer. These DSC resources allow you to perform computer management tasks, such as renaming the computer, joining a domain and scheduling tasks as well as configuring items such as virtual memory, event logs, time zones and power settings.' # Minimum version of the Windows PowerShell engine required by this module - PowerShellVersion = '4.0' + PowerShellVersion = '5.0' # Minimum version of the common language runtime (CLR) required by this module CLRVersion = '4.0' diff --git a/source/ComputerManagementDsc.psm1 b/source/ComputerManagementDsc.psm1 new file mode 100644 index 00000000..de514a79 --- /dev/null +++ b/source/ComputerManagementDsc.psm1 @@ -0,0 +1 @@ +# Note: This content will get replaced as part of the module build. Do not add to this file. diff --git a/source/Enum/1.Ensure.ps1 b/source/Enum/1.Ensure.ps1 new file mode 100644 index 00000000..4cfd9e73 --- /dev/null +++ b/source/Enum/1.Ensure.ps1 @@ -0,0 +1,9 @@ +<# + .SYNOPSIS + The possible states for the DSC resource parameter Ensure. +#> +enum Ensure +{ + Present + Absent +} diff --git a/source/Examples/Resources/PSResourceRepository/1-Register_PSGallery_Present.ps1 b/source/Examples/Resources/PSResourceRepository/1-Register_PSGallery_Present.ps1 new file mode 100644 index 00000000..3b7d005f --- /dev/null +++ b/source/Examples/Resources/PSResourceRepository/1-Register_PSGallery_Present.ps1 @@ -0,0 +1,21 @@ +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This configuration adds the PSGallery PSRepository to a machine +#> + +configuration Register_PSGallery_Present +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node localhost + { + PSResourceRepository 'Register PSGallery PSRepository' + { + Name = 'PSGallery' + Ensure = 'Present' + Default = $true + } + } +} diff --git a/source/Examples/Resources/PSResourceRepository/2-Register_PSRepository_Present.ps1 b/source/Examples/Resources/PSResourceRepository/2-Register_PSRepository_Present.ps1 new file mode 100644 index 00000000..ebd650dd --- /dev/null +++ b/source/Examples/Resources/PSResourceRepository/2-Register_PSRepository_Present.ps1 @@ -0,0 +1,25 @@ +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This configuration adds the PSRepository named MyPSRepository to a machine +#> + +configuration Register_PSRepository_Present +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node localhost + { + PSResourceRepository 'Register MyPSRepository PSRepository' + { + Name = 'MyPSRepository' + SourceLocation = 'https://www.mypsrepository.com/api/v2' + ScriptSourceLocation = 'https://www.mypsrepository.com/api/v2/package/' + PublishLocation = 'https://www.mypsrepository.com/api/v2/items/psscript' + ScriptPublishLocation = 'https://www.mypsrepository.com/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'NuGet' + } + } +} diff --git a/source/Examples/Resources/PSResourceRepository/3-Repository_Absent.ps1 b/source/Examples/Resources/PSResourceRepository/3-Repository_Absent.ps1 new file mode 100644 index 00000000..14a1ddae --- /dev/null +++ b/source/Examples/Resources/PSResourceRepository/3-Repository_Absent.ps1 @@ -0,0 +1,20 @@ +#Requires -module ComputerManagementDsc + +<# + .DESCRIPTION + This configuration removes the PSGallery PSRepository from a machine +#> + +configuration Repository_Absent +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node localhost + { + PSResourceRepository 'Remove PSGallery PSRepository' + { + Name = 'PSGallery' + Ensure = 'Absent' + } + } +} diff --git a/source/Private/ConvertFrom-CompareResult.ps1 b/source/Private/ConvertFrom-CompareResult.ps1 new file mode 100644 index 00000000..a917e70b --- /dev/null +++ b/source/Private/ConvertFrom-CompareResult.ps1 @@ -0,0 +1,44 @@ +<# + .SYNOPSIS + Returns a hashtable with property name and their expected value. + + .PARAMETER CompareResult + The result from Compare-DscParameterState. + + .EXAMPLE + ConvertFrom-CompareResult -CompareResult (Compare-DscParameterState) + + Returns a hashtable that contain all the properties not in desired state + and their expected value. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function ConvertFrom-CompareResult +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.Collections.Hashtable[]] + $CompareResult + ) + + begin + { + $returnHashtable = @{} + } + + process + { + $CompareResult | ForEach-Object -Process { + $returnHashtable[$_.Property] = $_.ExpectedValue + } + } + + end + { + return $returnHashtable + } +} diff --git a/source/Private/ConvertTo-Reason.ps1 b/source/Private/ConvertTo-Reason.ps1 new file mode 100644 index 00000000..65862736 --- /dev/null +++ b/source/Private/ConvertTo-Reason.ps1 @@ -0,0 +1,119 @@ +<# + .SYNOPSIS + Returns a array of the type `[Reason]`. + + .DESCRIPTION + This command converts the array of properties that is returned by the command + `Compare-DscParameterState`. The result is an array of the type `[Reason]` that + can be returned in a DSC resource's property **Reasons**. + + .PARAMETER Property + The result from the command Compare-DscParameterState. + + .PARAMETER ResourceName + The name of the resource. Will be used to populate the property Code with + the correct value. + + .EXAMPLE + ConvertTo-Reason -Property (Compare-DscParameterState) -ResourceName 'MyResource' + + Returns an array of `[Reason]` that contain all the properties not in desired + state and why a specific property is not in desired state. + + .OUTPUTS + [Reason[]] +#> +function ConvertTo-Reason +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('UseSyntacticallyCorrectExamples', '', Justification = 'Because the rule does not yet support parsing the code when the output type is not available. The ScriptAnalyzer rule UseSyntacticallyCorrectExamples will always error in the editor due to https://github.com/indented-automation/Indented.ScriptAnalyzerRules/issues/8.')] + [CmdletBinding()] + [OutputType([Reason[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [AllowEmptyCollection()] + [AllowNull()] + [System.Collections.Hashtable[]] + $Property, + + [Parameter(Mandatory = $true)] + [System.String] + $ResourceName + ) + + begin + { + # Always return an empty array if there are no properties to add. + $reasons = [Reason[]] @() + } + + process + { + foreach ($currentProperty in $Property) + { + if ($currentProperty.ExpectedValue -is [System.Enum]) + { + # Return the string representation of the value (instead of the numeric value). + $propertyExpectedValue = $currentProperty.ExpectedValue.ToString() + } + else + { + $propertyExpectedValue = $currentProperty.ExpectedValue + } + + if ($property.ActualValue -is [System.Enum]) + { + # Return the string representation of the value so that conversion to json is correct. + $propertyActualValue = $currentProperty.ActualValue.ToString() + } + else + { + $propertyActualValue = $currentProperty.ActualValue + } + + <# + In PowerShell 7 the command ConvertTo-Json returns 'null' on null + value, but not in Windows PowerShell. Switch to output empty string + if value is null. + #> + if ($PSVersionTable.PSEdition -eq 'Desktop') + { + if ($null -eq $propertyExpectedValue) + { + $propertyExpectedValue = '' + } + + if ($null -eq $propertyActualValue) + { + $propertyActualValue = '' + } + } + + # Convert the value to Json to be able to easily visualize complex types + $propertyActualValueJson = $propertyActualValue | ConvertTo-Json -Compress + $propertyExpectedValueJson = $propertyExpectedValue | ConvertTo-Json -Compress + + # If the property name contain the word Path, remove '\\' from path. + if ($currentProperty.Property -match 'Path') + { + $propertyActualValueJson = $propertyActualValueJson -replace '\\\\', '\' + $propertyExpectedValueJson = $propertyExpectedValueJson -replace '\\\\', '\' + } + + $reasons += [Reason] @{ + Code = '{0}:{0}:{1}' -f $ResourceName, $currentProperty.Property + # Convert the object to JSON to handle complex types. + Phrase = 'The property {0} should be {1}, but was {2}' -f @( + $currentProperty.Property, + $propertyExpectedValueJson, + $propertyActualValueJson + ) + } + } + } + + end + { + return $reasons + } +} diff --git a/source/Private/Get-ClassName.ps1 b/source/Private/Get-ClassName.ps1 new file mode 100644 index 00000000..06745e02 --- /dev/null +++ b/source/Private/Get-ClassName.ps1 @@ -0,0 +1,55 @@ +<# + .SYNOPSIS + Get the class name of the passed object, and optional an array with + all inherited classes. + + .PARAMETER InputObject + The object to be evaluated. + + .PARAMETER Recurse + Specifies if the class name of inherited classes shall be returned. The + recursive stops when the first object of the type `[System.Object]` is + found. + + .EXAMPLE + Get-ClassName -InputObject $this -Recurse + + Get the class name of the current instance and all the inherited (parent) + classes. + + .OUTPUTS + [System.String[]] +#> +function Get-ClassName +{ + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '', Justification = 'Because the rule does not understands that the command returns [System.String[]] when using , (comma) in the return statement')] + [CmdletBinding()] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Recurse + ) + + # Create a list of the inherited class names + $class = @($InputObject.GetType().FullName) + + if ($Recurse.IsPresent) + { + $parentClass = $InputObject.GetType().BaseType + + while ($parentClass -ne [System.Object]) + { + $class += $parentClass.FullName + + $parentClass = $parentClass.BaseType + } + } + + return , [System.String[]] $class +} diff --git a/source/Private/Get-DscProperty.ps1 b/source/Private/Get-DscProperty.ps1 new file mode 100644 index 00000000..481842c8 --- /dev/null +++ b/source/Private/Get-DscProperty.ps1 @@ -0,0 +1,153 @@ + +<# + .SYNOPSIS + Returns DSC resource properties that is part of a class-based DSC resource. + + .DESCRIPTION + Returns DSC resource properties that is part of a class-based DSC resource. + The properties can be filtered using name, type, or has been assigned a value. + + .PARAMETER InputObject + The object that contain one or more key properties. + + .PARAMETER Name + Specifies one or more property names to return. If left out all properties + are returned. + + .PARAMETER Type + Specifies one or more property types to return. If left out all property + types are returned. + + .PARAMETER HasValue + Specifies to return only properties that has been assigned a non-null value. + If left out all properties are returned regardless if there is a value + assigned or not. + + .EXAMPLE + Get-DscProperty -InputObject $this + + Returns all DSC resource properties of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Name @('MyProperty1', 'MyProperty2') + + Returns the specified DSC resource properties names of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Mandatory', 'Optional') + + Returns the specified DSC resource property types of the DSC resource. + + .EXAMPLE + Get-DscProperty -InputObject $this -Type @('Optional') -HasValue + + Returns the specified DSC resource property types of the DSC resource, + but only those properties that has been assigned a non-null value. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function Get-DscProperty +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter()] + [System.String[]] + $Name, + + [Parameter()] + [System.String[]] + $ExcludeName, + + [Parameter()] + [ValidateSet('Key', 'Mandatory', 'NotConfigurable', 'Optional')] + [System.String[]] + $Type, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue + ) + + $property = $InputObject.PSObject.Properties.Name | + Where-Object -FilterScript { + <# + Return all properties if $Name is not assigned, or if assigned + just those properties. + #> + (-not $Name -or $_ -in $Name) -and + + <# + Return all properties if $ExcludeName is not assigned. Skip + property if it is included in $ExcludeName. + #> + (-not $ExcludeName -or ($_ -notin $ExcludeName)) -and + + # Only return the property if it is a DSC property. + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.AttributeType.Name -eq 'DscPropertyAttribute' + } + ) + } + + if (-not [System.String]::IsNullOrEmpty($property)) + { + if ($PSBoundParameters.ContainsKey('Type')) + { + $propertiesOfType = @() + + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + <# + To simplify the code, ignoring that this will compare + MemberNAme against type 'Optional' which does not exist. + #> + $_.NamedArguments.MemberName -in $Type + } + ).NamedArguments.TypedValue.Value -eq $true + } + + # Include all optional parameter if it was requested. + if ($Type -contains 'Optional') + { + $propertiesOfType += $property | Where-Object -FilterScript { + $InputObject.GetType().GetMember($_).CustomAttributes.Where( + { + $_.NamedArguments.MemberName -notin @('Key', 'Mandatory', 'NotConfigurable') + } + ) + } + } + + $property = $propertiesOfType + } + } + + # Return a hashtable containing each key property and its value. + $getPropertyResult = @{} + + foreach ($currentProperty in $property) + { + if ($HasValue.IsPresent) + { + $isAssigned = Test-ResourceDscPropertyIsAssigned -Name $currentProperty -InputObject $InputObject + + if (-not $isAssigned) + { + continue + } + } + + $getPropertyResult.$currentProperty = $InputObject.$currentProperty + } + + return $getPropertyResult +} diff --git a/source/Private/Get-LocalizedDataRecursive.ps1 b/source/Private/Get-LocalizedDataRecursive.ps1 new file mode 100644 index 00000000..b32e18ae --- /dev/null +++ b/source/Private/Get-LocalizedDataRecursive.ps1 @@ -0,0 +1,87 @@ +<# + .SYNOPSIS + Get the localization strings data from one or more localization string files. + This can be used in classes to be able to inherit localization strings + from one or more parent (base) classes. + + The order of class names passed to parameter `ClassName` determines the order + of importing localization string files. First entry's localization string file + will be imported first, then next entry's localization string file, and so on. + If the second (or any consecutive) entry's localization string file contain a + localization string key that existed in a previous imported localization string + file that localization string key will be ignored. Making it possible for a + child class to override localization strings from one or more parent (base) + classes. + + .PARAMETER ClassName + An array of class names, normally provided by `Get-ClassName -Recurse`. + + .EXAMPLE + Get-LocalizedDataRecursive -ClassName $InputObject.GetType().FullName + + Returns a hashtable containing all the localized strings for the current + instance. + + .EXAMPLE + Get-LocalizedDataRecursive -ClassName (Get-ClassNamn -InputObject $this -Recurse) + + Returns a hashtable containing all the localized strings for the current + instance and any inherited (parent) classes. + + .OUTPUTS + [System.Collections.Hashtable] +#> +function Get-LocalizedDataRecursive +{ + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [System.String[]] + $ClassName + ) + + begin + { + $localizedData = @{} + } + + process + { + foreach ($name in $ClassName) + { + if ($name -match '\.psd1') + { + # Assume we got full file name. + $localizationFileName = $name + } + else + { + # Assume we only got class name. + $localizationFileName = '{0}.strings.psd1' -f $name + } + + Write-Debug -Message ('Importing localization data from {0}' -f $localizationFileName) + + # Get localized data for the class + $classLocalizationStrings = Get-LocalizedData -DefaultUICulture 'en-US' -FileName $localizationFileName -ErrorAction 'Stop' + + # Append only previously unspecified keys in the localization data + foreach ($key in $classLocalizationStrings.Keys) + { + if (-not $localizedData.ContainsKey($key)) + { + $localizedData[$key] = $classLocalizationStrings[$key] + } + } + } + } + + end + { + Write-Debug -Message ('Localization data: {0}' -f ($localizedData | ConvertTo-JSON)) + + return $localizedData + } +} diff --git a/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 new file mode 100644 index 00000000..5be5685d --- /dev/null +++ b/source/Private/Test-ResourceDscPropertyIsAssigned.ps1 @@ -0,0 +1,40 @@ +<# + .SYNOPSIS + Tests whether the class-based resource property is assigned a non-null value. + + .DESCRIPTION + Tests whether the class-based resource property is assigned a non-null value. + + .PARAMETER InputObject + Specifies the object that contain the property. + + .PARAMETER Name + Specifies the name of the property. + + .EXAMPLE + Test-ResourceDscPropertyIsAssigned -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property is assigned or not. + + .OUTPUTS + [System.Boolean] +#> +function Test-ResourceDscPropertyIsAssigned +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $isAssigned = -not ($null -eq $InputObject.$Name) + + return $isAssigned +} diff --git a/source/Private/Test-ResourceHasDscProperty.ps1 b/source/Private/Test-ResourceHasDscProperty.ps1 new file mode 100644 index 00000000..775582cf --- /dev/null +++ b/source/Private/Test-ResourceHasDscProperty.ps1 @@ -0,0 +1,62 @@ +<# + .SYNOPSIS + Tests whether the class-based resource has the specified property. + + .DESCRIPTION + Tests whether the class-based resource has the specified property. + + .PARAMETER InputObject + Specifies the object that should be tested for existens of the specified + property. + + .PARAMETER Name + Specifies the name of the property. + + .PARAMETER HasValue + Specifies if the property should be evaluated to have a non-value. If + the property exist but is assigned `$null` the command returns `$false`. + + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' + + Returns $true or $false whether the property exist or not. + + .EXAMPLE + Test-ResourceHasDscProperty -InputObject $this -Name 'MyDscProperty' -HasValue + + Returns $true if the property exist and is assigned a non-null value, if not + $false is returned. + + .OUTPUTS + [System.Boolean] +#> +function Test-ResourceHasDscProperty +{ + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + [PSObject] + $InputObject, + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $HasValue + ) + + $hasProperty = $false + + $isDscProperty = (Get-DscProperty @PSBoundParameters).ContainsKey($Name) + + if ($isDscProperty) + { + $hasProperty = $true + } + + return $hasProperty +} diff --git a/source/en-US/ComputerManagementDsc.strings.psd1 b/source/en-US/ComputerManagementDsc.strings.psd1 new file mode 100644 index 00000000..e016d8a7 --- /dev/null +++ b/source/en-US/ComputerManagementDsc.strings.psd1 @@ -0,0 +1,9 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + resource ComputerManagementDsc module. This file should only contain + localized strings for private and public functions. +#> + +ConvertFrom-StringData @' +'@ diff --git a/source/en-US/PSResourceRepository.strings.psd1 b/source/en-US/PSResourceRepository.strings.psd1 new file mode 100644 index 00000000..b5872d0f --- /dev/null +++ b/source/en-US/PSResourceRepository.strings.psd1 @@ -0,0 +1,18 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class PSResourceRepository. +#> + +ConvertFrom-StringData -StringData @' + GetTargetResourceMessage = Return the current state of the repository '{0}'. + RepositoryNotFound = The repository '{0}' was not found. + RemoveExistingRepository = Removing the repository '{0}'. + ProxyCredentialPassedWithoutProxyUri = Proxy Credential passed without Proxy Uri. + RegisterRepository = Registering repository '{0}' with SourceLocation '{1}'. + UpdateRepository = Updating repository '{0}' with SourceLocation '{1}'. + RegisterDefaultRepository = Registering default repository '{0}' with -Default parameter. + SourceLocationRequiredForRegistration = SourceLocation is a required parameter to register a repository. + NoDefaultSettingsPSGallery = The parameter Default must be set to True for a repository named PSGallery. + DefaultSettingsNoPSGallery = The parameter Default may only be used with repositories named PSGallery. +'@ diff --git a/source/en-US/ResourceBase.strings.psd1 b/source/en-US/ResourceBase.strings.psd1 new file mode 100644 index 00000000..05f4ff47 --- /dev/null +++ b/source/en-US/ResourceBase.strings.psd1 @@ -0,0 +1,17 @@ +<# + .SYNOPSIS + The localized resource strings in English (en-US) for the + class ResourceBase. +#> + +ConvertFrom-StringData @' + GetCurrentState = Getting the current state for resource '{0}' using the key property '{1}'. (RB0001) + TestDesiredState = Determining the current state for resource '{0}' using the key property '{1}'. (RB0002) + SetDesiredState = Setting the desired state for resource '{0}' using the key property '{1}'. (RB0003) + NotInDesiredState = The current state is not the desired state. (RB0004) + InDesiredState = The current state is the desired state. (RB0005) + SetProperty = The property '{0}' will be set to '{1}'. (RB0006) + NoPropertiesToSet = All properties are in desired state. (RB0007) + ModifyMethodNotImplemented = An override for the method Modify() is not implemented in the resource. (RB0008) + GetCurrentStateMethodNotImplemented = An override for the method GetCurrentState() is not implemented in the resource. (RB0009) +'@ diff --git a/source/prefix.ps1 b/source/prefix.ps1 new file mode 100644 index 00000000..fe46d7df --- /dev/null +++ b/source/prefix.ps1 @@ -0,0 +1,7 @@ +$script:dscResourceCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/DscResource.Common' +Import-Module -Name $script:dscResourceCommonModulePath + +$script:computerManagementDscCommonModulePath = Join-Path -Path $PSScriptRoot -ChildPath 'Modules/ComputerManagementDsc.Common' +Import-Module -Name $script:computerManagementDscCommonModulePath + +$script:localizedData = Get-LocalizedData -DefaultUICulture 'en-US' diff --git a/tests/Integration/Classes/PSResourceRepository.config.ps1 b/tests/Integration/Classes/PSResourceRepository.config.ps1 new file mode 100644 index 00000000..f21f6dc5 --- /dev/null +++ b/tests/Integration/Classes/PSResourceRepository.config.ps1 @@ -0,0 +1,89 @@ +$ConfigurationData = @{ + AllNodes = , @{ + NodeName = 'localhost' + CertificateFile = $Null + } + NonNodeData = @{ + PSResourceRepository_Create_Config = @{ + Name = 'PSTestGallery' + Ensure = 'Present' + SourceLocation = 'https://www.nuget.org/api/v2' + } + PSResourceRepository_Modify_Config = @{ + Name = 'PSTestGallery' + Ensure = 'Present' + SourceLocation = 'https://www.nuget.org/api/v2' + PublishLocation = 'https://www.nuget.org/api/v2/package/' + ScriptSourceLocation = 'https://www.nuget.org/api/v2/items/psscript/' + ScriptPublishLocation = 'https://www.nuget.org/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'NuGet' + } + PSResourceRepository_Remove_Config = @{ + Name = 'PSTestGallery' + Ensure = 'Absent' + } + } +} + +<# + .SYNOPSIS + Register a PSRepository +#> +configuration PSResourceRepository_Create_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + PSResourceRepository 'Integration_Test' + { + Name = $ConfigurationData.NonNodeData.PSResourceRepository_Create_Config.Name + Ensure = $ConfigurationData.NonNodeData.PSResourceRepository_Create_Config.Ensure + SourceLocation = $ConfigurationData.NonNodeData.PSResourceRepository_Create_Config.SourceLocation + } + } +} + +<# + .SYNOPSIS + Modifies an existing PSRepository +#> +configuration PSResourceRepository_Modify_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + PSResourceRepository 'Integration_Test' + { + Name = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.Name + Ensure = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.Ensure + SourceLocation = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.SourceLocation + ScriptSourceLocation = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.ScriptSourceLocation + PublishLocation = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.PublishLocation + ScriptPublishLocation = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.ScriptPublishLocation + InstallationPolicy = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.InstallationPolicy + PackageManagementProvider = $ConfigurationData.NonNodeData.PSResourceRepository_Modify_Config.PackageManagementProvider + } + } +} + +<# + .SYNOPSIS + Unregister an existing PSRepository +#> +configuration PSResourceRepository_Remove_Config +{ + Import-DscResource -ModuleName 'ComputerManagementDsc' + + node $AllNodes.NodeName + { + PSResourceRepository 'Integration_Test' + { + Name = $ConfigurationData.NonNodeData.PSResourceRepository_Remove_Config.Name + Ensure = $ConfigurationData.NonNodeData.PSResourceRepository_Remove_Config.Ensure + } + } +} + diff --git a/tests/Integration/Classes/PSResourceRepository.integration.tests.ps1 b/tests/Integration/Classes/PSResourceRepository.integration.tests.ps1 new file mode 100644 index 00000000..2a9e650b --- /dev/null +++ b/tests/Integration/Classes/PSResourceRepository.integration.tests.ps1 @@ -0,0 +1,236 @@ +$script:dscModuleName = 'ComputerManagementDsc' +$script:dscResourceFriendlyName = 'PSResourceRepository' +$script:dscResourceName = "$($script:dscResourceFriendlyName)" + +try +{ + Import-Module -Name DscResource.Test -Force -ErrorAction 'Stop' +} +catch [System.IO.FileNotFoundException] +{ + throw 'DscResource.Test module dependency not found. Please run ".\build.ps1 -Tasks build" first.' +} + +$initializationParams = @{ + DSCModuleName = $script:dscModuleName + DSCResourceName = $script:dscResourceName + ResourceType = 'Class' + TestType = 'Integration' +} +$script:testEnvironment = Initialize-TestEnvironment @initializationParams + +# Using try/finally to always cleanup. +try +{ + #region Integration Tests + $configurationFile = Join-Path -Path $PSScriptRoot -ChildPath "$($script:dscResourceName).config.ps1" + . $configurationFile + + Describe "$($script:dscResourceName)_Integration" { + BeforeAll { + $resourceId = "[$($script:dscResourceFriendlyName)]Integration_Test" + } + + $configurationName = "$($script:dscResourceName)_Create_Config" + + Context ('When using configuration {0}' -f $configurationName) { + + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 + } + + $shouldBeData = $ConfigurationData.NonNodeData.$configurationName + + # Key properties + $resourceCurrentState.Name | Should -Be $shouldBeData.Name + $resourceCurrentState.Ensure | Should -Be $shouldBeData.Ensure + $resourceCurrentState.SourceLocation | Should -Be $shouldBeData.SourceLocation + + # Optional Properties + $resourceCurrentState.Credential | Should -BeNullOrEmpty + $resourceCurrentState.Proxy | Should -BeNullOrEmpty + $resourceCurrentState.ProxyCredential | Should -BeNullOrEmpty + $resourceCurrentState.Default | Should -BeNullOrEmpty + + # Defaulted properties + $resourceCurrentState.PublishLocation | Should -Be 'https://www.nuget.org/api/v2/package/' + $resourceCurrentState.ScriptPublishLocation | Should -Be 'https://www.nuget.org/api/v2/package/' + $resourceCurrentState.ScriptSourceLocation | Should -BeNullOrEmpty + $resourceCurrentState.PackageManagementProvider | Should -Be 'NuGet' + $resourceCurrentState.InstallationPolicy | Should -Be 'Untrusted' + + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_Modify_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 + } + + $shouldBeData = $ConfigurationData.NonNodeData.$configurationName + + # Key properties + $resourceCurrentState.Name | Should -Be $shouldBeData.Name + + # Optional properties + $resourceCurrentState.SourceLocation | Should -Be $shouldBeData.SourceLocation + $resourceCurrentState.ScriptSourceLocation | Should -Be $shouldBeData.ScriptSourceLocation + $resourceCurrentState.PublishLocation | Should -Be $shouldBeData.PublishLocation + $resourceCurrentState.ScriptPublishLocation | Should -Be $shouldBeData.ScriptPublishLocation + $resourceCurrentState.InstallationPolicy | Should -Be $shouldBeData.InstallationPolicy + $resourceCurrentState.PackageManagementProvider | Should -Be $shouldBeData.PackageManagementProvider + $resourceCurrentState.Credential | Should -BeNullOrEmpty + $resourceCurrentState.Default | Should -BeNullOrEmpty + $resourceCurrentState.Proxy | Should -BeNullOrEmpty + $resourceCurrentState.ProxyCredential | Should -BeNullOrEmpty + + # Defaulted properties + $resourceCurrentState.Ensure | Should -Be $shouldBeData.Ensure + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + $configurationName = "$($script:dscResourceName)_Remove_Config" + + Context ('When using configuration {0}' -f $configurationName) { + It 'Should compile and apply the MOF without throwing' { + { + $configurationParameters = @{ + OutputPath = $TestDrive + 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 + } + + $shouldBeData = $ConfigurationData.NonNodeData.$configurationName + + # Key properties + $resourceCurrentState.Name | Should -Be $shouldBeData.Name + + # Defaulted properties + $resourceCurrentState.InstallationPolicy | Should -BeNullOrEmpty + $resourceCurrentState.SourceLocation | Should -BeNullOrEmpty + $resourceCurrentState.PackageManagementProvider | Should -BeNullOrEmpty + $resourceCurrentState.Credential | Should -BeNullOrEmpty + $resourceCurrentState.Default | Should -BeNullOrEmpty + $resourceCurrentState.PackageManagementProvider | Should -BeNullOrEmpty + $resourceCurrentState.Proxy | Should -BeNullOrEmpty + $resourceCurrentState.ProxyCredential | Should -BeNullOrEmpty + $resourceCurrentState.PublishLocation | Should -BeNullOrEmpty + $resourceCurrentState.ScriptPublishLocation | Should -BeNullOrEmpty + $resourceCurrentState.ScriptSourceLocation | Should -BeNullOrEmpty + $resourceCurrentState.SourceLocation | Should -BeNullOrEmpty + + # Ensure will be Absent + $resourceCurrentState.Ensure | Should -Be 'Absent' + } + + It 'Should return $true when Test-DscConfiguration is run' { + Test-DscConfiguration -Verbose | Should -Be 'True' + } + } + + Wait-ForIdleLcm -Clear + + } + #endregion +} +finally +{ + Restore-TestEnvironment -TestEnvironment $script:testEnvironment +} diff --git a/tests/Unit/Classes/PSResourceRepository.Tests.ps1 b/tests/Unit/Classes/PSResourceRepository.Tests.ps1 new file mode 100644 index 00000000..0bec3c3e --- /dev/null +++ b/tests/Unit/Classes/PSResourceRepository.Tests.ps1 @@ -0,0 +1,963 @@ +<# + .SYNOPSIS + Unit test for PSResourceRepository DSC resource. +#> + +# Suppressing this rule because Script Analyzer does not understand Pester's syntax. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] +param () + +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.' +} + +try +{ + $script:dscModuleName = 'ComputerManagementDsc' + + Import-Module -Name $script:dscModuleName + + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Mock:ModuleName'] = $script:dscModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $script:dscModuleName + + Describe 'PSResourceRepository' { + Context 'When class is instantiated' { + It 'Should not throw an exception' { + InModuleScope -ScriptBlock { + { [PSResourceRepository]::new() } | Should -Not -Throw + } + } + + It 'Should have a default or empty constructor' { + InModuleScope -ScriptBlock { + $instance = [PSResourceRepository]::new() + $instance | Should -Not -BeNullOrEmpty + } + } + + It 'Should be the correct type' { + InModuleScope -ScriptBlock { + $instance = [PSResourceRepository]::new() + $instance.GetType().Name | Should -Be 'PSResourceRepository' + } + } + } + } + + Describe 'PSResourceRepository\Get()' -Tag 'Get' { + Context 'When the system is in the desired state' { + Context 'When the repository is Present with default values' { + It 'Should return the correct result when the Repository is present and default params are passed' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + } + + <# + This mocks the method GetCurrentState(). + 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:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'Nuget' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -BeNullOrEmpty + $currentState.PublishLocation | Should -BeNullOrEmpty + $currentState.ScriptPublishLocation | Should -BeNullOrEmpty + $currentState.Proxy | Should -BeNullOrEmpty + $currentState.ProxyCredential | Should -BeNullOrEmpty + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Default | Should -BeNullOrEmpty + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + } + } + + It 'Should return the correct result when the Repository is Present and all properties are passed' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + Ensure = 'Present' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + $currentState.Proxy | Should -BeNullOrEmpty + $currentState.ProxyCredential | Should -BeNullOrEmpty + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Default | Should -BeNullOrEmpty + } + } + } + + Context 'When the respository should be Absent' { + It 'Should return the correct result when the Repository is Absent' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.SourceLocation | Should -BeNullOrEmpty + $currentState.Ensure | Should -Be 'Absent' + $currentState.InstallationPolicy | Should -BeNullOrEmpty + $currentState.ScriptSourceLocation | Should -BeNullOrEmpty + $currentState.PublishLocation | Should -BeNullOrEmpty + $currentState.ScriptPublishLocation | Should -BeNullOrEmpty + $currentState.Proxy | Should -BeNullOrEmpty + $currentState.ProxyCredential | Should -BeNullOrEmpty + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Default | Should -BeNullOrEmpty + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the repository is present but should be absent' { + It 'Should return the correct result when the Repository is present but should be absent' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + Ensure = 'Present' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + } + } + } + + Context 'When the repository is absent but should be present' { + It 'Should return the correct result when the Repository is absent but should be present' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + } + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Absent' + $currentState.SourceLocation | Should -BeNullOrEmpty + $currentState.ScriptSourceLocation | Should -BeNullOrEmpty + $currentState.PublishLocation | Should -BeNullOrEmpty + $currentState.ScriptPublishLocation | Should -BeNullOrEmpty + $currentState.InstallationPolicy | Should -BeNullOrEmpty + $currentState.PackageManagementProvider | Should -BeNullOrEmpty + } + } + } + + Context 'When the repository is present but not in the correct state' { + It 'Should return the correct results when the Repository is Present but not in the correct state' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + Ensure = 'Present' + } + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + Ensure = 'Present' + SourceLocation = 'https://www.notcorrect.com/api/v2' + ScriptSourceLocation = 'https://www.notcorrect.com/api/v2/items/psscript' + PublishLocation = 'https://www.notcorrect.com/api/v2/package/' + ScriptPublishLocation = 'https://www.notcorrect.com/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'Package' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.notcorrect.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.notcorrect.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Trusted' + $currentState.PackageManagementProvider | Should -Be 'Package' + } + } + + It 'Should return the correct results when the Repository is Present but should be Absent' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + $script:mockPSResourceRepositoryInstance | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'GetCurrentState' -Value { + return [System.Collections.Hashtable] @{ + Name = 'FakePSGallery' + Ensure = 'Present' + SourceLocation = 'https://www.notcorrect.com/api/v2' + ScriptSourceLocation = 'https://www.notcorrect.com/api/v2/items/psscript' + PublishLocation = 'https://www.notcorrect.com/api/v2/package/' + ScriptPublishLocation = 'https://www.notcorrect.com/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'Package' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + + $currentState = $script:mockPSResourceRepositoryInstance.Get() + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.notcorrect.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.notcorrect.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Trusted' + $currentState.PackageManagementProvider | Should -Be 'Package' + } + } + } + } + } + + Describe 'PSResourceRepository\Set()' -Tag 'Set' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + } | + # 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:mockPSResourceRepositoryInstance | + # 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:mockPSResourceRepositoryInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 0 + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance | + # Mock method Compare() which is called by the base method Set() + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'SourceLocation' + ExpectedValue = 'https://www.fakegallery.com/api/v2' + ActualValue = 'https://www.powershellgallery.com/api/v2' + } + } -PassThru | + Add-Member -Force -MemberType 'ScriptMethod' -Name 'AssertProperties' -Value { + return + } + } + } + + It 'Should call method Modify()' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance.Set() + + $script:mockMethodModifyCallCount | Should -Be 1 + } + } + } + } + + Describe 'PSResourceRepository\Test()' -Tag 'Test' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + } + } + + Context 'When the system is in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance | + # Mock method Compare() which is called by the base method Test () + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return $null + } + } + } + + It 'Should return $true' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance.Test() | Should -BeTrue + } + } + } + + Context 'When the system is not in the desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance | + # Mock method Compare() which is called by the base method Test () + Add-Member -Force -MemberType 'ScriptMethod' -Name 'Compare' -Value { + return @{ + Property = 'SourceLocation' + ExpectedValue = 'https://www.powershellgallery.com/api/v2' + ActualValue = 'https://www.incorrectpowershellgallery.com/api/v2' + } + } + } + } + + It 'Should return $false' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance.Test() | Should -BeFalse + } + } + } + } + + Describe 'PSResourceRepository\GetCurrentState()' -Tag 'GetCurrentState' { + Context 'When the system is in the desired state' { + Context 'When the repository should be Present' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + } + } + + It 'Should return the correct result when the Repository is present and all params are passed' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + $currentState.Proxy | Should -BeNullOrEmpty + $currentState.ProxyCredential | Should -BeNullOrEmpty + $currentState.Credential | Should -BeNullOrEmpty + $currentState.Default | Should -BeNullOrEmpty + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + + It 'Should return the correct result when the Repository is present and the minimum params are passed' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + } + } + + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState( + @{ + Name = 'FakePSGallery' + } + ) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the respository should be Absent' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return $null + } + } + + It 'Should return the correct result when the Repository is Absent' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.SourceLocation | Should -BeNullOrEmpty + $currentState.Ensure | Should -Be 'Absent' + $currentState.InstallationPolicy | Should -BeNullOrEmpty + $currentState.ScriptSourceLocation | Should -BeNullOrEmpty + $currentState.PublishLocation | Should -BeNullOrEmpty + $currentState.ScriptPublishLocation | Should -BeNullOrEmpty + $currentState.PackageManagementProvider | Should -BeNullOrEmpty + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + } + + Context 'When the system is not in the desired state' { + Context 'When the repository is present but should be absent' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + } + } + } + + It 'Should return the correct result when the Repository is present but should be absent' { + + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.powershellgallery.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.powershellgallery.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Untrusted' + $currentState.PackageManagementProvider | Should -Be 'NuGet' + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the repository is absent but should be present' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return $null + } + } + + It 'Should return the correct result when the Repository is absent but should be present' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + } + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Absent' + $currentState.SourceLocation | Should -BeNullOrEmpty + $currentState.ScriptSourceLocation | Should -BeNullOrEmpty + $currentState.PublishLocation | Should -BeNullOrEmpty + $currentState.ScriptPublishLocation | Should -BeNullOrEmpty + $currentState.InstallationPolicy | Should -BeNullOrEmpty + $currentState.PackageManagementProvider | Should -BeNullOrEmpty + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the repository is present but not in the correct state' { + BeforeEach { + Mock -CommandName Get-PSRepository -MockWith { + return @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.notcorrect.com/api/v2' + ScriptSourceLocation = 'https://www.notcorrect.com/api/v2/items/psscript' + PublishLocation = 'https://www.notcorrect.com/api/v2/package/' + ScriptPublishLocation = 'https://www.notcorrect.com/api/v2/package/' + InstallationPolicy = 'Trusted' + PackageManagementProvider = 'Package' + } + } + } + + It 'Should return the correct results when the Repository is Present but not in the correct state' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + ScriptSourceLocation = 'https://www.powershellgallery.com/api/v2/items/psscript' + PublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + ScriptPublishLocation = 'https://www.powershellgallery.com/api/v2/package/' + InstallationPolicy = 'Untrusted' + PackageManagementProvider = 'NuGet' + Ensure = 'Present' + } + + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.notcorrect.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.notcorrect.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Trusted' + $currentState.PackageManagementProvider | Should -Be 'Package' + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + + It 'Should return the correct results when the Repository is Present but should be Absent' { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + Ensure = 'Absent' + } + + $currentState = $script:mockPSResourceRepositoryInstance.GetCurrentState(@{ + Name = 'FakePSGallery' + }) + $currentState.Name | Should -Be 'FakePSGallery' + $currentState.Ensure | Should -Be 'Present' + $currentState.SourceLocation | Should -Be 'https://www.notcorrect.com/api/v2' + $currentState.ScriptSourceLocation | Should -Be 'https://www.notcorrect.com/api/v2/items/psscript' + $currentState.PublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.ScriptPublishLocation | Should -Be 'https://www.notcorrect.com/api/v2/package/' + $currentState.InstallationPolicy | Should -Be 'Trusted' + $currentState.PackageManagementProvider | Should -Be 'Package' + + Assert-MockCalled Get-PSRepository -Exactly -Times 1 -Scope It + } + } + } + } + } + + Describe 'PSResourceRepository\Modify()' -Tag 'Modify' { + Context 'When the system is not in the desired state and the repository is not registered' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + } + + Mock -CommandName Register-PSRepository + } + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Modify( + @{ + Ensure = 'Present' + SourceLocation = 'https://www.fakepsgallery.com/api/v2' + } + ) + } | Should -Not -Throw + + Assert-MockCalled -CommandName Register-PSRepository -Exactly -Times 1 -Scope It + } + } + + It 'Should call throw the correct InvalidArgumentException when SourceLocation is not set' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.SourceLocation = $null + $script:mockPSResourceRepositoryInstance.Modify( + @{ + Ensure = 'Present' + } + ) + } | Should -Throw -ExpectedMessage $script:mockPSResourceRepositoryInstance.localizedData.SourceLocationRequiredForRegistration + } + } + } + + Context 'When the system is not in the desired state and the repository is registered' { + Context 'When the repository is present but should be absent' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository]@{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Absent' + } + } + + Mock -CommandName Unregister-PSRepository + } + + It 'Should call the correct mock' { + + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Modify( + @{ + Ensure = 'Absent' + } + ) + } | Should -Not -Throw + + Assert-MockCalled -CommandName Unregister-PSRepository -Exactly -Times 1 -Scope It + } + } + } + + Context 'When the repository is present but not in desired state' { + BeforeAll { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{ + Name = 'FakePSGallery' + SourceLocation = 'https://www.powershellgallery.com/api/v2' + Ensure = 'Present' + } + } + + Mock -CommandName Set-PSRepository + } + + It 'Should call the correct mock' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Modify( + @{ + SourceLocation = 'https://www.fakepsgallery.com/api/v2' + } + ) + } | Should -Not -Throw + + Assert-MockCalled -CommandName Set-PSRepository -Exactly -Times 1 -Scope It + } + } + } + } + } + + Describe 'PSResourceRepository\AssertProperties()' -Tag 'AssertProperties' { + BeforeEach { + InModuleScope -ScriptBlock { + $script:mockPSResourceRepositoryInstance = [PSResourceRepository] @{} + } + } + Context 'When passing dependant parameters' { + Context 'When passing ProxyCredential without Proxy' { + It 'Should throw the correct error' { + InModuleScope -ScriptBlock { + { + $securePassword = New-Object -Type SecureString + $credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList 'USER', $securePassword + $script:mockPSResourceRepositoryInstance.ProxyCredental = $credential + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage $script:mockPSResourceRepositoryInstance.localizedData.ProxyCredentialPassedWithoutProxyUri + } + } + } + } + + Context 'When dealing with PSGallery parameters' { + It 'Should throw the correct error when Default true is not passed with name PSGallery' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $false + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage $script:mockPSResourceRepositoryInstance.localizedData.NoDefaultSettingsPSGallery + } + } + } + + It 'Should not throw when Default is not true and name is PSGallery but ensure is absent' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Ensure = 'Absent' + $script:mockPSResourceRepositoryInstance.Default = $false + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Not -Throw + } + } + } + + It 'Should throw the correct error when Default true is passed without the name PSGallery' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'NotTheDefaultPSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage $script:mockPSResourceRepositoryInstance.localizedData.DefaultSettingsNoPSGallery + } + } + } + + It 'Should throw the correct error when Default true is passed with SourceLocation' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.SourceLocation = 'https://notaurl.com/' + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + + It 'Should throw the correct error when Default true is passed with Credential' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.Credential = $credential + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + + It 'Should throw the correct error when Default true is passed with ScriptSourceLocation' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.ScriptSourceLocation = 'https://notaurl.com/' + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + + It 'Should throw the correct error when Default true is passed with PublishLocation' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.PublishLocation = 'https://notaurl.com/' + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + + It 'Should throw the correct error when Default true is passed with ScriptPublishLocation' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.ScriptPublishLocation = 'https://notaurl.com/' + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + + It 'Should throw the correct error when Default true is passed with PackageManagementProvider' { + InModuleScope -ScriptBlock { + { + $script:mockPSResourceRepositoryInstance.Name = 'PSGallery' + $script:mockPSResourceRepositoryInstance.Default = $true + $script:mockPSResourceRepositoryInstance.PackageManagementProvider = 'Package' + $script:mockPSResourceRepositoryInstance.AssertProperties() | Should -Throw -ExpectedMessage 'DRC0010' + } + } + } + } + } + } +} +finally +{ + $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 +}