From b7f792847e2df2e9b8a4dd223ca818920b0d1d93 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sun, 10 Feb 2019 17:39:07 +0100 Subject: [PATCH 1/3] Cleanup PSModule --- .MetaTestOptIn.json | 11 + CHANGELOG.md | 147 ++++--- .../MSFT_PSModule/MSFT_PSModule.psm1 | 383 +++++++++++------- .../en-US/MSFT_PSModule.strings.psd1 | 28 +- DSC/Examples/.gitattributes | 1 + DSC/Examples/README.md | 7 + .../1-PSModule_InstallModuleConfig.ps1 | 73 ++++ .../2-PSModule_InstallModuleTrustedConfig.ps1 | 76 ++++ ...Module_InstallModuleAllowClobberConfig.ps1 | 74 ++++ ...ule_InstallModuleSpecificVersionConfig.ps1 | 83 ++++ ..._InstallModuleWithinVersionRangeConfig.ps1 | 93 +++++ .../6-PSModule_UninstallModuleConfig.ps1 | 74 ++++ .../PowerShellGet.ResourceHelper.psm1 | 176 +++----- DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 | 32 +- .../PowerShellGet.ResourceHelper.Tests.ps1 | 96 ++--- appveyor.yml | 8 +- tools/build.psm1 | 66 ++- 17 files changed, 1018 insertions(+), 410 deletions(-) create mode 100644 .MetaTestOptIn.json create mode 100644 DSC/Examples/.gitattributes create mode 100644 DSC/Examples/README.md create mode 100644 DSC/Examples/Resources/PSModule/1-PSModule_InstallModuleConfig.ps1 create mode 100644 DSC/Examples/Resources/PSModule/2-PSModule_InstallModuleTrustedConfig.ps1 create mode 100644 DSC/Examples/Resources/PSModule/3-PSModule_InstallModuleAllowClobberConfig.ps1 create mode 100644 DSC/Examples/Resources/PSModule/4-PSModule_InstallModuleSpecificVersionConfig.ps1 create mode 100644 DSC/Examples/Resources/PSModule/5-PSModule_InstallModuleWithinVersionRangeConfig.ps1 create mode 100644 DSC/Examples/Resources/PSModule/6-PSModule_UninstallModuleConfig.ps1 diff --git a/.MetaTestOptIn.json b/.MetaTestOptIn.json new file mode 100644 index 00000000..30245283 --- /dev/null +++ b/.MetaTestOptIn.json @@ -0,0 +1,11 @@ +[ + "Common Tests - Validate Module Files", + "Common Tests - Validate Script Files", + "Common Tests - Validate Example Files", + "Common Tests - Validate Example Files To Be Published", + "Common Tests - Required Script Analyzer Rules", + "Common Tests - New Error-Level Script Analyzer Rules", + "Common Tests - Custom Script Analyzer Rules", + "Common Tests - Validate Markdown Links", + "Common Tests - Relative Path Length" +] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e30659f..ba16e567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,63 +2,72 @@ ## Unreleased -* Update the AppVeyor CI test pipeline with a new job to run tests for +- Update the AppVeyor CI test pipeline with a new job to run tests for the DSC resources, primarily for the resource `PSModule`. The new job uses the test framework used for the DSC Resource Kit, the [DscResource.Tests](https://github.com/PowerShell/DscResource.Tests) repository. -* Update .gitignore to ignore the [DscResource.Tests](https://github.com/PowerShell/DscResource.Tests) +- Update .gitignore to ignore the [DscResource.Tests](https://github.com/PowerShell/DscResource.Tests) test framework. When running unit test locally it is cloned into the local repository folder. -* Added module PowerShellGet.LocalizationHelper containing localization +- Added module PowerShellGet.LocalizationHelper containing localization helper functions for DSC resources, and unit tests for the helper functions. -* Moved helper functions for the DSC resource `PSModule` to the module +- Moved helper functions for the DSC resource `PSModule` to the module PowerShellGet.ResourceHelper. Added improved localization support, and code formatting against workspace VS Code settings. -* Changes to PSModule. - * Added improved localization support. - * Changed type on the parameters that had `[Switch]` to correctly use +- Changes to PSModule. + - Added improved localization support. + - Changed type on the parameters that had `[Switch]` to correctly use `Systen.Boolean` to match the schema.mof. - * Code formatting against workspace VS Code settings. - * Added unit tests. - * Added integration tests - * It is now possible to install a module and passing in `AllowClobber` + - Code formatting against workspace VS Code settings. + - Added unit tests. + - Added integration tests + - It is now possible to install a module and passing in `AllowClobber` when the modules package source is trusted (it already worked in other scenarios). -* Changed the AppVeyor CI build pipeline so it added the DSC resource +- Changed the AppVeyor CI build pipeline so it added the DSC resource `PSModule` and dependent helper modules (the `Modules` folder) to the AppVeyor artifact. ## 2.0.4 + Bug Fix -* Remove PSGallery availability checks (#374) + +- Remove PSGallery availability checks (#374) ## 2.0.3 + Bug fixes and Improvements -* Fix CommandAlreadyAvailable error for PackageManagement module (#333) -* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner) -* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin) -* Improvements for Catalog tests (#343) -* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88) -* Install modules with many commands faster (#351) + +- Fix CommandAlreadyAvailable error for PackageManagement module (#333) +- Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner) +- Expanded aliases for improved readability (#338) (Thanks @lazywinadmin) +- Improvements for Catalog tests (#343) +- Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88) +- Install modules with many commands faster (#351) New Features -* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci) + +- Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci) ## 2.0.1 + Bug fixes + - Resolved Publish-Module doesn't report error but fails to publish module (#316) - Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333) ## 2.0.0 + Breaking Change + - Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times. For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. - ## 1.6.7 Bug fixes + - Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313) - Updated error message in Save-Module cmdlet when the specified path is not accessible (#313) - Added few additional verbose messages (#313) @@ -66,35 +75,41 @@ Bug fixes ## 1.6.6 Dependency Updates -* Add dependency on version 4.1.0 or newer of NuGet.exe -* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe + +- Add dependency on version 4.1.0 or newer of NuGet.exe +- Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe Build and Code Cleanup Improvements -* Improved error handling in network connectivity tests. + +- Improved error handling in network connectivity tests. Bug fixes + - Change Update ModuleManifest so that prefix is not added to CmdletsToExport. - Change Update ModuleManifest so that parameters will not reset to default values. -- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets. +- Specify AllowPrereleaseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets. ## 1.6.5 New features -* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258) + +- Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258) - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline. Build and Code Cleanup Improvements -* Splitting of functions (#229) (Thanks @Benny1007) + +- Splitting of functions (#229) (Thanks @Benny1007) - Moves private functions into respective private folder. - Moves public functions as defined in PSModule.psd1 into respective public folder. - Removes all functions from PSModule.psm1 file. - Dot sources the functions from PSModule.psm1 file. - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file. -* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007) +- Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007) - Merged public and private functions into one .psm1 file to increase load time performance. Bug fixes + - Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201) - Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215) - Change Publish-* to allow version comparison instead of string comparison (#219) @@ -106,31 +121,35 @@ Bug fixes ## 1.6.0 New features -* Prerelease Version Support (#185) + +- Prerelease Version Support (#185) - Implemented prerelease versions functionality in PowerShellGet cmdlets. - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery. - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule) -* Enabled publish cmdlets on PWSH and Nano Server (#196) +- Enabled publish cmdlets on PWSH and Nano Server (#196) - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server. - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script . - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'* - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations. Breaking Change + - PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH. Bug fixes + - Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195) - Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts) ## 1.5.0.0 New features -* Added support for modules requiring license acceptance (#150) + +- Added support for modules requiring license acceptance (#150) - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance) -* Added version for REQUIREDSCRIPTS (#162) +- Added version for REQUIREDSCRIPTS (#162) - Enabled following scenarios for REQUIREDSCRIPTS - [1.0] - RequiredVersion - [1.0,2.0] - Min and Max Version @@ -138,55 +157,59 @@ New features - 1.0 - Min Version Bug fixes -* Fixed empty version value in nuspec (#157) + +- Fixed empty version value in nuspec (#157) ## 1.1.3.2 -* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153) -* Fixed for DateTime format serialization issue. (#141) -* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129) + +- Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153) +- Fixed for DateTime format serialization issue. (#141) +- Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129) ## 1.1.3.1 New features -* Added `PrivateData` field to ScriptFileInfo. (#119) + +- Added `PrivateData` field to ScriptFileInfo. (#119) Bug fixes -* Fixed Add-Type issue in v6.0.0-beta.1 release of PowerShellCore. (#125, #124) -* Install-Script -Scope CurrentUser PATH changes should not require a reboot for new PS processes. (#124) - - Made changes to broadcast the Environment variable changes, so that other processes pick changes to Environment variables without having to reboot or logoff/logon. -* Changed `Get-EnvironmentVariable` to get the unexpanded version of `%path%`. (#117) -* Refactor credential parameter propagation to sub-functions. (#104) -* Added credential parameter to subsequent calls of `Publish-Module/Script`. (#93) - - This is needed when a module is published that has the RequiredModules attribute in the manifest on a repository that does not have anonymous access because the required module lookups will fail. + +- Fixed Add-Type issue in v6.0.0-beta.1 release of PowerShellCore. (#125, #124) +- Install-Script -Scope CurrentUser PATH changes should not require a reboot for new PS processes. (#124) + - Made changes to broadcast the Environment variable changes, so that other processes pick changes to Environment variables without having to reboot or logoff/logon. +- Changed `Get-EnvironmentVariable` to get the unexpanded version of `%path%`. (#117) +- Refactor credential parameter propagation to sub-functions. (#104) +- Added credential parameter to subsequent calls of `Publish-Module/Script`. (#93) + - This is needed when a module is published that has the RequiredModules attribute in the manifest on a repository that does not have anonymous access because the required module lookups will fail. ## 1.1.2.0 Bug fixes -* Renamed `PublishModuleIsNotSupportedOnNanoServer` errorid to `PublishModuleIsNotSupportedOnPowerShellCoreEdition`. (#44) - - Also renamed `PublishScriptIsNotSupportedOnNanoServer` to `PublishScriptIsNotSupportedOnPowerShellCoreEdition`. -* Fixed an issue in `Update-Module` and `Update-Script` cmdlets to show proper version of current item being updated in `Confirm`/`WhatIf` message. (#44) -* Updated `Test-ModuleInstalled` function to return single module instead of multiple modules. (#44) -* Updated `ModuleCommandAlreadyAvailable` error message to include all conflicting commands instead of one. (#44) - - Corresponding changes to collect the complete set of conflicting commands from the being installed. - - Also ensured that conflicting commands from PSModule.psm1 are ignored in the command collision analysis as Get-Command includes the commands from current local scope as well. -* Fixed '[Test-ScriptFileInfo] Fails on *NIX newlines (LF vs. CRLF)' (#18) +- Renamed `PublishModuleIsNotSupportedOnNanoServer` errorid to `PublishModuleIsNotSupportedOnPowerShellCoreEdition`. (#44) + - Also renamed `PublishScriptIsNotSupportedOnNanoServer` to `PublishScriptIsNotSupportedOnPowerShellCoreEdition`. +- Fixed an issue in `Update-Module` and `Update-Script` cmdlets to show proper version of current item being updated in `Confirm`/`WhatIf` message. (#44) +- Updated `Test-ModuleInstalled` function to return single module instead of multiple modules. (#44) +- Updated `ModuleCommandAlreadyAvailable` error message to include all conflicting commands instead of one. (#44) + - Corresponding changes to collect the complete set of conflicting commands from the being installed. + - Also ensured that conflicting commands from PSModule.psm1 are ignored in the command collision analysis as Get-Command includes the commands from current local scope as well. +- Fixed '[Test-ScriptFileInfo] Fails on *NIX newlines (LF vs. CRLF)' (#18) ## 1.1.1.0 Bug fixes -* Fixed 'Update-Module fails with `ModuleAuthenticodeSignature` error for modules with signed PSD1'. (#12) (#8) -* Fixed 'Properties of `AdditionalMetadata` are case-sensitive'. #7 -* Changed `ErrorAction` to `Ignore` for few cmdlet usages as they should not show up in ErrorVariable. - - For example, error returned by `Get-Command Test-FileCatalog` should be ignored. +- Fixed 'Update-Module fails with `ModuleAuthenticodeSignature` error for modules with signed PSD1'. (#12) (#8) +- Fixed 'Properties of `AdditionalMetadata` are case-sensitive'. #7 +- Changed `ErrorAction` to `Ignore` for few cmdlet usages as they should not show up in ErrorVariable. + - For example, error returned by `Get-Command Test-FileCatalog` should be ignored. ## 1.1.0.0 -* Initial release from GitHub. -* PowerShellCore support. -* Security enhancements including the enforcement of catalog-signed modules during installation. -* Authenticated Repository support. -* Proxy Authentication support. -* Responses to a number of user requests and issues. +- Initial release from GitHub. +- PowerShellCore support. +- Security enhancements including the enforcement of catalog-signed modules during installation. +- Authenticated Repository support. +- Proxy Authentication support. +- Responses to a number of user requests and issues. diff --git a/DSC/DSCResources/MSFT_PSModule/MSFT_PSModule.psm1 b/DSC/DSCResources/MSFT_PSModule/MSFT_PSModule.psm1 index 230c2a13..ae5cfbe3 100644 --- a/DSC/DSCResources/MSFT_PSModule/MSFT_PSModule.psm1 +++ b/DSC/DSCResources/MSFT_PSModule/MSFT_PSModule.psm1 @@ -24,12 +24,7 @@ $helperName = 'PowerShellGet.ResourceHelper' $dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" Import-Module -Name $dscResourcesFolderFilePath -# DSC Resource for the $CurrentProviderName. -$CurrentProviderName = "PowerShellGet" - -# Return the current state of the resource. -function Get-TargetResource { - <# +<# .SYNOPSIS This DSC resource provides a mechanism to download PowerShell modules from the PowerShell Gallery and install it on your computer. @@ -61,66 +56,84 @@ function Get-TargetResource { .PARAMETER SkipPublisherCheck Allows the installation of modules that have not been catalog signed. +#> +function Get-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. #> - + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, + [Parameter()] [System.String] - $Repository = "PSGallery", + $Repository = 'PSGallery', + [Parameter()] [System.String] $RequiredVersion, + [Parameter()] [System.String] $MaximumVersion, + [Parameter()] [System.String] $MinimumVersion, + [Parameter()] [System.Boolean] $Force, + [Parameter()] [System.Boolean] $AllowClobber, + [Parameter()] [System.Boolean] $SkipPublisherCheck ) - # Initialize the $Ensure variable. - $ensure = 'Absent' + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + Repository = $Repository + Description = $null + Guid = $null + ModuleBase = $null + ModuleType = $null + Author = $null + InstalledVersion = $null + RequiredVersion = $RequiredVersion + MinimumVersion = $MinimumVersion + MaximumVersion = $MaximumVersion + Force = $Force + AllowClobber = $AllowClobber + SkipPublisherCheck = $SkipPublisherCheck + InstallationPolicy = $null + } + + Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("Name", "Repository", "MinimumVersion", "MaximumVersion", "RequiredVersion") + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') # Get the module with the right version and repository properties. $modules = Get-RightModule @extractedArguments -ErrorAction SilentlyContinue -WarningAction SilentlyContinue # If the module is found, the count > 0 - if ($modules.count -gt 0) { - $ensure = 'Present' + if ($modules.Count -gt 0) { + Write-Verbose -Message ($localizedData.ModuleFound -f $Name) - Write-Verbose -Message ($localizedData.ModuleFound -f $($Name)) - } - else { - Write-Verbose -Message ($localizedData.ModuleNotFound -f $($Name)) - } - - Write-Debug -Message "Ensure of $($Name) module is $($ensure)" - - if ($ensure -eq 'Absent') { - $returnValue = @{ - Ensure = $ensure - Name = $Name - } - } - else { # Find a module with the latest version and return its properties. $latestModule = $modules[0] @@ -134,25 +147,31 @@ function Get-TargetResource { $repositoryName = Get-ModuleRepositoryName -Module $latestModule -ErrorAction SilentlyContinue -WarningAction SilentlyContinue $installationPolicy = Get-InstallationPolicy -RepositoryName $repositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - $returnValue = @{ - Ensure = $ensure - Name = $Name - Repository = $repositoryName - Description = $latestModule.Description - Guid = $latestModule.Guid - ModuleBase = $latestModule.ModuleBase - ModuleType = $latestModule.ModuleType - Author = $latestModule.Author - InstalledVersion = $latestModule.Version - InstallationPolicy = if ($installationPolicy) {"Trusted"}else {"Untrusted"} + if ($installationPolicy) { + $installationPolicyReturnValue = 'Trusted' + } + else { + $installationPolicyReturnValue = 'Untrusted' } + + $returnValue.Ensure = 'Present' + $returnValue.Repository = $repositoryName + $returnValue.Description = $latestModule.Description + $returnValue.Guid = $latestModule.Guid + $returnValue.ModuleBase = $latestModule.ModuleBase + $returnValue.ModuleType = $latestModule.ModuleType + $returnValue.Author = $latestModule.Author + $returnValue.InstalledVersion = $latestModule.Version + $returnValue.InstallationPolicy = $installationPolicyReturnValue + } + else { + Write-Verbose -Message ($localizedData.ModuleNotFound -f $Name) } + return $returnValue } -function Test-TargetResource { - <# +<# .SYNOPSIS This DSC resource provides a mechanism to download PowerShell modules from the PowerShell Gallery and install it on your computer. @@ -190,50 +209,66 @@ function Test-TargetResource { .PARAMETER SkipPublisherCheck Allows the installation of modules that have not been catalog signed. +#> +function Test-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. #> - + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] [CmdletBinding()] [OutputType([System.Boolean])] param ( - [ValidateSet("Present", "Absent")] + [Parameter()] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, + [Parameter()] [System.String] - $Repository = "PSGallery", + $Repository = 'PSGallery', - [ValidateSet("Trusted", "Untrusted")] + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] [System.String] - $InstallationPolicy = "Untrusted", + $InstallationPolicy = 'Untrusted', + [Parameter()] [System.String] $RequiredVersion, + [Parameter()] [System.String] $MaximumVersion, + [Parameter()] [System.String] $MinimumVersion, + [Parameter()] [System.Boolean] $Force, + [Parameter()] [System.Boolean] $AllowClobber, + [Parameter()] [System.Boolean] $SkipPublisherCheck ) - Write-Debug -Message "Calling Test-TargetResource" + Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("Name", "Repository", "MinimumVersion", "MaximumVersion", "RequiredVersion") + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') $status = Get-TargetResource @extractedArguments @@ -248,8 +283,7 @@ function Test-TargetResource { } } -function Set-TargetResource { - <# +<# .SYNOPSIS This DSC resource provides a mechanism to download PowerShell modules from the PowerShell Gallery and install it on your computer. @@ -287,69 +321,87 @@ function Set-TargetResource { .PARAMETER SkipPublisherCheck Allows the installation of modules that have not been catalog signed. +#> +function Set-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. #> - + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-TryStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-CatchClause', '')] [CmdletBinding()] param ( - [ValidateSet("Present", "Absent")] + [Parameter()] + [ValidateSet('Present', 'Absent')] [System.String] - $Ensure = "Present", + $Ensure = 'Present', - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String] $Name, + [Parameter()] [System.String] - $Repository = "PSGallery", + $Repository = 'PSGallery', - [ValidateSet("Trusted", "Untrusted")] + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] [System.String] - $InstallationPolicy = "Untrusted", + $InstallationPolicy = 'Untrusted', + [Parameter()] [System.String] $RequiredVersion, + [Parameter()] [System.String] $MaximumVersion, + [Parameter()] [System.String] $MinimumVersion, + [Parameter()] [System.Boolean] $Force, + [Parameter()] [System.Boolean] $AllowClobber, + [Parameter()] [System.Boolean] $SkipPublisherCheck ) # Validate the repository argument - if ($PSBoundParameters.ContainsKey("Repository")) { - ValidateArgument -Argument $Repository -Type "PackageSource" -ProviderName $CurrentProviderName -Verbose + if ($PSBoundParameters.ContainsKey('Repository')) { + Test-ParameterValue -Value $Repository -Type 'PackageSource' -ProviderName 'PowerShellGet' -Verbose } - if ($Ensure -ieq "Present") { + if ($Ensure -ieq 'Present') { # Version check - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("MinimumVersion", "MaximumVersion", "RequiredVersion") - - ValidateVersionArgument @extractedArguments + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('MinimumVersion', 'MaximumVersion', 'RequiredVersion') - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("Name", "Repository", "MinimumVersion", "MaximumVersion", "RequiredVersion") + Test-VersionParameter @extractedArguments - Write-Verbose -Message ($localizedData.StartFindmodule -f $($Name)) + try { + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') - $modules = Find-Module @extractedArguments -ErrorVariable ev + Write-Verbose -Message ($localizedData.StartFindModule -f $Name) - if (-not $modules) { - ThrowError -ExceptionName "System.InvalidOperationException" ` - -ExceptionMessage ($localizedData.ModuleNotFoundInRepository -f $Name, $ev.Exception) ` - -ErrorId "ModuleNotFoundInRepository" ` - -ErrorCategory InvalidOperation + $modules = Find-Module @extractedArguments -ErrorVariable ev + } + catch { + $errorMessage = $script:localizedData.ModuleNotFoundInRepository -f $Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } $trusted = $null @@ -366,83 +418,77 @@ function Set-TargetResource { } } - # The respository is trusted, so we install it. - if ($trusted) { - Write-Verbose -Message ($localizedData.StartInstallModule -f $Name, $moduleFound.Version.toString(), $moduleFound.Repository) + try { + # The repository is trusted, so we install it. + if ($trusted) { + Write-Verbose -Message ($localizedData.StartInstallModule -f $Name, $moduleFound.Version.toString(), $moduleFound.Repository) - # Extract the installation options. - $extractedSwitches = ExtractArguments -FunctionBoundParameters $PSBoundParameters -ArgumentNames ("Force", "AllowClobber", "SkipPublisherCheck") + # Extract the installation options. + $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('Force', 'AllowClobber', 'SkipPublisherCheck') - $moduleFound | Install-Module @extractedSwitches -ErrorVariable ev - } - # The repository is untrusted but user's installation policy is trusted, so we install it with a warning. - elseif ($InstallationPolicy -ieq 'Trusted') { - Write-Warning -Message ($localizedData.InstallationPolicyWarning -f $Name, $modules[0].Repository, $InstallationPolicy) + $moduleFound | Install-Module @extractedSwitches + } + # The repository is untrusted but user's installation policy is trusted, so we install it with a warning. + elseif ($InstallationPolicy -ieq 'Trusted') { + Write-Warning -Message ($localizedData.InstallationPolicyWarning -f $Name, $modules[0].Repository, $InstallationPolicy) - # Extract installation options (Force implied by InstallationPolicy). - $extractedSwitches = ExtractArguments -FunctionBoundParameters $PSBoundParameters -ArgumentNames ("AllowClobber", "SkipPublisherCheck") + # Extract installation options (Force implied by InstallationPolicy). + $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('AllowClobber', 'SkipPublisherCheck') - # If all the repositories are untrusted, we choose the first one. - $modules[0] | Install-Module @extractedSwitches -Force -ErrorVariable ev - } - # Both user and repository is untrusted - else { - ThrowError -ExceptionName "System.InvalidOperationException" ` - -ExceptionMessage ($localizedData.InstallationPolicyFailed -f $InstallationPolicy, "Untrusted") ` - -ErrorId "InstallationPolicyFailed" ` - -ErrorCategory InvalidOperation - } + # If all the repositories are untrusted, we choose the first one. + $modules[0] | Install-Module @extractedSwitches -Force + } + # Both user and repository is untrusted + else { + $errorMessage = $script:localizedData.InstallationPolicyFailed -f $InstallationPolicy, 'Untrusted' + New-InvalidOperationException -Message $errorMessage + } - if ($ev) { - ThrowError -ExceptionName "System.InvalidOperationException" ` - -ExceptionMessage ($localizedData.FailtoInstall -f $Name, $ev.Exception) ` - -ErrorId "FailtoInstall" ` - -ErrorCategory InvalidOperation + Write-Verbose -Message ($localizedData.InstalledSuccess -f $Name) } - else { - Write-Verbose -Message ($localizedData.InstalledSuccess -f $($Name)) + catch { + $errorMessage = $script:localizedData.FailToInstall -f $Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } } # Ensure=Absent else { - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("Name", "Repository", "MinimumVersion", "MaximumVersion", "RequiredVersion") + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') # Get the module with the right version and repository properties. - $modules = Get-RightModule @extractedArguments -ErrorVariable ev + $modules = Get-RightModule @extractedArguments - if ((-not $modules) -or $ev) { - ThrowError -ExceptionName "System.InvalidOperationException" ` - -ExceptionMessage ($localizedData.ModuleWithRightPropertyNotFound -f $Name, $ev.Exception) ` - -ErrorId "ModuleWithRightPropertyNotFound" ` - -ErrorCategory InvalidOperation + if (-not $modules) { + $errorMessage = $script:localizedData.ModuleWithRightPropertyNotFound -f $Name + New-InvalidOperationException -Message $errorMessage } foreach ($module in $modules) { # Get the path where the module is installed. $path = $module.ModuleBase - Write-Verbose -Message ($localizedData.StartUnInstallModule -f $($Name)) + Write-Verbose -Message ($localizedData.StartUnInstallModule -f $Name) - # There is no Uninstall-Module cmdlet exists, so we will remove the ModuleBase folder as an uninstall operation. - Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -Recurse -ErrorVariable ev + try { + <# + There is no Uninstall-Module cmdlet for Windows PowerShell 4.0, + so we will remove the ModuleBase folder as an uninstall operation. + #> + Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -Recurse - if ($ev) { - ThrowError -ExceptionName "System.InvalidOperationException" ` - -ExceptionMessage ($localizedData.FailtoUninstall -f $module.Name, $ev.Exception) ` - -ErrorId "FailtoUninstall" ` - -ErrorCategory InvalidOperation + Write-Verbose -Message ($localizedData.UnInstalledSuccess -f $module.Name) } - else { - Write-Verbose -Message ($localizedData.UnInstalledSuccess -f $($module.Name)) + catch { + $errorMessage = $script:localizedData.FailToUninstall -f $module.Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ } } # foreach } # Ensure=Absent } -Function Get-RightModule { - <# +<# .SYNOPSIS This is a helper function. It returns the modules that meet the specified versions and the repository requirements. @@ -460,25 +506,37 @@ Function Get-RightModule { .PARAMETER Repository Specifies the name of the module source repository where the module can be found. +#> +function Get-RightModule { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. #> - + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Name, + [Parameter()] [System.String] $RequiredVersion, + [Parameter()] [System.String] $MinimumVersion, + [Parameter()] [System.String] $MaximumVersion, + [Parameter()] [System.String] $Repository ) @@ -491,11 +549,14 @@ Function Get-RightModule { return $null } - # As Get-Module does not take RequiredVersion, MinimumVersion, MaximumVersion, or Repository, below we need to check - # whether the modules are containing the right version and repository location. + <# + As Get-Module does not take RequiredVersion, MinimumVersion, MaximumVersion, or Repository, + below we need to check whether the modules are containing the right version and repository + location. + #> - $extractedArguments = ExtractArguments -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ("MaximumVersion", "MinimumVersion", "RequiredVersion") + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('MaximumVersion', 'MinimumVersion', 'RequiredVersion') $returnVal = @() foreach ($m in $modules) { @@ -508,22 +569,22 @@ Function Get-RightModule { } # Case 2 - a user provides RequiredVersion - elseif ($extractedArguments.ContainsKey("RequiredVersion")) { - # Check if it matches with the installedversion - $versionMatch = ($installedVersion -eq [System.Version]$RequiredVersion) + elseif ($extractedArguments.ContainsKey('RequiredVersion')) { + # Check if it matches with the installed version + $versionMatch = ($installedVersion -eq [System.Version] $RequiredVersion) } else { # Case 3 - a user provides MinimumVersion - if ($extractedArguments.ContainsKey("MinimumVersion")) { - $versionMatch = ($installedVersion -ge [System.Version]$extractedArguments['MinimumVersion']) + if ($extractedArguments.ContainsKey('MinimumVersion')) { + $versionMatch = ($installedVersion -ge [System.Version] $extractedArguments['MinimumVersion']) } # Case 4 - a user provides MaximumVersion - if ($extractedArguments.ContainsKey("MaximumVersion")) { - $isLessThanMax = ($installedVersion -le [System.Version]$extractedArguments['MaximumVersion']) + if ($extractedArguments.ContainsKey('MaximumVersion')) { + $isLessThanMax = ($installedVersion -le [System.Version] $extractedArguments['MaximumVersion']) - if ($extractedArguments.ContainsKey("MinimumVersion")) { + if ($extractedArguments.ContainsKey('MinimumVersion')) { $versionMatch = $versionMatch -and $isLessThanMax } else { @@ -534,16 +595,16 @@ Function Get-RightModule { # Case 5 - Both MinimumVersion and MaximumVersion are provided. It's covered by the above. # Do not return $false yet to allow the foreach to continue if (-not $versionMatch) { - Write-Verbose -Message ($localizedData.VersionMismatch -f $($Name), $($installedVersion)) + Write-Verbose -Message ($localizedData.VersionMismatch -f $Name, $installedVersion) $versionMatch = $false } } # Case 6 - Version matches but need to check if the module is from the right repository. if ($versionMatch) { - # a user does not provide Repository, we are good - if (-not $PSBoundParameters.ContainsKey("Repository")) { - Write-Verbose -Message ($localizedData.ModuleFound -f "$($Name) $($installedVersion)") + # A user does not provide Repository, we are good + if (-not $PSBoundParameters.ContainsKey('Repository')) { + Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") $returnVal += $m } else { @@ -551,7 +612,7 @@ Function Get-RightModule { $sourceName = Get-ModuleRepositoryName -Module $m if ($Repository -ieq $sourceName) { - Write-Verbose -Message ($localizedData.ModuleFound -f "$($Name) $($installedVersion)") + Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") $returnVal += $m } else { @@ -560,33 +621,43 @@ Function Get-RightModule { } } } # foreach + return $returnVal } -Function Get-ModuleRepositoryName { - <# +<# .SYNOPSIS This is a helper function that returns the module's repository name. .PARAMETER Module Specifies the name of the PowerShell module. +#> +function Get-ModuleRepositoryName { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. #> - + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Object] $Module ) - # RepositorySourceLocation property is supported in PS V5 only. To work with the earlier PS version, we need to do a different way. - # PSGetModuleInfo.xml exists for any PS modules downloaded through PSModule provider. - $psGetModuleInfoFileName = "PSGetModuleInfo.xml" + <# + RepositorySourceLocation property is supported in PS V5 only. To work with the earlier + PowerShell version, we need to do a different way. PSGetModuleInfo.xml exists for any + PowerShell modules downloaded through PSModule provider. + #> + $psGetModuleInfoFileName = 'PSGetModuleInfo.xml' $psGetModuleInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $Module.ModuleBase -ChildPath $psGetModuleInfoFileName - Write-Verbose -Message ($localizedData.FoundModulePath -f $($psGetModuleInfoPath)) + Write-Verbose -Message ($localizedData.FoundModulePath -f $psGetModuleInfoPath) if (Microsoft.PowerShell.Management\Test-path -Path $psGetModuleInfoPath) { $psGetModuleInfo = Microsoft.PowerShell.Utility\Import-Clixml -Path $psGetModuleInfoPath @@ -594,5 +665,3 @@ Function Get-ModuleRepositoryName { return $psGetModuleInfo.Repository } } - -Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource diff --git a/DSC/DSCResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 b/DSC/DSCResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 index 0eb5b1f7..c43e7661 100644 --- a/DSC/DSCResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 +++ b/DSC/DSCResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 @@ -11,26 +11,26 @@ # # culture = "en-US" ConvertFrom-StringData -StringData @' -###PSLOC - FailtoUninstall = Failed to uninstall the module '{0}'. Message: {1} - FailtoInstall = Failed to install the module '{0}'. Message: {1} + FailToUninstall = Failed to uninstall the module '{0}'. + FailToInstall = Failed to install the module '{0}'. InDesiredState = Resource '{0}' is in the desired state. NotInDesiredState = Resource '{0}' is not in the desired state. - ModuleFound = Module '{0}' found in the node. - ModuleNotFound = Module '{0}' not found in the node. - ModuleWithRightPropertyNotFound = Module '{0}' with the right version or other properties not found in the node. Message: {1} - ModuleNotFoundInRepository = Module '{0}' with the right version or other properties not found in the repository. Message: {1} - StartGetModule = Begin invoking Get-Module '{0}' - StartFindModule = Begin invoking Find-Module '{0}' + ModuleFound = Module '{0}' is found on the node. + ModuleNotFound = Module '{0}' is not found on the node. + ModuleWithRightPropertyNotFound = Module '{0}' with the right version or other properties not found in the node. + ModuleNotFoundInRepository = Module '{0}' with the right version or other properties not found in the repository. + StartGetModule = Begin invoking Get-Module '{0}'. + StartFindModule = Begin invoking Find-Module '{0}'. StartInstallModule = Begin invoking Install-Module '{0}' version '{1}' from '{2}' repository. - StartUnInstallModule = Begin invoking uninstall of the module '{0}' + StartUnInstallModule = Begin invoking Remove-Item to remove the module '{0}' from the file system. InstalledSuccess = Successfully installed the module '{0}' UnInstalledSuccess = Successfully uninstalled the module '{0}' VersionMismatch = The installed Module '{0}' has the version: '{1}' RepositoryMismatch = The installed Module '{0}' is from '{1}' repository. - FoundModulePath = Found the module path: '{0}' + FoundModulePath = Found the module path: '{0}'. MultipleModuleFound = Total: '{0}' modules found with the same name. Please use -RequiredVersion for filtering. Message: {1} - InstallationPolicyWarning = You are installing the module '{0}' from an untrusted repository' {1}'. Your current InstallationPolicy is '{2}'. If you trust the repository, set the policy to "Trusted". "Untrusted" otherwise. - InstallationPolicyFailed = Failed in the installation policy. Your current InstallationPolicy is '{0}' and the repository is '{1}'. If you trust the repository, set the policy to "Trusted". "Untrusted" otherwise. -###PSLOC + InstallationPolicyWarning = You are installing the module '{0}' from an untrusted repository' {1}'. Your current InstallationPolicy is '{2}'. If you trust the repository, set the policy to "Trusted". + InstallationPolicyFailed = Failed in the installation policy. Your current InstallationPolicy is '{0}' and the repository is '{1}'. If you trust the repository, set the policy to "Trusted". + GetTargetResourceMessage = Getting the current state of the module '{0}'. + TestTargetResourceMessage = Determining if the module '{0}' is in the desired state. '@ diff --git a/DSC/Examples/.gitattributes b/DSC/Examples/.gitattributes new file mode 100644 index 00000000..92be83e2 --- /dev/null +++ b/DSC/Examples/.gitattributes @@ -0,0 +1 @@ +* text eol=crlf diff --git a/DSC/Examples/README.md b/DSC/Examples/README.md new file mode 100644 index 00000000..4db26804 --- /dev/null +++ b/DSC/Examples/README.md @@ -0,0 +1,7 @@ +# Examples + +## Resource examples + +These are the links to the examples for each individual resource. + +- [PSModule](Resources/PSModule) diff --git a/DSC/Examples/Resources/PSModule/1-PSModule_InstallModuleConfig.ps1 b/DSC/Examples/Resources/PSModule/1-PSModule_InstallModuleConfig.ps1 new file mode 100644 index 00000000..e8583e89 --- /dev/null +++ b/DSC/Examples/Resources/PSModule/1-PSModule_InstallModuleConfig.ps1 @@ -0,0 +1,73 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 45d8677b-817f-4b2a-8d47-a802c4f758b1 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module. + + .DESCRIPTION + Configuration that installs a module. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be downloaded and installed. + + .EXAMPLE + PSModule_InstallModuleConfig -ModuleName 'PSLogging' + + Compiles a configuration that downloads and installs the module 'PSLogging'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'PSLogging' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_InstallModuleConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'PSLogging'. + + Replace the and with correct values. +#> +configuration PSModule_InstallModuleConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModule' + { + Name = $ModuleName + } + } +} diff --git a/DSC/Examples/Resources/PSModule/2-PSModule_InstallModuleTrustedConfig.ps1 b/DSC/Examples/Resources/PSModule/2-PSModule_InstallModuleTrustedConfig.ps1 new file mode 100644 index 00000000..84b6635c --- /dev/null +++ b/DSC/Examples/Resources/PSModule/2-PSModule_InstallModuleTrustedConfig.ps1 @@ -0,0 +1,76 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID d16da19d-439a-4730-8e02-6928d6a8ed28 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module, and overrides the + current trust level for the package source. + + .DESCRIPTION + Configuration that installs a module, and overrides the + current trust level for the package source. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be downloaded and installed. + + .EXAMPLE + PSModule_InstallModuleTrustedConfig -ModuleName 'PSLogging' + + Compiles a configuration that downloads and installs the module 'PSLogging'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'PSLogging' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_InstallModuleTrustedConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'PSLogging'. + + Replace the and with correct values. +#> +configuration PSModule_InstallModuleTrustedConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModuleAsTrusted' + { + Name = $ModuleName + InstallationPolicy = 'Trusted' + } + } +} diff --git a/DSC/Examples/Resources/PSModule/3-PSModule_InstallModuleAllowClobberConfig.ps1 b/DSC/Examples/Resources/PSModule/3-PSModule_InstallModuleAllowClobberConfig.ps1 new file mode 100644 index 00000000..44b4abab --- /dev/null +++ b/DSC/Examples/Resources/PSModule/3-PSModule_InstallModuleAllowClobberConfig.ps1 @@ -0,0 +1,74 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 47eb256b-9c81-437d-9148-890bc94f15ed +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module and allows clobber. + + .DESCRIPTION + Configuration that installs a module and allows clobber. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be downloaded and installed. + + .EXAMPLE + PSModule_InstallModuleAllowClobberConfig -ModuleName 'SqlServer' + + Compiles a configuration that downloads and installs the module 'SqlServer'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'SqlServer' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_InstallModuleAllowClobberConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'SqlServer'. + + Replace the and with correct values. +#> +configuration PSModule_InstallModuleAllowClobberConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModuleAndAllowClobber' + { + Name = $ModuleName + AllowClobber = $true + } + } +} diff --git a/DSC/Examples/Resources/PSModule/4-PSModule_InstallModuleSpecificVersionConfig.ps1 b/DSC/Examples/Resources/PSModule/4-PSModule_InstallModuleSpecificVersionConfig.ps1 new file mode 100644 index 00000000..67c3a244 --- /dev/null +++ b/DSC/Examples/Resources/PSModule/4-PSModule_InstallModuleSpecificVersionConfig.ps1 @@ -0,0 +1,83 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID cbed3f85-50cf-49ca-bde4-1b3ef0e33687 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module with a specific version. + + .DESCRIPTION + Configuration that installs a module with a specific version. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be downloaded and installed. + + .PARAMETER RequiredVersion + The specific (required) version of the module to download and install. + + .EXAMPLE + PSModule_InstallModuleSpecificVersionConfig -ModuleName 'SqlServer' -RequiredVersion '21.1.18068' + + Compiles a configuration that downloads and installs the module 'SqlServer'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'SqlServer' + RequiredVersion = '21.1.18068' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_InstallModuleSpecificVersionConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'SqlServer'. + + Replace the and with correct values. +#> +configuration PSModule_InstallModuleSpecificVersionConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $RequiredVersion + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModuleAndAllowClobber' + { + Name = $ModuleName + RequiredVersion = $RequiredVersion + } + } +} diff --git a/DSC/Examples/Resources/PSModule/5-PSModule_InstallModuleWithinVersionRangeConfig.ps1 b/DSC/Examples/Resources/PSModule/5-PSModule_InstallModuleWithinVersionRangeConfig.ps1 new file mode 100644 index 00000000..65806691 --- /dev/null +++ b/DSC/Examples/Resources/PSModule/5-PSModule_InstallModuleWithinVersionRangeConfig.ps1 @@ -0,0 +1,93 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID b3a8515b-9164-4fc5-9df0-e883f6420a83 +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that installs a module withing a specific version range. + + .DESCRIPTION + Configuration that installs a module withing a specific version range. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be downloaded and installed. + + .PARAMETER MinimumVersion + The minimum version of the module to download and install. + + .PARAMETER MaximumVersion + The maximum version of the module to download and install. + + .EXAMPLE + PSModule_InstallModuleWithinVersionRangeConfig -ModuleName 'SqlServer' -MinimumVersion '21.0.17199' -MaximumVersion '21.1.18068' + + Compiles a configuration that downloads and installs the module 'SqlServer'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'SqlServer' + MinimumVersion = '21.0.17199' + MaximumVersion = '21.1.18068' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_InstallModuleWithinVersionRangeConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'SqlServer'. + + Replace the and with correct values. +#> +configuration PSModule_InstallModuleWithinVersionRangeConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $MinimumVersion, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $MaximumVersion + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModuleAndAllowClobber' + { + Name = $ModuleName + MinimumVersion = $MinimumVersion + MaximumVersion = $MaximumVersion + } + } +} diff --git a/DSC/Examples/Resources/PSModule/6-PSModule_UninstallModuleConfig.ps1 b/DSC/Examples/Resources/PSModule/6-PSModule_UninstallModuleConfig.ps1 new file mode 100644 index 00000000..b255a121 --- /dev/null +++ b/DSC/Examples/Resources/PSModule/6-PSModule_UninstallModuleConfig.ps1 @@ -0,0 +1,74 @@ +<#PSScriptInfo +.VERSION 1.0.0 +.GUID 83a844ed-4e23-427d-94c9-72bdcae0e1bb +.AUTHOR Microsoft Corporation +.COMPANYNAME Microsoft Corporation +.COPYRIGHT +.TAGS DSCConfiguration +.LICENSEURI https://github.com/PowerShell/PowerShellGet/blob/master/LICENSE +.PROJECTURI https://github.com/PowerShell/PowerShellGet +.ICONURI +.EXTERNALMODULEDEPENDENCIES +.REQUIREDSCRIPTS +.EXTERNALSCRIPTDEPENDENCIES +.RELEASENOTES First version. +.PRIVATEDATA 2016-Datacenter,2016-Datacenter-Server-Core +#> + +#Requires -module PowerShellGet + +<# + .SYNOPSIS + Configuration that uninstalls an installed module. + + .DESCRIPTION + Configuration that uninstalls an installed module. + + .PARAMETER NodeName + The names of one or more nodes to compile a configuration for. + Defaults to 'localhost'. + + .PARAMETER ModuleName + The name of the module to be uninstalled. + + .EXAMPLE + PSModule_UninstallModuleConfig -ModuleName 'PSLogging' + + Compiles a configuration that downloads and installs the module 'PSLogging'. + + .EXAMPLE + $configurationParameters = @{ + ModuleName = 'PSLogging' + } + Start-AzureRmAutomationDscCompilationJob -ResourceGroupName '' -AutomationAccountName '' -ConfigurationName 'PSModule_UninstallModuleConfig' -Parameters $configurationParameters + + Compiles a configuration in Azure Automation that downloads and installs + the module 'PSLogging'. + + Replace the and with correct values. +#> +configuration PSModule_UninstallModuleConfig +{ + param + ( + [Parameter()] + [System.String[]] + $NodeName = 'localhost', + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ModuleName + ) + + Import-DscResource -ModuleName 'PowerShellGet' + + Node $nodeName + { + PSModule 'InstallModule' + { + Ensure = 'Absent' + Name = $ModuleName + } + } +} diff --git a/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 b/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 index 36b8a1eb..c56fdefc 100644 --- a/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 +++ b/DSC/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 @@ -23,31 +23,31 @@ Import-Module -Name $dscResourcesFolderFilePath # Import Localization Strings $script:localizedData = Get-LocalizedData -ResourceName 'PowerShellGet.ResourceHelper' -ScriptRoot $PSScriptRoot -Function ExtractArguments { - <# +<# .SYNOPSIS This is a helper function that extract the parameters from a given table. .PARAMETER FunctionBoundParameters - Specifies the hashtable containing a set of parameters to be extracted. + Specifies the hash table containing a set of parameters to be extracted. .PARAMETER ArgumentNames Specifies a list of arguments you want to extract. - #> - +#> +function New-SplatParameterHashTable { [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $FunctionBoundParameters, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [System.String[]] $ArgumentNames ) - Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.mycommand)) + Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) $returnValue = @{} @@ -57,149 +57,101 @@ Function ExtractArguments { $returnValue.Add($arg, $FunctionBoundParameters[$arg]) } } - return $returnValue -} - -Function ThrowError { - <# - .SYNOPSIS - This is a helper function that throws an error. - - .PARAMETER ExceptionName - Specifies the type of errors, e.g. System.ArgumentException. - - .PARAMETER ExceptionMessage - Specifies the exception message. - - .PARAMETER ErrorId - Specifies an identifier of the error. - - .PARAMETER ErrorCategory - Specifies the error category, e.g., InvalidArgument defined in System.Management.Automation. - #> - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionName, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionMessage, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [parameter(Mandatory = $true)] - [ValidateNotNull()] - [System.Management.Automation.ErrorCategory] - $ErrorCategory - ) - - Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.mycommand)) - - $exception = New-Object -TypeName $ExceptionName -ArgumentList $ExceptionMessage; - $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList ($exception, $ErrorId, $ErrorCategory, $null) - - throw $errorRecord + return $returnValue } -Function ValidateArgument { - <# +<# .SYNOPSIS - This is a helper function that validates the arguments. + This is a helper function that validate that a value is correct and used correctly. - .PARAMETER Argument - Specifies the argument to be validated. + .PARAMETER Value + Specifies the value to be validated. .PARAMETER Type Specifies the type of argument. - #> + .PARAMETER Type + Specifies the name of the provider. + + .OUTPUTS + None. Throws an error if the test fails. +#> +function Test-ParameterValue { [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] - $Argument, + $Value, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $Type, - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $ProviderName ) - Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.mycommand)) + Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) switch ($Type) { - "SourceUri" { + 'SourceUri' { # Checks whether given URI represents specific scheme # Most common schemes: file, http, https, ftp $scheme = @('http', 'https', 'file', 'ftp') - $newUri = $Argument -as [System.URI] - $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newuri.Scheme)) + $newUri = $Value -as [System.URI] + $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newUri.Scheme)) if ($returnValue -eq $false) { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.InValidUri -f $Argument)` - -ErrorId "InValidUri" ` - -ErrorCategory InvalidArgument + $errorMessage = $script:localizedData.InValidUri -f $Value + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage } } - "DestinationPath" { - $returnValue = Test-Path -Path $Argument + + 'DestinationPath' { + $returnValue = Test-Path -Path $Value if ($returnValue -eq $false) { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.PathDoesNotExist -f $Argument)` - -ErrorId "PathDoesNotExist" ` - -ErrorCategory InvalidArgument + $errorMessage = $script:localizedData.PathDoesNotExist -f $Value + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage } } - "PackageSource" { - # Argument can be either the package source Name or source Uri. + + 'PackageSource' { + # Value can be either the package source Name or source Uri. # Check if the source is a Uri. - $uri = $Argument -as [System.URI] + $uri = $Value -as [System.URI] if ($uri -and $uri.AbsoluteURI) { # Check if it's a valid Uri. - ValidateArgument -Argument $Argument -Type "SourceUri" -ProviderName $ProviderName + Test-ParameterValue -Value $Value -Type 'SourceUri' -ProviderName $ProviderName } else { # Check if it's a registered package source name. - $source = PackageManagement\Get-PackageSource -Name $Argument -ProviderName $ProviderName -verbose -ErrorVariable ev + $source = PackageManagement\Get-PackageSource -Name $Value -ProviderName $ProviderName -ErrorVariable ev if ((-not $source) -or $ev) { # We do not need to throw error here as Get-PackageSource does already. - Write-Verbose -Message ($LocalizedData.SourceNotFound -f $source) + Write-Verbose -Message ($script:localizedData.SourceNotFound -f $source) } } } + default { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.UnexpectedArgument -f $Type)` - -ErrorId "UnexpectedArgument" ` - -ErrorCategory InvalidArgument + $errorMessage = $script:localizedData.UnexpectedArgument -f $Type + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage } } } -Function ValidateVersionArgument { - <# +<# .SYNOPSIS This is a helper function that does the version validation. @@ -211,22 +163,25 @@ Function ValidateVersionArgument { .PARAMETER MinimumVersion Provides the minimum version. - #> - +#> +function Test-VersionParameter { [CmdletBinding()] param ( + [Parameter()] [System.String] $RequiredVersion, + [Parameter()] [System.String] $MinimumVersion, + [Parameter()] [System.String] $MaximumVersion ) - Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.mycommand)) + Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.MyCommand)) $isValid = $false @@ -246,35 +201,34 @@ Function ValidateVersionArgument { # This is an invalid case. When RequiredVersion is provided, others are not allowed. so $isValid is false, which is already set in the init. if ($isValid -eq $false) { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.VersionError)` - -ErrorId "VersionError" ` - -ErrorCategory InvalidArgument + $errorMessage = $script:localizedData.VersionError + New-InvalidArgumentException ` + -ArgumentName 'RequiredVersion, MinimumVersion or MaximumVersion' ` + -Message $errorMessage } } -Function Get-InstallationPolicy { - <# +<# .SYNOPSIS - This is a helper function that retrives the InstallationPolicy from the given repository. + This is a helper function that retrieves the InstallationPolicy from the given repository. .PARAMETER RepositoryName Provides the repository Name. - #> - +#> +function Get-InstallationPolicy { [CmdletBinding()] param ( - [parameter(Mandatory = $true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.String] $RepositoryName ) - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.MyCommand)) - $repositoryobj = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + $repositoryObject = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - if ($repositoryobj) { - return $repositoryobj.IsTrusted + if ($repositoryObject) { + return $repositoryObject.IsTrusted } } diff --git a/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 b/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 index 0a766936..ff21f8db 100644 --- a/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 +++ b/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 @@ -221,6 +221,7 @@ try { It 'Should return the same values as passed as parameters' { $getTargetResourceResult = Get-TargetResource -Name $mockModuleName $getTargetResourceResult.Name | Should -Be $mockModuleName + $getTargetResourceResult.Repository | Should -Be $mockRepositoryName Assert-MockCalled -CommandName Get-RightModule -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-ModuleRepositoryName -Exactly -Times 0 -Scope It @@ -231,7 +232,6 @@ try { $getTargetResourceResult = Get-TargetResource -Name $mockModuleName $getTargetResourceResult.Ensure | Should -Be 'Absent' - $getTargetResourceResult.Repository | Should -BeNullOrEmpty $getTargetResourceResult.Description | Should -BeNullOrEmpty $getTargetResourceResult.Guid | Should -BeNullOrEmpty $getTargetResourceResult.ModuleBase | Should -BeNullOrEmpty @@ -239,6 +239,12 @@ try { $getTargetResourceResult.Author | Should -BeNullOrEmpty $getTargetResourceResult.InstalledVersion | Should -BeNullOrEmpty $getTargetResourceResult.InstallationPolicy | Should -BeNullOrEmpty + $getTargetResourceResult.RequiredVersion | Should -BeNullOrEmpty + $getTargetResourceResult.MinimumVersion | Should -BeNullOrEmpty + $getTargetResourceResult.MaximumVersion | Should -BeNullOrEmpty + $getTargetResourceResult.Force | Should -Be $false + $getTargetResourceResult.AllowClobber | Should -Be $false + $getTargetResourceResult.SkipPublisherCheck | Should -Be $false Assert-MockCalled -CommandName Get-RightModule -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Get-ModuleRepositoryName -Exactly -Times 0 -Scope It @@ -291,7 +297,7 @@ try { Context 'When the Repository is ''PSGallery''' { BeforeAll { Mock -CommandName Get-InstallationPolicy -MockWith $mockGetInstallationPolicy_Trusted - Mock -CommandName ValidateArgument + Mock -CommandName Test-ParameterValue } It 'Should call the Install-Module with the correct parameters' { @@ -321,12 +327,14 @@ try { Context 'When the module name cannot be found' { BeforeAll { - Mock -CommandName Find-Module + Mock -CommandName Find-Module -MockWith { + throw 'Mocked error' + } } It 'Should throw the correct error' { { Set-TargetResource -Name $mockModuleName } | - Should -Throw ($localizedData.ModuleNotFoundInRepository -f $mockModuleName, '') + Should -Throw ($localizedData.ModuleNotFoundInRepository -f $mockModuleName) } } } @@ -355,7 +363,21 @@ try { It 'Should throw the correct error' { { Set-TargetResource -Name $mockModuleName -Ensure 'Absent' } | - Should -Throw ($localizedData.ModuleWithRightPropertyNotFound -f $mockModuleName, '') + Should -Throw ($localizedData.ModuleWithRightPropertyNotFound -f $mockModuleName) + } + } + + Context 'When a module cannot be removed' { + BeforeAll { + Mock -CommandName Get-RightModule -MockWith $mockGetRightModule_SingleModule + Mock -CommandName Remove-Item -MockWith { + throw 'Mock fail to remove module error' + } + } + + It 'Should throw the correct error' { + { Set-TargetResource -Name $mockModuleName -Ensure 'Absent' } | + Should -Throw ($localizedData.FailToUninstall -f $mockModuleName) } } } diff --git a/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 b/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 index aadb9c29..f4a5f255 100644 --- a/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 +++ b/DSC/Tests/Unit/PowerShellGet.ResourceHelper.Tests.ps1 @@ -17,7 +17,7 @@ Describe "$script:helperModuleName Unit Tests" { } InModuleScope $script:helperModuleName { - Describe 'ExtractArguments' { + Describe 'New-SplatParameterHashTable' { Context 'When specific parameters should be returned' { It 'Should return a hashtable with the correct values' { $mockPSBoundParameters = @{ @@ -27,7 +27,7 @@ Describe "$script:helperModuleName Unit Tests" { Property4 = '4' } - $extractArgumentsResult = ExtractArguments ` + $extractArgumentsResult = New-SplatParameterHashTable ` -FunctionBoundParameters $mockPSBoundParameters ` -ArgumentNames @('Property2', 'Property3') @@ -46,7 +46,7 @@ Describe "$script:helperModuleName Unit Tests" { Property1 = '1' } - $extractArgumentsResult = ExtractArguments ` + $extractArgumentsResult = New-SplatParameterHashTable ` -FunctionBoundParameters $mockPSBoundParameters ` -ArgumentNames @('Property2', 'Property3') @@ -60,7 +60,7 @@ Describe "$script:helperModuleName Unit Tests" { $mockPSBoundParameters = @{ } - $extractArgumentsResult = ExtractArguments ` + $extractArgumentsResult = New-SplatParameterHashTable ` -FunctionBoundParameters $mockPSBoundParameters ` -ArgumentNames @('Property2', 'Property3') @@ -70,101 +70,85 @@ Describe "$script:helperModuleName Unit Tests" { } } - Describe 'ThrowError' { - It 'Should throw the correct error' { - { - $mockedErrorMessage = 'mocked error' - $mockErrorId = 'MockedError' - $mockExceptionName = 'InvalidOperationException' - - ThrowError ` - -ExceptionName $mockExceptionName ` - -ExceptionMessage $mockedErrorMessage ` - -ErrorId $mockErrorId ` - -ErrorCategory 'InvalidOperation' - } | Should -Throw $mockedErrorMessage - } - } - - Describe 'ValidateArgument' { + Describe 'Test-ParameterValue' { BeforeAll { $mockProviderName = 'PowerShellGet' } - Context 'When passing a correct uri as ''Argument'' and type is ''SourceUri''' { + Context 'When passing a correct uri as ''Value'' and type is ''SourceUri''' { It 'Should not throw an error' { { - ValidateArgument ` - -Argument 'https://mocked.uri' ` + Test-ParameterValue ` + -Value 'https://mocked.uri' ` -Type 'SourceUri' ` -ProviderName $mockProviderName } | Should -Not -Throw } } - Context 'When passing an invalid uri as ''Argument'' and type is ''SourceUri''' { + Context 'When passing an invalid uri as ''Value'' and type is ''SourceUri''' { It 'Should throw the correct error' { - $mockArgument = 'mocked.uri' + $mockParameterName = 'mocked.uri' { - ValidateArgument ` - -Argument $mockArgument ` + Test-ParameterValue ` + -Value $mockParameterName ` -Type 'SourceUri' ` -ProviderName $mockProviderName - } | Should -Throw ($LocalizedData.InValidUri -f $mockArgument) + } | Should -Throw ($LocalizedData.InValidUri -f $mockParameterName) } } - Context 'When passing a correct path as ''Argument'' and type is ''DestinationPath''' { + Context 'When passing a correct path as ''Value'' and type is ''DestinationPath''' { It 'Should not throw an error' { { - ValidateArgument ` - -Argument 'TestDrive:\' ` + Test-ParameterValue ` + -Value 'TestDrive:\' ` -Type 'DestinationPath' ` -ProviderName $mockProviderName } | Should -Not -Throw } } - Context 'When passing an invalid path as ''Argument'' and type is ''DestinationPath''' { + Context 'When passing an invalid path as ''Value'' and type is ''DestinationPath''' { It 'Should throw the correct error' { - $mockArgument = 'TestDrive:\NonExistentPath' + $mockParameterName = 'TestDrive:\NonExistentPath' { - ValidateArgument ` - -Argument $mockArgument ` + Test-ParameterValue ` + -Value $mockParameterName ` -Type 'DestinationPath' ` -ProviderName $mockProviderName - } | Should -Throw ($LocalizedData.PathDoesNotExist -f $mockArgument) + } | Should -Throw ($LocalizedData.PathDoesNotExist -f $mockParameterName) } } - Context 'When passing a correct uri as ''Argument'' and type is ''PackageSource''' { + Context 'When passing a correct uri as ''Value'' and type is ''PackageSource''' { It 'Should not throw an error' { { - ValidateArgument ` - -Argument 'https://mocked.uri' ` + Test-ParameterValue ` + -Value 'https://mocked.uri' ` -Type 'PackageSource' ` -ProviderName $mockProviderName } | Should -Not -Throw } } - Context 'When passing an correct package source as ''Argument'' and type is ''PackageSource''' { + Context 'When passing an correct package source as ''Value'' and type is ''PackageSource''' { BeforeAll { - $mockArgument = 'PSGallery' + $mockParameterName = 'PSGallery' Mock -CommandName Get-PackageSource -MockWith { return New-Object -TypeName Object | - Add-Member -Name 'Name' -MemberType NoteProperty -Value $mockArgument -PassThru | + Add-Member -Name 'Name' -MemberType NoteProperty -Value $mockParameterName -PassThru | Add-Member -Name 'ProviderName' -MemberType NoteProperty -Value $mockProviderName -PassThru -Force } } It 'Should not throw an error' { { - ValidateArgument ` - -Argument $mockArgument ` + Test-ParameterValue ` + -Value $mockParameterName ` -Type 'PackageSource' ` -ProviderName $mockProviderName } | Should -Not -Throw @@ -175,15 +159,15 @@ Describe "$script:helperModuleName Unit Tests" { Context 'When passing type is ''PackageSource'' and passing a package source that does not exist' { BeforeAll { - $mockArgument = 'PSGallery' + $mockParameterName = 'PSGallery' Mock -CommandName Get-PackageSource } It 'Should not throw an error' { { - ValidateArgument ` - -Argument $mockArgument ` + Test-ParameterValue ` + -Value $mockParameterName ` -Type 'PackageSource' ` -ProviderName $mockProviderName } | Should -Not -Throw @@ -199,8 +183,8 @@ Describe "$script:helperModuleName Unit Tests" { It 'Should throw the correct error' { { - ValidateArgument ` - -Argument 'AnyArgument' ` + Test-ParameterValue ` + -Value 'AnyArgument' ` -Type $mockType ` -ProviderName $mockProviderName } | Should -Throw ($LocalizedData.UnexpectedArgument -f $mockType) @@ -208,23 +192,23 @@ Describe "$script:helperModuleName Unit Tests" { } } - Describe 'ValidateVersionArgument' { + Describe 'Test-VersionParameter' { Context 'When not passing in any parameters (using default values)' { It 'Should return true' { - ValidateVersionArgument | Should -BeTrue + Test-VersionParameter | Should -BeTrue } } Context 'When only ''RequiredVersion'' are passed' { It 'Should return true' { - ValidateVersionArgument -RequiredVersion '3.0.0.0' | Should -BeTrue + Test-VersionParameter -RequiredVersion '3.0.0.0' | Should -BeTrue } } Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { It 'Should throw the correct error' { { - ValidateVersionArgument ` + Test-VersionParameter ` -MinimumVersion '2.0.0.0' ` -MaximumVersion '1.0.0.0' } | Should -Throw $LocalizedData.VersionError @@ -234,7 +218,7 @@ Describe "$script:helperModuleName Unit Tests" { Context 'When ''MinimumVersion'' has a lower version than ''MaximumVersion''' { It 'Should throw the correct error' { { - ValidateVersionArgument ` + Test-VersionParameter ` -MinimumVersion '2.0.0.0' ` -MaximumVersion '1.0.0.0' } | Should -Throw $LocalizedData.VersionError @@ -244,7 +228,7 @@ Describe "$script:helperModuleName Unit Tests" { Context 'When ''RequiredVersion'', ''MinimumVersion'', and ''MaximumVersion'' are passed' { It 'Should throw the correct error' { { - ValidateVersionArgument ` + Test-VersionParameter ` -RequiredVersion '3.0.0.0' ` -MinimumVersion '2.0.0.0' ` -MaximumVersion '1.0.0.0' diff --git a/appveyor.yml b/appveyor.yml index 95b052a1..1c9ad88f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -62,16 +62,22 @@ for: only: - TestCategory: DSC + environment: + gallery_api: + secure: 9ekJzfsPCDBkyLrfmov83XbbhZ6E2N3z+B/Io8NbDetbHc6hWS19zsDmy7t0Vvxv + install: - git clone https://github.com/PowerShell/DscResource.Tests %APPVEYOR_BUILD_FOLDER%\DSC\DscResource.Tests - ps: | Import-Module .\tools\build.psm1 Invoke-DscPowerShellGetAppVeyorInstallTask - test_script: - ps: Invoke-DscPowerShellGetAppVeyorTestTask + deploy_script: + - ps: Invoke-DscPowerShellGetAppVeyorDeployTask + # Upload the project along with TestResults as a zip archive on_finish: - ps: | diff --git a/tools/build.psm1 b/tools/build.psm1 index d74ff2fb..f9fdb897 100644 --- a/tools/build.psm1 +++ b/tools/build.psm1 @@ -474,6 +474,24 @@ function Set-AppVeyorLocationOriginalBuildFolder { Set-Location -Path $env:APPVEYOR_BUILD_FOLDER } +<# + .SYNOPSIS + This function removes any present PowerShellGet modules in $env:PSModulePath's. + + .NOTES + Used by other helper functions when testing DSC resources in AppVeyor. +#> +function Remove-ModulePowerShellGet { + [CmdletBinding()] + param() + + Remove-Module -Name PowerShellGet -Force + Get-Module -Name PowerShellGet -ListAvailable | ForEach-Object { + Remove-Item -Path (Split-Path -Path $_.Path -Parent) -Recurse -Force + } +} + + <# .SYNOPSIS This function runs all the necessary install steps to prepare the @@ -494,10 +512,7 @@ function Invoke-DscPowerShellGetAppVeyorInstallTask { Invoke-AppveyorInstallTask # Remove any present PowerShellGet modules so DSC integration tests don't see multiple modules. - Remove-Module -Name PowerShellGet -Force - Get-Module -Name PowerShellGet -ListAvailable | ForEach-Object { - Remove-Item -Path (Split-Path -Path $_.Path -Parent) -Recurse -Force - } + Remove-ModulePowerShellGet } catch { throw $_ @@ -542,3 +557,46 @@ function Invoke-DscPowerShellGetAppVeyorTestTask { Set-AppVeyorLocationOriginalBuildFolder } } + +<# + .SYNOPSIS + This function starts the deploy step for the DSC resources. The + deploy step only publishes the examples to the PowerShell Gallery + so they show up in the gallery part of Azure State Configuration. + + .NOTES + Publishes using the account 'dscresourcekit' which is owned by + PowerShell DSC Team (DSC Resource Kit). + Only runs on the master branch. +#> +function Invoke-DscPowerShellGetAppVeyorDeployTask { + [CmdletBinding()] + param() + + <# + Removes any present PowerShellGet modules so deployment + don't see multiple modules. + #> + Remove-ModulePowerShellGet + + <# + Removes the source folder so when the deploy step copies + the module folder it only see the resource in the 'dist' + folder created by the AppVeyor install task. + #> + Remove-Item -Path (Join-Path -Path $env:APPVEYOR_BUILD_FOLDER -ChildPath 'src') -Recurse -Force + + # Temporary change the build folder during testing of the DSC resources. + Set-AppVeyorLocationDscResourceModuleBuildFolder + + try { + Import-Module -Name "$env:APPVEYOR_BUILD_FOLDER\DscResource.Tests\AppVeyor.psm1" + Invoke-AppVeyorDeployTask -OptIn @('PublishExample') -ModuleRootPath (Split-Path -Path $env:APPVEYOR_BUILD_FOLDER -Parent) + } + catch { + throw $_ + } + finally { + Set-AppVeyorLocationOriginalBuildFolder + } +} From c659b2df9668c43dce514eec4a3cfbd358c47408 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Fri, 15 Feb 2019 19:38:44 +0100 Subject: [PATCH 2/3] Update CHANGELOG.md --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba16e567..d658af92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Made the list entries in the CHANGELOG.md to use dash `-` throughout to + be consequent (before there was a mix of dashes and asterisk). - Update the AppVeyor CI test pipeline with a new job to run tests for the DSC resources, primarily for the resource `PSModule`. The new job uses the test framework used for the DSC Resource Kit, @@ -15,6 +17,11 @@ - Moved helper functions for the DSC resource `PSModule` to the module PowerShellGet.ResourceHelper. Added improved localization support, and code formatting against workspace VS Code settings. + - Cleaned up the code against the [DscResources style guideline](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md). + - Renamed helper functions in the PowerShellGet.ResourceHelper module + to use Verb-Noun. + - Refactored the error handling logic to use the localization helper + functions, and also so that the error handling could be tested. - Changes to PSModule. - Added improved localization support. - Changed type on the parameters that had `[Switch]` to correctly use @@ -25,9 +32,32 @@ - It is now possible to install a module and passing in `AllowClobber` when the modules package source is trusted (it already worked in other scenarios). + - Rephrased some of the localization strings. + - Cleaned up the code against the [DscResources style guideline](https://github.com/PowerShell/DscResources/blob/master/StyleGuidelines.md). + Suppressed some of the Script Analyzer rules that are not complaint + with the the Visual Studio Code workspace settings in this repository. + - Refactored the error handling logic to use the localization helper + functions, and also so that the error handling could be tested. + - Refactored the Get-TargetResource to return the correct hash table + when the current state is absent. + - Added new examples. - Changed the AppVeyor CI build pipeline so it added the DSC resource `PSModule` and dependent helper modules (the `Modules` folder) to the AppVeyor artifact. +- Added the `.MetaTestOptIn.json` file to opt-in for a lot of common test + in the DscResource.Tests test framework that tests the DSC resources. +- The examples under the folder `DSC/Examples` will be [published to PowerShell Gallery](https://github.com/PowerShell/DscResource.Tests#publish-examples-to-powershell-gallery) + so that they show up in the gallery part of Azure State Configuration. + The examples are published under the account 'dscresourcekit' which is + owned by the PowerShell DSC Team (DSC Resource Kit). + - In the folder `DSC/Examples` a `.gitattributes` was added to make sure + the examples is always checkout out using CRLF. There is an issue + using `Test-ScriptFileInfo` when files is checkout out using only LF + which is the default in AppVeyor. + - In the file `appveyor.yml` the PowerShell Gallery API key was added + for the account 'dscresourcekit', which can only be decrypted using + the PowerShell AppVeyor account. + ## 2.0.4 From a32c9b9b1847527b703309e91006364fd7060722 Mon Sep 17 00:00:00 2001 From: Johan Ljunggren Date: Sat, 16 Feb 2019 14:39:59 +0100 Subject: [PATCH 3/3] Fix descriptions in tests --- DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 b/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 index ff21f8db..e264d2f1 100644 --- a/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 +++ b/DSC/Tests/Unit/MSFT_PSModule.Tests.ps1 @@ -404,7 +404,7 @@ try { } } - It 'Should return the same values as passed as parameters' { + It 'Should return the state as $true' { $testTargetResourceResult = Test-TargetResource -Name $mockModuleName $testTargetResourceResult | Should -Be $true @@ -430,7 +430,7 @@ try { } } - It 'Should return the same values as passed as parameters' { + It 'Should return the state as $true' { $testTargetResourceResult = Test-TargetResource -Ensure 'Absent' -Name $mockModuleName $testTargetResourceResult | Should -Be $true @@ -458,7 +458,7 @@ try { } } - It 'Should return the same values as passed as parameters' { + It 'Should return the state as $false' { $testTargetResourceResult = Test-TargetResource -Name $mockModuleName $testTargetResourceResult | Should -Be $false @@ -484,7 +484,7 @@ try { } } - It 'Should return the same values as passed as parameters' { + It 'Should return the state as $false' { $testTargetResourceResult = Test-TargetResource -Ensure 'Absent' -Name $mockModuleName $testTargetResourceResult | Should -Be $false