diff --git a/.gitattributes b/.gitattributes index d49b050..ecba8fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,17 @@ # Needed for publishing of examples, build worker defaults to core.autocrlf=input. +# The Sampler pipeline template suggest using '* text eol=autocrlf' but that will +# make some tests fail with CR/LF differences. * text eol=crlf +*.mof text eol=crlf +*.sh text eol=lf +*.svg eol=lf + # Ensure any exe files are treated as binary *.exe binary *.jpg binary *.xl* binary *.pfx binary +*.png binary +*.dll binary +*.so binary diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9917040 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,6 @@ +blank_issues_enabled: false +contact_links: + - name: "Virtual PowerShell User Group #DSC channel" + url: https://dsccommunity.org/community/contact/ + about: "To talk to the community and maintainers of DSC Community, please visit the #DSC channel." + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cbfb93a..d9df03e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,50 +1,49 @@ -# Pull Request -### Pull Request (PR) description + +#### Pull Request (PR) description + #### This Pull Request (PR) fixes the following issues + #### Task list + -- [ ] Added an entry to the change log under the Unreleased section of the - file CHANGELOG.md. Entry should say what was changed and how that - affects users (if applicable), and reference the issue being resolved - (if applicable). + +- [ ] Added an entry to the change log under the Unreleased section of the file CHANGELOG.md. + Entry should say what was changed and how that affects users (if applicable), and + reference the issue being resolved (if applicable). - [ ] Documentation added/updated in README.md. - [ ] Comment-based help added/updated. - [ ] Localization strings added/updated in all localization files as appropriate. - [ ] Examples appropriately added/updated. -- [ ] Unit tests added/updated. -- [ ] Integration tests added/updated (where possible). +- [ ] Unit tests added/updated. See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). +- [ ] Integration tests added/updated (where possible). See [DSC Community Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines). - [ ] New/changed code adheres to [DSC Community Style Guidelines](https://dsccommunity.org/styleguidelines). diff --git a/.gitignore b/.gitignore index ab2af19..17c483d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ output/ !**/README.md .kitchen/ +*.nupkg *.suo *.user *.coverage diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a1f268..1ed4085 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,10 +10,12 @@ "powershell.codeFormatting.pipelineIndentationStyle": "IncreaseIndentationAfterEveryPipeline", "powershell.codeFormatting.preset": "Custom", "powershell.codeFormatting.alignPropertyValuePairs": true, - "files.trimTrailingWhitespace": true, - "files.insertFinalNewline": true, + "powershell.developer.bundledModulesPath": "${cwd}/output/RequiredModules", "powershell.scriptAnalysis.settingsPath": ".vscode\\analyzersettings.psd1", "powershell.scriptAnalysis.enable": true, + "files.trimTrailingWhitespace": true, + "files.trimFinalNewlines": true, + "files.insertFinalNewline": true, "files.associations": { "*.ps1xml": "xml" }, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4085e3e..2991140 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -41,7 +41,7 @@ "label": "build", "type": "shell", "command": "&${cwd}/build.ps1", - "args": ["-AutoRestore"], + "args": [], "presentation": { "echo": true, "reveal": "always", diff --git a/CHANGELOG.md b/CHANGELOG.md index 78e8397..73536c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,17 @@ -# Change log for DscResource.DocGenerator +# Changelog for DscResource.DocGenerator The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed + +- Updated pipelines files to the latest in Sampler. +- Fix missing verbose message in function `Invoke-Git`. +- Fix variable reference for localized strings so they passed HQRM tests. +- Fix statement so function passed HQRM tests. + ## [0.10.3] - 2022-01-26 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f6895ab..3544bcc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,7 @@ # Contributing Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). + +## Running the Tests + +If want to know how to run this module's tests you can look at the [Testing Guidelines](https://dsccommunity.org/guidelines/testing-guidelines/#running-tests) diff --git a/GitVersion.yml b/GitVersion.yml index 2cb7e7f..66dd7e8 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,6 +1,6 @@ mode: ContinuousDelivery next-version: 0.1.0 -major-version-bump-message: '\s?(breaking|major|breaking\schange)' +major-version-bump-message: '(breaking\schange|breaking|major)\b' minor-version-bump-message: '(adds?|features?|minor)\b' patch-version-bump-message: '\s?(fix|patch)' no-bump-message: '\+semver:\s?(none|skip)' diff --git a/LICENSE b/LICENSE index f4c3ee9..0683f75 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -The MIT License (MIT) +MIT License Copyright (c) DSC Community contributors. @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 50f1109..5a1a8ef 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # DscResource.DocGenerator +Functionality to help generate documentation for modules. + [![Build Status](https://dev.azure.com/dsccommunity/DscResource.DocGenerator/_apis/build/status/dsccommunity.DscResource.DocGenerator?branchName=main)](https://dev.azure.com/dsccommunity/DscResource.DocGenerator/_build/latest?definitionId=19&branchName=main) ![Azure DevOps coverage (branch)](https://img.shields.io/azure-devops/coverage/dsccommunity/DscResource.DocGenerator/19/main) [![codecov](https://codecov.io/gh/dsccommunity/DscResource.DocGenerator/branch/main/graph/badge.svg)](https://codecov.io/gh/dsccommunity/DscResource.DocGenerator) @@ -7,8 +9,6 @@ [![PowerShell Gallery (with prereleases)](https://img.shields.io/powershellgallery/vpre/DscResource.DocGenerator?label=DscResource.DocGenerator%20Preview)](https://www.powershellgallery.com/packages/DscResource.DocGenerator/) [![PowerShell Gallery](https://img.shields.io/powershellgallery/v/DscResource.DocGenerator?label=DscResource.DocGenerator)](https://www.powershellgallery.com/packages/DscResource.DocGenerator/) -Functionality to help generate documentation for modules. - ## Code of Conduct This project has adopted this [Code of Conduct](CODE_OF_CONDUCT.md). @@ -22,9 +22,13 @@ full release to [PowerShell Gallery](https://www.powershellgallery.com/). ## Contributing -Please check out the DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). +Please check out common DSC Community [contributing guidelines](https://dsccommunity.org/guidelines/contributing). This repository align to the [DSC Community Style Guidelines](https://dsccommunity.org/styleguidelines). +## Change log + +A full list of changes in each version can be found in the [change log](CHANGELOG.md). + ## Known Issues ### Composite Resources and Linux diff --git a/RequiredModules.psd1 b/RequiredModules.psd1 index 69ad12c..ccd38e8 100644 --- a/RequiredModules.psd1 +++ b/RequiredModules.psd1 @@ -3,7 +3,7 @@ AddToPath = $true Target = 'output\RequiredModules' Parameters = @{ - Repository = '' + Repository = 'PSGallery' } } @@ -12,6 +12,7 @@ Pester = '4.10.1' Plaster = 'latest' ModuleBuilder = 'latest' + ChangelogManagement = 'latest' Sampler = @{ version = 'latest' @@ -22,10 +23,10 @@ 'Sampler.GitHubTasks' = 'latest' MarkdownLinkCheck = 'latest' - ChangelogManagement = 'latest' 'DscResource.Test' = 'latest' 'DscResource.AnalyzerRules' = 'latest' xDscResourceDesigner = 'latest' + # Build dependent modules 'DscResource.Common' = 'latest' } diff --git a/Resolve-Dependency.ps1 b/Resolve-Dependency.ps1 index 4928e62..294cb68 100644 --- a/Resolve-Dependency.ps1 +++ b/Resolve-Dependency.ps1 @@ -1,108 +1,183 @@ +<# + .DESCRIPTION + Bootstrap script for PSDepend. + + .PARAMETER DependencyFile + Specifies the configuration file for the this script. The default value is + 'RequiredModules.psd1' relative to this script's path. + + .PARAMETER PSDependTarget + Path for PSDepend to be bootstrapped and save other dependencies. + Can also be CurrentUser or AllUsers if you wish to install the modules in + such scope. The default value is './output/RequiredModules' relative to + this script's path. + + .PARAMETER Proxy + Specifies the URI to use for Proxy when attempting to bootstrap + PackageProvider and PowerShellGet. + + .PARAMETER ProxyCredential + Specifies the credential to contact the Proxy when provided. + + .PARAMETER Scope + Specifies the scope to bootstrap the PackageProvider and PSGet if not available. + THe default value is 'CurrentUser'. + + .PARAMETER Gallery + Specifies the gallery to use when bootstrapping PackageProvider, PSGet and + when calling PSDepend (can be overridden in Dependency files). The default + value is 'PSGallery'. + + .PARAMETER GalleryCredential + Specifies the credentials to use with the Gallery specified above. + + .PARAMETER AllowOldPowerShellGetModule + Allow you to use a locally installed version of PowerShellGet older than + 1.6.0 (not recommended). Default it will install the latest PowerShellGet + if an older version than 2.0 is detected. + + .PARAMETER MinimumPSDependVersion + Allow you to specify a minimum version fo PSDepend, if you're after specific + features. + + .PARAMETER AllowPrerelease + Not yet written. + + .PARAMETER WithYAML + Not yet written. + + .NOTES + Load defaults for parameters values from Resolve-Dependency.psd1 if not + provided as parameter. +#> [CmdletBinding()] param ( - [Parameter()] - [String] + [System.String] $DependencyFile = 'RequiredModules.psd1', [Parameter()] - [String] - # Path for PSDepend to be bootstrapped and save other dependencies. - # Can also be CurrentUser or AllUsers if you wish to install the modules in such scope - # Default to $PWD.Path/output/modules - $PSDependTarget = (Join-Path $PSScriptRoot './output/RequiredModules'), + [System.String] + $PSDependTarget = (Join-Path -Path $PSScriptRoot -ChildPath './output/RequiredModules'), [Parameter()] - [uri] - # URI to use for Proxy when attempting to Bootstrap PackageProvider & PowerShellGet + [System.Uri] $Proxy, [Parameter()] - # Credential to contact the Proxy when provided - [PSCredential]$ProxyCredential, + [System.Management.Automation.PSCredential] + $ProxyCredential, [Parameter()] [ValidateSet('CurrentUser', 'AllUsers')] - [String] - # Scope to bootstrap the PackageProvider and PSGet if not available + [System.String] $Scope = 'CurrentUser', [Parameter()] - [String] - # Gallery to use when bootstrapping PackageProvider, PSGet and when calling PSDepend (can be overridden in Dependency files) + [System.String] $Gallery = 'PSGallery', [Parameter()] - [PSCredential] - # Credentials to use with the Gallery specified above + [System.Management.Automation.PSCredential] $GalleryCredential, - [Parameter()] - [switch] - # Allow you to use a locally installed version of PowerShellGet older than 1.6.0 (not recommended, default to $False) + [System.Management.Automation.SwitchParameter] $AllowOldPowerShellGetModule, [Parameter()] - [String] - # Allow you to specify a minimum version fo PSDepend, if you're after specific features. + [System.String] $MinimumPSDependVersion, [Parameter()] - [Switch] + [System.Management.Automation.SwitchParameter] $AllowPrerelease, [Parameter()] - [Switch] + [System.Management.Automation.SwitchParameter] $WithYAML ) -# Load Defaults for parameters values from Resolve-Dependency.psd1 if not provided as parameter try { - Write-Verbose -Message "Importing Bootstrap default parameters from '$PSScriptRoot/Resolve-Dependency.psd1'." - $ResolveDependencyDefaults = Import-PowerShellDataFile -Path (Join-Path $PSScriptRoot '.\Resolve-Dependency.psd1' -Resolve -ErrorAction Stop) - $ParameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys - if ($ParameterToDefault.Count -eq 0) + if ($PSVersionTable.PSVersion.Major -le 5) + { + if (-not (Get-Command -Name 'Import-PowerShellDataFile' -ErrorAction 'SilentlyContinue')) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion '3.1.0.0' + } + + <# + Making sure the imported PackageManagement module is not from PS7 module + path. The VSCode PS extension is changing the $env:PSModulePath and + prioritize the PS7 path. This is an issue with PowerShellGet because + it loads an old version if available (or fail to load latest). + #> + Get-Module -ListAvailable PackageManagement | + Where-Object -Property 'ModuleBase' -NotMatch 'powershell.7' | + Select-Object -First 1 | + Import-Module -Force + } + + Write-Verbose -Message 'Importing Bootstrap default parameters from ''$PSScriptRoot/Resolve-Dependency.psd1''.' + + $resolveDependencyConfigPath = Join-Path -Path $PSScriptRoot -ChildPath '.\Resolve-Dependency.psd1' -Resolve -ErrorAction 'Stop' + + $resolveDependencyDefaults = Import-PowerShellDataFile -Path $resolveDependencyConfigPath + + $parameterToDefault = $MyInvocation.MyCommand.ParameterSets.Where{ $_.Name -eq $PSCmdlet.ParameterSetName }.Parameters.Keys + + if ($parameterToDefault.Count -eq 0) { - $ParameterToDefault = $MyInvocation.MyCommand.Parameters.Keys + $parameterToDefault = $MyInvocation.MyCommand.Parameters.Keys } - # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option - foreach ($ParamName in $ParameterToDefault) + + # Set the parameters available in the Parameter Set, or it's not possible to choose yet, so all parameters are an option. + foreach ($parameterName in $parameterToDefault) { - if (-Not $PSBoundParameters.Keys.Contains($ParamName) -and $ResolveDependencyDefaults.ContainsKey($ParamName)) + if (-not $PSBoundParameters.Keys.Contains($parameterName) -and $resolveDependencyDefaults.ContainsKey($parameterName)) { - Write-Verbose -Message "Setting $ParamName with $($ResolveDependencyDefaults[$ParamName])" + Write-Verbose -Message "Setting $parameterName with $($resolveDependencyDefaults[$parameterName])." + try { - $variableValue = $ResolveDependencyDefaults[$ParamName] - if ($variableValue -is [string]) + $variableValue = $resolveDependencyDefaults[$parameterName] + + if ($variableValue -is [System.String]) { $variableValue = $ExecutionContext.InvokeCommand.ExpandString($variableValue) } - $PSBoundParameters.Add($ParamName, $variableValue) - Set-Variable -Name $ParamName -value $variableValue -Force -ErrorAction SilentlyContinue + + $PSBoundParameters.Add($parameterName, $variableValue) + + Set-Variable -Name $parameterName -value $variableValue -Force -ErrorAction 'SilentlyContinue' } catch { - Write-Verbose -Message "Error adding default for $ParamName : $($_.Exception.Message)" + Write-Verbose -Message "Error adding default for $parameterName : $($_.Exception.Message)." } } } } catch { - Write-Warning -Message "Error attempting to import Bootstrap's default parameters from $(Join-Path $PSScriptRoot '.\Resolve-Dependency.psd1'): $($_.Exception.Message)." + Write-Warning -Message "Error attempting to import Bootstrap's default parameters from '$resolveDependencyConfigPath': $($_.Exception.Message)." } -Write-Progress -Activity "Bootstrap:" -PercentComplete 0 -CurrentOperation "NuGet Bootstrap" +Write-Progress -Activity 'Bootstrap:' -PercentComplete 0 -CurrentOperation 'NuGet Bootstrap' + +# TODO: This should handle the parameter $AllowOldPowerShellGetModule. +$powerShellGetModule = Import-Module -Name 'PowerShellGet' -MinimumVersion '2.0' -ErrorAction 'SilentlyContinue' -PassThru -if (!(Get-PackageProvider -Name NuGet -ForceBootstrap -ErrorAction SilentlyContinue)) +# Install the package provider if it is not available. +$nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 + +if (-not $powerShellGetModule -and -not $nuGetProvider) { - $providerBootstrapParams = @{ + $providerBootstrapParameters = @{ Name = 'nuget' - force = $true + Force = $true ForceBootstrap = $true ErrorAction = 'Stop' } @@ -111,47 +186,60 @@ if (!(Get-PackageProvider -Name NuGet -ForceBootstrap -ErrorAction SilentlyConti { 'Proxy' { - $providerBootstrapParams.Add('Proxy', $Proxy) + $providerBootstrapParameters.Add('Proxy', $Proxy) } + 'ProxyCredential' { - $providerBootstrapParams.Add('ProxyCredential', $ProxyCredential) + $providerBootstrapParameters.Add('ProxyCredential', $ProxyCredential) } + 'Scope' { - $providerBootstrapParams.Add('Scope', $Scope) + $providerBootstrapParameters.Add('Scope', $Scope) } } if ($AllowPrerelease) { - $providerBootstrapParams.Add('AllowPrerelease', $true) + $providerBootstrapParameters.Add('AllowPrerelease', $true) } - Write-Information "Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed)" + Write-Information -MessageData 'Bootstrap: Installing NuGet Package Provider from the web (Make sure Microsoft addresses/ranges are allowed).' + $null = Install-PackageProvider @providerBootstrapParams - $latestNuGetVersion = (Get-PackageProvider -Name NuGet -ListAvailable | Select-Object -First 1).Version.ToString() - Write-Information "Bootstrap: Importing NuGet Package Provider version $latestNuGetVersion to current session." - $Null = Import-PackageProvider -Name NuGet -RequiredVersion $latestNuGetVersion -Force + + $nuGetProvider = Get-PackageProvider -Name 'NuGet' -ListAvailable | Select-Object -First 1 + + $nuGetProviderVersion = $nuGetProvider.Version.ToString() + + Write-Information -MessageData "Bootstrap: Importing NuGet Package Provider version $nuGetProviderVersion to current session." + + $Null = Import-PackageProvider -Name 'NuGet' -RequiredVersion $nuGetProviderVersion -Force } -Write-Progress -Activity "Bootstrap:" -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" +Write-Progress -Activity 'Bootstrap:' -PercentComplete 10 -CurrentOperation "Ensuring Gallery $Gallery is trusted" + +# Fail if the given PSGallery is not registered. +$previousGalleryInstallationPolicy = (Get-PSRepository -Name $Gallery -ErrorAction 'Stop').InstallationPolicy + +Set-PSRepository -Name $Gallery -InstallationPolicy 'Trusted' -ErrorAction 'Ignore' -# Fail if the given PSGallery is not Registered -$Policy = (Get-PSRepository $Gallery -ErrorAction Stop).InstallationPolicy -Set-PSRepository -Name $Gallery -InstallationPolicy Trusted -ErrorAction Ignore try { - Write-Progress -Activity "Bootstrap:" -PercentComplete 25 -CurrentOperation "Checking PowerShellGet" - # Ensure the module is loaded and retrieve the version you have - $PowerShellGetVersion = (Import-Module PowerShellGet -PassThru -ErrorAction SilentlyContinue).Version + Write-Progress -Activity 'Bootstrap:' -PercentComplete 25 -CurrentOperation 'Checking PowerShellGet' - Write-Verbose "Bootstrap: The PowerShellGet version is $PowerShellGetVersion" - # Versions below 1.6.0 are considered old, unreliable & not recommended - if (!$PowerShellGetVersion -or ($PowerShellGetVersion -lt [System.version]'1.6.0' -and !$AllowOldPowerShellGetModule)) + # Ensure the module is loaded and retrieve the version you have. + $powerShellGetVersion = (Import-Module -Name 'PowerShellGet' -PassThru -ErrorAction 'SilentlyContinue').Version + + Write-Verbose -Message "Bootstrap: The PowerShellGet version is $powerShellGetVersion" + + # Versions below 2.0 are considered old, unreliable & not recommended + if (-not $powerShellGetVersion -or ($powerShellGetVersion -lt [System.Version] '2.0' -and -not $AllowOldPowerShellGetModule)) { - Write-Progress -Activity "Bootstrap:" -PercentComplete 40 -CurrentOperation "Installing newer version of PowerShellGet" - $InstallPSGetParam = @{ + Write-Progress -Activity 'Bootstrap:' -PercentComplete 40 -CurrentOperation 'Installing newer version of PowerShellGet' + + $installPowerShellGetParameters = @{ Name = 'PowerShellGet' Force = $True SkipPublisherCheck = $true @@ -164,49 +252,64 @@ try { 'Proxy' { - $InstallPSGetParam.Add('Proxy', $Proxy) + $installPowerShellGetParameters.Add('Proxy', $Proxy) } + 'ProxyCredential' { - $InstallPSGetParam.Add('ProxyCredential', $ProxyCredential) + $installPowerShellGetParameters.Add('ProxyCredential', $ProxyCredential) } + 'GalleryCredential' { - $InstallPSGetParam.Add('Credential', $GalleryCredential) + $installPowerShellGetParameters.Add('Credential', $GalleryCredential) } } - Install-Module @InstallPSGetParam - Remove-Module PowerShellGet -force -ErrorAction SilentlyContinue - Import-Module PowerShellGet -Force - $NewLoadedVersion = (Get-Module PowerShellGet).Version.ToString() - Write-Information "Bootstrap: PowerShellGet version loaded is $NewLoadedVersion" - Write-Progress -Activity "Bootstrap:" -PercentComplete 60 -CurrentOperation "Installing newer version of PowerShellGet" + Write-Progress -Activity 'Bootstrap:' -PercentComplete 60 -CurrentOperation 'Installing newer version of PowerShellGet' + + Install-Module @installPowerShellGetParameters + + Remove-Module -Name 'PowerShellGet' -Force -ErrorAction 'SilentlyContinue' + Remove-Module -Name 'PackageManagement' -Force + + $powerShellGetModule = Import-Module PowerShellGet -Force -PassThru + + $powerShellGetVersion = $powerShellGetModule.Version.ToString() + + Write-Information -MessageData "Bootstrap: PowerShellGet version loaded is $powerShellGetVersion" + } + + # Try to import the PSDepend module from the available modules. + $getModuleParameters = @{ + Name = 'PSDepend' + ListAvailable = $true } - # Try to import the PSDepend module from the available modules - try + $psDependModule = Get-Module @getModuleParameters + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) { - $ImportPSDependParam = @{ - Name = 'PSDepend' - ErrorAction = 'Stop' - Force = $true + try + { + $psDependModule = $psDependModule | Where-Object -FilterScript { $_.Version -ge $MinimumPSDependVersion } } - - if ($MinimumPSDependVersion) + catch { - $ImportPSDependParam.add('MinimumVersion', $MinimumPSDependVersion) + throw ('There was a problem finding the minimum version of PSDepend. Error: {0}' -f $_) } - $null = Import-Module @ImportPSDependParam } - catch + + if (-not $psDependModule) { - # PSDepend module not found, installing or saving it + # PSDepend module not found, installing or saving it. if ($PSDependTarget -in 'CurrentUser', 'AllUsers') { - Write-Debug "PSDepend module not found. Attempting to install from Gallery $Gallery" - Write-Warning "Installing PSDepend in $PSDependTarget Scope" - $InstallPSDependParam = @{ + Write-Debug -Message "PSDepend module not found. Attempting to install from Gallery $Gallery." + + Write-Warning -Message "Installing PSDepend in $PSDependTarget Scope." + + $installPSDependParameters = @{ Name = 'PSDepend' Repository = $Gallery Force = $true @@ -217,50 +320,69 @@ try if ($MinimumPSDependVersion) { - $InstallPSDependParam.add('MinimumVersion', $MinimumPSDependVersion) + $installPSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) } - Write-Progress -Activity "Bootstrap:" -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" - Install-Module @InstallPSDependParam + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Installing PSDepend from $Gallery" + + Install-Module @installPSDependParameters } else { - Write-Debug "PSDepend module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" - $SaveModuleParam = @{ + Write-Debug -Message "PSDepend module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + + $saveModuleParameters = @{ Name = 'PSDepend' Repository = $Gallery Path = $PSDependTarget + Force = $true } if ($MinimumPSDependVersion) { - $SaveModuleParam.add('MinimumVersion', $MinimumPSDependVersion) + $saveModuleParameters.add('MinimumVersion', $MinimumPSDependVersion) } - Write-Progress -Activity "Bootstrap:" -PercentComplete 75 -CurrentOperation "Saving & Importing PSDepend from $Gallery to $Scope" - Save-Module @SaveModuleParam + Write-Progress -Activity 'Bootstrap:' -PercentComplete 75 -CurrentOperation "Saving & Importing PSDepend from $Gallery to $Scope" + + Save-Module @saveModuleParameters } } - finally + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 80 -CurrentOperation 'Loading PSDepend' + + $importModulePSDependParameters = @{ + Name = 'PSDepend' + ErrorAction = 'Stop' + Force = $true + } + + if ($PSBoundParameters.ContainsKey('MinimumPSDependVersion')) { - Write-Progress -Activity "Bootstrap:" -PercentComplete 100 -CurrentOperation "Loading PSDepend" - # We should have successfully bootstrapped PSDepend. Fail if not available - Import-Module PSDepend -ErrorAction Stop + $importModulePSDependParameters.Add('MinimumVersion', $MinimumPSDependVersion) } + # We should have successfully bootstrapped PSDepend. Fail if not available. + $null = Import-Module @importModulePSDependParameters + if ($WithYAML) { - if (-Not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) + Write-Progress -Activity 'Bootstrap:' -PercentComplete 82 -CurrentOperation 'Verifying PowerShell module PowerShell-Yaml' + + if (-not (Get-Module -ListAvailable -Name 'PowerShell-Yaml')) { - Write-Verbose "PowerShell-Yaml module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + Write-Progress -Activity 'Bootstrap:' -PercentComplete 85 -CurrentOperation 'Installing PowerShell module PowerShell-Yaml' + + Write-Verbose -Message "PowerShell-Yaml module not found. Attempting to Save from Gallery $Gallery to $PSDependTarget" + $SaveModuleParam = @{ Name = 'PowerShell-Yaml' Repository = $Gallery Path = $PSDependTarget + Force = $true } Save-Module @SaveModuleParam - Import-Module "PowerShell-Yaml" -ErrorAction Stop } else { @@ -268,22 +390,28 @@ try } } + Write-Progress -Activity 'Bootstrap:' -PercentComplete 90 -CurrentOperation 'Invoke PSDepend' + Write-Progress -Activity "PSDepend:" -PercentComplete 0 -CurrentOperation "Restoring Build Dependencies" - if (Test-Path $DependencyFile) + + if (Test-Path -Path $DependencyFile) { - $PSDependParams = @{ + $psDependParameters = @{ Force = $true Path = $DependencyFile } - # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified - Invoke-PSDepend @PSDependParams + # TODO: Handle when the Dependency file is in YAML, and -WithYAML is specified. + Invoke-PSDepend @psDependParameters } + Write-Progress -Activity "PSDepend:" -PercentComplete 100 -CurrentOperation "Dependencies restored" -Completed + + Write-Progress -Activity 'Bootstrap:' -PercentComplete 100 -CurrentOperation "Bootstrap complete" -Completed } finally { # Reverting the Installation Policy for the given gallery - Set-PSRepository -Name $Gallery -InstallationPolicy $Policy - Write-Verbose "Project Bootstrapped, returning to Invoke-Build" + Set-PSRepository -Name $Gallery -InstallationPolicy $previousGalleryInstallationPolicy + Write-Verbose -Message "Project Bootstrapped, returning to Invoke-Build" } diff --git a/Resolve-Dependency.psd1 b/Resolve-Dependency.psd1 index 711cc56..2ae8c0d 100644 --- a/Resolve-Dependency.psd1 +++ b/Resolve-Dependency.psd1 @@ -3,4 +3,3 @@ AllowPrerelease = $false WithYAML = $true } - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7bd7bc2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,30 @@ +## Security + +The DSC Community takes the security of our modules seriously, which includes all source code repositories managed through our GitHub organization. + +If you believe you have found a security vulnerability in any DSC Community owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to one or several members of the DSC Community organization. +The easiest way to do so is to send us a direct message via twitter or slack. + +You should receive a response within 48 hours. If for some reason you do not, please follow up to other member of the community. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +## Preferred Languages + +We prefer all communications to be in English. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ec825bc..03da6da 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,17 +3,20 @@ trigger: include: - main paths: - exclude: - - CHANGELOG.md + include: + - source/* tags: include: - "v*" exclude: - "*-*" + variables: buildFolderName: output buildArtifactName: output testResultFolderName: testResults + sourceFolderName: source + defaultBranch: main stages: - stage: Build @@ -21,14 +24,17 @@ stages: - job: Package_Module displayName: 'Package Module' pool: - vmImage: 'ubuntu 18.04' + vmImage: 'ubuntu-latest' steps: - - task: GitVersion@5 - name: gitVersion - displayName: 'Evaluate Next Version' - inputs: - runtime: 'core' - configFilePath: 'GitVersion.yml' + - pwsh: | + dotnet tool install --global GitVersion.Tool + $gitVersionObject = dotnet-gitversion | ConvertFrom-Json + $gitVersionObject.PSObject.Properties.ForEach{ + Write-Host -Object "Setting Task Variable '$($_.Name)' with value '$($_.Value)'." + Write-Host -Object "##vso[task.setvariable variable=$($_.Name);]$($_.Value)" + } + Write-Host -Object "##vso[build.updatebuildnumber]$($gitVersionObject.FullSemVer)" + displayName: Calculate ModuleVersion (GitVersion) - task: PowerShell@2 name: package displayName: 'Build & Package Module' @@ -37,64 +43,124 @@ stages: arguments: '-ResolveDependency -tasks pack' pwsh: true env: - ModuleVersion: $(gitVersion.InformationalVersion) + ModuleVersion: $(NuGetVersionV2) - task: PublishPipelineArtifact@1 - displayName: 'Publish Pipeline Artifact' + displayName: 'Publish Build Artifact' inputs: - targetPath: 'output/' - artifact: 'output' + targetPath: '$(buildFolderName)/' + artifact: $(buildArtifactName) publishLocation: 'pipeline' parallel: true - stage: Test dependsOn: Build jobs: - - job: test_windows_core - displayName: 'Windows (PowerShell Core)' - timeoutInMinutes: 0 - pool: - vmImage: 'windows-latest' - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact' - inputs: - buildType: 'current' - artifactName: $(buildArtifactName) - targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' - - task: PowerShell@2 - name: test - displayName: 'Run Tests' - inputs: - filePath: './build.ps1' - arguments: '-tasks test' - pwsh: true - - task: PublishTestResults@2 - displayName: 'Publish Test Results' - condition: succeededOrFailed() - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: 'output/testResults/NUnit*.xml' - testRunTitle: 'Windows Server Core (PowerShell Core)' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Test Artifact' - inputs: - targetPath: '$(buildFolderName)/$(testResultFolderName)/' - artifactName: 'CodeCoverageWinPS7' - parallel: true + - job: Test_HQRM + displayName: 'HQRM' + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: test + displayName: 'Run HQRM Test' + inputs: + filePath: './build.ps1' + arguments: '-Tasks hqrmtest' + pwsh: false + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: 'output/testResults/NUnit*.xml' + testRunTitle: 'HQRM' - - job: test_linux - displayName: 'Linux' - timeoutInMinutes: 0 - pool: - vmImage: 'ubuntu 18.04' - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact' - inputs: - buildType: 'current' - artifactName: $(buildArtifactName) - targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' - - script: | + - job: test_windows_wps + displayName: 'Unit Windows (WPS)' + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: test + displayName: 'Run Unit Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test" + pwsh: false + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Test Windows (WPS)' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageWindowsWPS' + parallel: true + + - job: test_windows_ps + displayName: 'Unit Windows (PS)' + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: test + displayName: 'Run Unit Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test" + pwsh: true + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Test Windows (PS)' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageWindowsPS' + parallel: true + + - job: test_linux + displayName: 'Unit Linux' + pool: + vmImage: 'ubuntu-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - script: | wget https://github.com/microsoft/omi/releases/download/v1.6.8-1/omi-1.6.8-1.ssl_110.ulinux.x64.deb wget https://github.com/microsoft/PowerShell-DSC-for-Linux/releases/download/v1.2.1-0/dsc-1.2.1-0.ssl_100.x64.deb @@ -103,157 +169,152 @@ stages: # This is a fix to the following DSC for Linux issue: https://github.com/microsoft/PowerShell-DSC-for-Linux/issues/674 sudo mv /etc/opt/omi/conf/dsc/configuration/baseregistration /etc/opt/omi/conf/dsc/configuration/BaseRegistration sudo mv /etc/opt/omi/conf/dsc/configuration/BaseRegistration/baseresource.schema.mof /etc/opt/omi/conf/dsc/configuration/BaseRegistration/BaseResource.schema.mof - displayName: 'Install DSC for Linux' - - - task: PowerShell@2 - name: test - displayName: 'Run Tests' - inputs: - filePath: './build.ps1' - arguments: '-tasks test' - - task: PublishTestResults@2 - displayName: 'Publish Test Results' - condition: succeededOrFailed() - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: 'output/testResults/NUnit*.xml' - testRunTitle: 'Linux' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Test Artifact' - inputs: - targetPath: '$(buildFolderName)/$(testResultFolderName)/' - artifactName: 'CodeCoverageLinux' - parallel: true + displayName: 'Install DSC for Linux' + - task: PowerShell@2 + name: test + displayName: 'Run Unit Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test" + pwsh: true + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Test Linux' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageLinux' + parallel: true - - job: test_windows_ps - displayName: 'Windows (Windows PowerShell)' - timeoutInMinutes: 0 - pool: - vmImage: 'windows-latest' - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact' - inputs: - buildType: 'current' - artifactName: $(buildArtifactName) - targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' - - task: PowerShell@2 - name: test - displayName: 'Run Tests' - inputs: - filePath: './build.ps1' - arguments: '-tasks test' - pwsh: false - - task: PublishTestResults@2 - displayName: 'Publish Test Results' - condition: succeededOrFailed() - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: 'output/testResults/NUnit*.xml' - testRunTitle: 'Windows Server Core (Windows PowerShell)' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Test Artifact' - inputs: - targetPath: '$(buildFolderName)/$(testResultFolderName)/' - artifactName: 'CodeCoverageWinPS51' - parallel: true + - job: test_macos + displayName: 'Unit macOS' + pool: + vmImage: 'macos-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: test + displayName: 'Run Unit Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test" + pwsh: true + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Test macOS' + - task: PublishPipelineArtifact@1 + displayName: 'Publish Test Artifact' + inputs: + targetPath: '$(buildFolderName)/$(testResultFolderName)/' + artifactName: 'CodeCoverageMacOS' + parallel: true - - job: test_macos - displayName: 'macOS' - timeoutInMinutes: 0 - pool: - vmImage: 'macos-latest' - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact' - inputs: - buildType: 'current' - artifactName: $(buildArtifactName) - targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' - - task: PowerShell@2 - name: test - displayName: 'Run Tests' - inputs: - filePath: './build.ps1' - arguments: '-tasks test' - pwsh: true - - task: PublishTestResults@2 - displayName: 'Publish Test Results' - condition: succeededOrFailed() - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: 'output/testResults/NUnit*.xml' - testRunTitle: 'MacOS' - - task: PublishPipelineArtifact@1 - displayName: 'Publish Test Artifact' - inputs: - targetPath: '$(buildFolderName)/$(testResultFolderName)/' - artifactName: 'CodeCoverageMacOS' - parallel: true + - job: Test_Integration + displayName: 'Integration' + pool: + vmImage: 'windows-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: PowerShell@2 + name: configureWinRM + displayName: 'Configure WinRM' + inputs: + targetType: 'inline' + script: 'winrm quickconfig -quiet' + pwsh: false + - task: PowerShell@2 + name: test + displayName: 'Run Integration Test' + inputs: + filePath: './build.ps1' + arguments: "-Tasks test -CodeCoverageThreshold 0 -PesterScript 'tests/Integration'" + pwsh: false + - task: PublishTestResults@2 + displayName: 'Publish Test Results' + condition: succeededOrFailed() + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(buildFolderName)/$(testResultFolderName)/NUnit*.xml' + testRunTitle: 'Integration' - - job: Code_Coverage - displayName: 'Publish Code Coverage' - dependsOn: - - test_macos - - test_linux - - test_windows_core - - test_windows_ps - pool: - vmImage: 'ubuntu 18.04' - timeoutInMinutes: 0 - steps: - - pwsh: | - $repositoryOwner,$repositoryName = $env:BUILD_REPOSITORY_NAME -split '/' - echo "##vso[task.setvariable variable=RepositoryOwner;isOutput=true]$repositoryOwner" - echo "##vso[task.setvariable variable=RepositoryName;isOutput=true]$repositoryName" - name: dscBuildVariable - displayName: 'Set Environment Variables' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Pipeline Artifact' - inputs: - buildType: 'current' - artifactName: $(buildArtifactName) - targetPath: '$(Build.SourcesDirectory)/$(buildArtifactName)' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Test Artifact macOS' - inputs: - buildType: 'current' - artifactName: 'CodeCoverageMacOS' - targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Test Artifact Linux' - inputs: - buildType: 'current' - artifactName: 'CodeCoverageLinux' - targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Test Artifact Windows (PS 5.1)' - inputs: - buildType: 'current' - artifactName: 'CodeCoverageWinPS51' - targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' - - task: DownloadPipelineArtifact@2 - displayName: 'Download Test Artifact Windows (PS7)' - inputs: - buildType: 'current' - artifactName: 'CodeCoverageWinPS7' - targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)' - - task: PowerShell@2 - name: merge - displayName: 'Merge Code Coverage files' - inputs: - filePath: './build.ps1' - arguments: '-tasks merge' - pwsh: true - - task: PublishCodeCoverageResults@1 - displayName: 'Publish Azure Code Coverage' - inputs: - codeCoverageTool: 'JaCoCo' - summaryFileLocation: '$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' - pathToSources: '$(Build.SourcesDirectory)/source/' - - script: | - bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" - displayName: 'Upload to Codecov.io' + - job: Code_Coverage + displayName: 'Publish Code Coverage' + dependsOn: + - test_windows_wps + - test_windows_ps + - test_linux + - test_macos + pool: + vmImage: 'ubuntu-latest' + timeoutInMinutes: 0 + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifact' + inputs: + buildType: 'current' + artifactName: $(buildArtifactName) + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)' + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Linux' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageLinux' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/CodeCoverageLinux' + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Windows (WPS)' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageWindowsWPS' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/CodeCoverageWindowsWPS' + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact Windows (PS)' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageWindowsPS' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/CodeCoverageWindowsPS' + - task: DownloadPipelineArtifact@2 + displayName: 'Download Test Artifact macOS' + inputs: + buildType: 'current' + artifactName: 'CodeCoverageMacOS' + targetPath: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/CodeCoverageMacOS' + - task: PowerShell@2 # Merges the code coverage files. + name: merge + displayName: 'Merge Code Coverage files' + inputs: + filePath: './build.ps1' + arguments: '-tasks merge' + pwsh: true + - task: PublishCodeCoverageResults@1 + displayName: 'Publish Code Coverage to Azure DevOps' + inputs: + codeCoverageTool: 'JaCoCo' + summaryFileLocation: '$(Build.SourcesDirectory)/$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml' + pathToSources: '$(Build.SourcesDirectory)/$(sourceFolderName)/' + - script: | + bash <(curl -s https://codecov.io/bash) -f "./$(buildFolderName)/$(testResultFolderName)/JaCoCo_coverage.xml" + displayName: 'Publish Code Coverage to Codecov.io' - stage: Deploy dependsOn: Test @@ -270,7 +331,7 @@ stages: - job: Deploy_Module displayName: 'Deploy Module' pool: - vmImage: 'ubuntu 18.04' + vmImage: 'ubuntu-latest' steps: - task: DownloadPipelineArtifact@2 displayName: 'Download Pipeline Artifact' @@ -288,8 +349,8 @@ stages: env: GitHubToken: $(GitHubToken) GalleryApiToken: $(GalleryApiToken) - ReleaseBranch: main - MainGitBranch: main + ReleaseBranch: $(defaultBranch) + MainGitBranch: $(defaultBranch) - task: PowerShell@2 name: sendChangelogPR displayName: 'Send Changelog PR' @@ -299,5 +360,5 @@ stages: pwsh: true env: GitHubToken: $(GitHubToken) - ReleaseBranch: main - MainGitBranch: main + ReleaseBranch: $(defaultBranch) + MainGitBranch: $(defaultBranch) diff --git a/build.ps1 b/build.ps1 index 0639785..712d4b2 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,365 +1,519 @@ <# + .DESCRIPTION + Bootstrap and build script for PowerShell module CI/CD pipeline -.DESCRIPTION - Bootstrap and build script for PowerShell module pipeline + .PARAMETER Tasks + The task or tasks to run. The default value is '.' (runs the default task). + .PARAMETER CodeCoverageThreshold + The code coverage target threshold to uphold. Set to 0 to disable. + The default value is '' (empty string). + + .PARAMETER BuildConfig + Not yet written. + + .PARAMETER OutputDirectory + Specifies the folder to build the artefact into. The default value is 'output'. + + .PARAMETER BuiltModuleSubdirectory + Subdirectory name to build the module (under $OutputDirectory). The default + value is '' (empty string). + + .PARAMETER RequiredModulesDirectory + Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency + and PSDepend where to save the required modules. It is also possible to use + 'CurrentUser' och 'AllUsers' to install missing dependencies. You can override + the value for PSDepend in the Build.psd1 build manifest. The default value is + 'output/RequiredModules'. + + .PARAMETER PesterScript + One or more paths that will override the Pester configuration in build + configuration file when running the build task Invoke_Pester_Tests. + + If running Pester 5 test, use the alias PesterPath to be future-proof. + + .PARAMETER PesterTag + Filter which tags to run when invoking Pester tests. This is used in the + Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER PesterExcludeTag + Filter which tags to exclude when invoking Pester tests. This is used in + the Invoke-Pester.pester.build.ps1 tasks. + + .PARAMETER DscTestTag + Filter which tags to run when invoking DSC Resource tests. This is used + in the DscResource.Test.build.ps1 tasks. + + .PARAMETER DscTestExcludeTag + Filter which tags to exclude when invoking DSC Resource tests. This is + used in the DscResource.Test.build.ps1 tasks. + + .PARAMETER ResolveDependency + Not yet written. + + .PARAMETER BuildInfo + The build info object from ModuleBuilder. Defaults to an empty hashtable. + + .PARAMETER AutoRestore + Not yet written. #> [CmdletBinding()] param ( [Parameter(Position = 0)] - [string[]]$Tasks = '.', + [System.String[]] + $Tasks = '.', [Parameter()] - [String] + [System.String] $CodeCoverageThreshold = '', [Parameter()] - [validateScript( + [System.String] + [ValidateScript( { Test-Path -Path $_ } )] - $BuildConfig = './build.yaml', + $BuildConfig, [Parameter()] - # A Specific folder to build the artefact into. + [System.String] $OutputDirectory = 'output', [Parameter()] - # Subdirectory name to build the module (under $OutputDirectory) + [System.String] $BuiltModuleSubdirectory = '', - # Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency & PSDepend where to save the required modules, - # or use CurrentUser, AllUsers to target where to install missing dependencies - # You can override the value for PSDepend in the Build.psd1 build manifest - # This defaults to $OutputDirectory/modules (by default: ./output/modules) [Parameter()] + [System.String] $RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'), [Parameter()] - [string[]] + # This alias is to prepare for the rename of this parameter to PesterPath when Pester 4 support is removed + [Alias('PesterPath')] + [System.Object[]] $PesterScript, - # Filter which tags to run when invoking Pester tests - # This is used in the Invoke-Pester.pester.build.ps1 tasks [Parameter()] - [string[]] + [System.String[]] $PesterTag, - # Filter which tags to exclude when invoking Pester tests - # This is used in the Invoke-Pester.pester.build.ps1 tasks [Parameter()] - [string[]] + [System.String[]] $PesterExcludeTag, - # Filter which tags to run when invoking DSC Resource tests - # This is used in the DscResource.Test.build.ps1 tasks [Parameter()] - [string[]] + [System.String[]] $DscTestTag, - # Filter which tags to exclude when invoking DSC Resource tests - # This is used in the DscResource.Test.build.ps1 tasks [Parameter()] - [string[]] + [System.String[]] $DscTestExcludeTag, [Parameter()] [Alias('bootstrap')] - [switch]$ResolveDependency, + [System.Management.Automation.SwitchParameter] + $ResolveDependency, [Parameter(DontShow)] [AllowNull()] + [System.Collections.Hashtable] $BuildInfo, [Parameter()] - [switch] + [System.Management.Automation.SwitchParameter] $AutoRestore ) -# The BEGIN block (at the end of this file) handles the Bootstrap of the Environment before Invoke-Build can run the tasks -# if the -ResolveDependency (aka Bootstrap) is specified, the modules are already available, and can be auto loaded +<# + The BEGIN block (at the end of this file) handles the Bootstrap of the Environment + before Invoke-Build can run the tasks if the parameter ResolveDependency (or + parameter alias Bootstrap) is specified. +#> process { if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') { - # Only run the process block through InvokeBuild (Look at the Begin block at the bottom of this script) + # Only run the process block through InvokeBuild (look at the Begin block at the bottom of this script). return } - # Execute the Build Process from the .build.ps1 path. - Push-Location -Path $PSScriptRoot -StackName BeforeBuild + # Execute the Build process from the .build.ps1 path. + Push-Location -Path $PSScriptRoot -StackName 'BeforeBuild' try { - Write-Host -ForeGroundColor magenta "[build] Parsing defined tasks" + Write-Host -Object "[build] Parsing defined tasks" -ForeGroundColor Magenta - # Load Default BuildInfo if not provided as parameter - if (!$PSBoundParameters.ContainsKey('BuildInfo')) + # Load the default BuildInfo if the parameter BuildInfo is not set. + if (-not $PSBoundParameters.ContainsKey('BuildInfo')) { try { - if (Test-Path $BuildConfig) + if (Test-Path -Path $BuildConfig) { - $ConfigFile = (Get-Item -Path $BuildConfig) - Write-Host "[build] Loading Configuration from $ConfigFile" - $BuildInfo = switch -Regex ($ConfigFile.Extension) + $configFile = Get-Item -Path $BuildConfig + + Write-Host -Object "[build] Loading Configuration from $configFile" + + $BuildInfo = switch -Regex ($configFile.Extension) { # Native Support for PSD1 '\.psd1' { + if (-not (Get-Command -Name Import-PowerShellDataFile -ErrorAction SilentlyContinue)) + { + Import-Module -Name Microsoft.PowerShell.Utility -RequiredVersion 3.1.0.0 + } + Import-PowerShellDataFile -Path $BuildConfig } + # Support for yaml when module PowerShell-Yaml is available '\.[yaml|yml]' { - Import-Module -ErrorAction Stop -Name 'powershell-yaml' - ConvertFrom-Yaml -Yaml (Get-Content -Raw $ConfigFile) + Import-Module -Name 'powershell-yaml' -ErrorAction Stop + + ConvertFrom-Yaml -Yaml (Get-Content -Raw $configFile) } + # Native Support for JSON and JSONC (by Removing comments) '\.[json|jsonc]' { - $JSONC = (Get-Content -Raw -Path $ConfigFile) - $JSON = $JSONC -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' - # This should probably be converted to hashtable for splatting - $JSON | ConvertFrom-Json + $jsonFile = Get-Content -Raw -Path $configFile + + $jsonContent = $jsonFile -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/' + + # Yaml is superset of JSON. + ConvertFrom-Yaml -Yaml $jsonContent } + + # Unknown extension, return empty hashtable. default { - Write-Error "Extension '$_' not supported. using @{}" + Write-Error -Message "Extension '$_' not supported. using @{}" + @{ } } } } else { - Write-Host -Object "Configuration file $BuildConfig not found" -ForegroundColor Red + Write-Host -Object "Configuration file '$($BuildConfig.FullName)' not found" -ForegroundColor Red + + # No config file was found, return empty hashtable. $BuildInfo = @{ } } } catch { - Write-Host -Object "Error loading Config $ConfigFile.`r`n Are you missing dependencies?" -ForegroundColor Yellow - Write-Host -Object "Make sure you run './build.ps1 -ResolveDependency -tasks noop' to restore the Required modules the first time" -ForegroundColor Yellow + $logMessage = "Error loading Config '$($BuildConfig.FullName)'.`r`nAre you missing dependencies?`r`nMake sure you run './build.ps1 -ResolveDependency -tasks noop' before running build to restore the required modules." + + Write-Host -Object $logMessage -ForegroundColor Yellow + $BuildInfo = @{ } - Write-Error $_.Exception.Message + + Write-Error -Message $_.Exception.Message } } - # If the Invoke-Build Task Header is specified in the Build Info, set it + # If the Invoke-Build Task Header is specified in the Build Info, set it. if ($BuildInfo.TaskHeader) { - Set-BuildHeader ([scriptblock]::Create($BuildInfo.TaskHeader)) + Set-BuildHeader -Script ([scriptblock]::Create($BuildInfo.TaskHeader)) } - # Import Tasks from modules via their exported aliases when defined in BUild Manifest - # https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks - if ($BuildInfo.containsKey('ModuleBuildTasks')) + <# + Add BuildModuleOutput to PSModule Path environment variable. + Moved here (not in begin block) because build file can contains BuiltSubModuleDirectory value. + #> + if ($BuiltModuleSubdirectory) + { + if (-not (Split-Path -IsAbsolute -Path $BuiltModuleSubdirectory)) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuiltModuleSubdirectory + } + else + { + $BuildModuleOutput = $BuiltModuleSubdirectory + } + } # test if BuiltModuleSubDirectory set in build config file + elseif ($BuildInfo.ContainsKey('BuiltModuleSubDirectory')) + { + $BuildModuleOutput = Join-Path -Path $OutputDirectory -ChildPath $BuildInfo['BuiltModuleSubdirectory'] + } + else + { + $BuildModuleOutput = $OutputDirectory + } + + # Pre-pending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder. + if ($powerShellModulePaths -notcontains $BuildModuleOutput) { - foreach ($Module in $BuildInfo['ModuleBuildTasks'].Keys) + Write-Host -Object "[build] Pre-pending '$BuildModuleOutput' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $BuildModuleOutput + [System.IO.Path]::PathSeparator + $env:PSModulePath + } + + <# + Import Tasks from modules via their exported aliases when defined in Build Manifest. + https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks + #> + if ($BuildInfo.ContainsKey('ModuleBuildTasks')) + { + foreach ($module in $BuildInfo['ModuleBuildTasks'].Keys) { try { - Write-Host -ForegroundColor DarkGray -Verbose "Importing tasks from module $Module" - $LoadedModule = Import-Module $Module -PassThru -ErrorAction Stop - foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($Module)) + Write-Host -Object "Importing tasks from module $module" -ForegroundColor DarkGray + + $loadedModule = Import-Module -Name $module -PassThru -ErrorAction Stop + + foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($module)) { - $LoadedModule.ExportedAliases.GetEnumerator().Where{ - # using -like to support wildcard - Write-Host -ForegroundColor DarkGray "`t Loading $($_.Key)..." + $loadedModule.ExportedAliases.GetEnumerator().Where{ + Write-Host -Object "`t Loading $($_.Key)..." -ForegroundColor DarkGray + + # Using -like to support wildcard. $_.Key -like $TaskToExport }.ForEach{ - # Dot sourcing the Tasks via their exported aliases + # Dot-sourcing the Tasks via their exported aliases. . (Get-Alias $_.Key) } } } catch { - Write-Host -ForegroundColor Red -Object "Could not load tasks for module $Module." - Write-Error $_ + Write-Host -Object "Could not load tasks for module $module." -ForegroundColor Red + + Write-Error -Message $_ } } } - # Loading Build Tasks defined in the source/tasks folder (will override the ones imported above if same task name) - Get-ChildItem -Path "source/tasks/" -Recurse -Include *.ps1 -ErrorAction Ignore | ForEach-Object { - "Importing file $($_.BaseName)" | Write-Verbose - . $_.FullName - } + <# + Loading Build Tasks defined in the source/tasks folder (will override + the ones imported above if same task name). + + Using 'source/task/' is changed specific for this module DscResource.DocGenerator + to be able to dogfood its own tasks + #> + Get-ChildItem -Path 'source/tasks/' -Recurse -Include '*.ps1' -ErrorAction Ignore | + ForEach-Object { + "Importing file $($_.BaseName)" | Write-Verbose - # Synopsis: Empty task, useful to test the bootstrap process + . $_.FullName + } + + # Synopsis: Empty task, useful to test the bootstrap process. task noop { } - # Define default task sequence ("."), can be overridden in the $BuildInfo + # Define default task sequence ("."), can be overridden in the $BuildInfo. task . { - Write-Build Yellow "No sequence currently defined for the default task" + Write-Build -Object 'No sequence currently defined for the default task' -ForegroundColor Yellow } - # Load Invoke-Build task sequences/workflows from $BuildInfo - Write-Host -ForegroundColor DarkGray "Adding Workflow from configuration:" - foreach ($Workflow in $BuildInfo.BuildWorkflow.keys) + Write-Host -Object 'Adding Workflow from configuration:' -ForegroundColor DarkGray + + # Load Invoke-Build task sequences/workflows from $BuildInfo. + foreach ($workflow in $BuildInfo.BuildWorkflow.keys) { - Write-Verbose "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')" - $WorkflowItem = $BuildInfo.BuildWorkflow.($Workflow) - if ($WorkflowItem.Trim() -match '^\{(?[\w\W]*)\}$') + Write-Verbose -Message "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')." + + $workflowItem = $BuildInfo.BuildWorkflow.($workflow) + + if ($workflowItem.Trim() -match '^\{(?[\w\W]*)\}$') { - $WorkflowItem = [ScriptBlock]::Create($Matches['sb']) + $workflowItem = [ScriptBlock]::Create($Matches['sb']) } - Write-Host -ForegroundColor DarkGray " +-> $Workflow" - task $Workflow $WorkflowItem + + Write-Host -Object " +-> $workflow" -ForegroundColor DarkGray + + task $workflow $workflowItem } - Write-Host -ForeGroundColor magenta "[build] Executing requested workflow: $($Tasks -join ', ')" + Write-Host -Object "[build] Executing requested workflow: $($Tasks -join ', ')" -ForeGroundColor Magenta } finally { - Pop-Location -StackName BeforeBuild + Pop-Location -StackName 'BeforeBuild' } } Begin { + # Find build config if not specified. + if (-not $BuildConfig) + { + $config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction Ignore + + if (-not $config -or ($config -is [System.Array] -and $config.Length -le 0)) + { + throw 'No build configuration found. Specify path via parameter BuildConfig.' + } + elseif ($config -is [System.Array]) + { + if ($config.Length -gt 1) + { + throw 'More than one build configuration found. Specify which path to use via parameter BuildConfig.' + } + + $BuildConfig = $config[0] + } + else + { + $BuildConfig = $config + } + } + # Bootstrapping the environment before using Invoke-Build as task runner - if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { - Write-Host -foregroundColor Green "[pre-build] Starting Build Init" - Push-Location $PSScriptRoot -StackName BuildModule + Write-Host -Object "[pre-build] Starting Build Init" -ForegroundColor Green + + Push-Location $PSScriptRoot -StackName 'BuildModule' } if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers')) { - # Installing modules instead of saving them - Write-Host -foregroundColor Green "[pre-build] Required Modules will be installed for $RequiredModulesDirectory, not saved." - # Tell Resolve-Dependency to use provided scope as the -PSDependTarget if not overridden in Build.psd1 + # Installing modules instead of saving them. + Write-Host -Object "[pre-build] Required Modules will be installed to the PowerShell module path that is used for $RequiredModulesDirectory." -ForegroundColor Green + + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> $PSDependTarget = $RequiredModulesDirectory } else { - if (-Not (Split-Path -IsAbsolute -Path $OutputDirectory)) + if (-not (Split-Path -IsAbsolute -Path $OutputDirectory)) { $OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory } - # Resolving the absolute path to save the required modules to - if (-Not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) + # Resolving the absolute path to save the required modules to. + if (-not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory)) { $RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory } - # Create the output/modules folder if not exists, or resolve the Absolute path otherwise - if (Resolve-Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) + # Create the output/modules folder if not exists, or resolve the Absolute path otherwise. + if (Resolve-Path -Path $RequiredModulesDirectory -ErrorAction SilentlyContinue) { - Write-Debug "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" - $RequiredModulesPath = Convert-Path $RequiredModulesDirectory + Write-Debug -Message "[pre-build] Required Modules path already exist at $RequiredModulesDirectory" + + $requiredModulesPath = Convert-Path -Path $RequiredModulesDirectory } else { - Write-Host -foregroundColor Green "[pre-build] Creating required modules directory $RequiredModulesDirectory." - $RequiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName - } + Write-Host -Object "[pre-build] Creating required modules directory $RequiredModulesDirectory." -ForegroundColor Green - # Prepending $RequiredModulesPath folder to PSModulePath to resolve from this folder FIRST - if ($RequiredModulesDirectory -notIn @('CurrentUser', 'AllUsers') -and - (($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $RequiredModulesDirectory)) - { - Write-Host -foregroundColor Green "[pre-build] Prepending '$RequiredModulesDirectory' folder to PSModulePath" - $Env:PSModulePath = $RequiredModulesDirectory + [io.path]::PathSeparator + $Env:PSModulePath + $requiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName } - # Checking if the user should -ResolveDependency - if ((!(Get-Module -ListAvailable powershell-yaml) -or !(Get-Module -ListAvailable InvokeBuild) -or !(Get-Module -ListAvailable PSDepend)) -and !$ResolveDependency) + $powerShellModulePaths = $env:PSModulePath -split [System.IO.Path]::PathSeparator + + # Pre-pending $requiredModulesPath folder to PSModulePath to resolve from this folder FIRST. + if ($RequiredModulesDirectory -notin @('CurrentUser', 'AllUsers') -and + ($powerShellModulePaths -notcontains $RequiredModulesDirectory)) { - if ($AutoRestore -or !$PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') - { - Write-Host -ForegroundColor Yellow "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" - $ResolveDependency = $true - } - else - { - Write-Warning "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter." - Write-Warning "Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." - } + Write-Host -Object "[pre-build] Pre-pending '$RequiredModulesDirectory' folder to PSModulePath" -ForegroundColor Green + + $env:PSModulePath = $RequiredModulesDirectory + [System.IO.Path]::PathSeparator + $env:PSModulePath } - if ($BuiltModuleSubdirectory) + $powerShellYamlModule = Get-Module -Name 'powershell-yaml' -ListAvailable + $invokeBuildModule = Get-Module -Name 'InvokeBuild' -ListAvailable + $psDependModule = Get-Module -Name 'PSDepend' -ListAvailable + + # Checking if the user should -ResolveDependency. + if (-not ($powerShellYamlModule -and $invokeBuildModule -and $psDependModule) -and -not $ResolveDependency) { - if (-Not (Split-Path -IsAbsolute $BuiltModuleSubdirectory)) + if ($AutoRestore -or -not $PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build') { - $BuildModuleOutput = Join-Path $OutputDirectory $BuiltModuleSubdirectory + Write-Host -Object "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n" -ForegroundColor Yellow + + $ResolveDependency = $true } else { - $BuildModuleOutput = $BuiltModuleSubdirectory + Write-Warning -Message "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter. Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task." } } - else - { - $BuildModuleOutput = $OutputDirectory - } - - # Prepending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder - if (($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $BuildModuleOutput) - { - Write-Host -foregroundColor Green "[pre-build] Prepending '$BuildModuleOutput' folder to PSModulePath" - $Env:PSModulePath = $BuildModuleOutput + [io.path]::PathSeparator + $Env:PSModulePath - } - # Tell Resolve-Dependency to use $RequiredModulesPath as -PSDependTarget if not overridden in Build.psd1 - $PSDependTarget = $RequiredModulesPath + <# + The variable $PSDependTarget will be used below when building the splatting + variable before calling Resolve-Dependency.ps1, unless overridden in the + file Resolve-Dependency.psd1. + #> + $PSDependTarget = $requiredModulesPath } if ($ResolveDependency) { - Write-Host -Object "[pre-build] Resolving dependencies." -foregroundColor Green - $ResolveDependencyParams = @{ } + Write-Host -Object "[pre-build] Resolving dependencies." -ForegroundColor Green + $resolveDependencyParams = @{ } - # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency + # If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency. if ($BuildConfig -match '\.[yaml|yml]$') { - $ResolveDependencyParams.add('WithYaml', $True) + $resolveDependencyParams.Add('WithYaml', $true) } - $ResolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').parameters.keys - foreach ($CmdParameter in $ResolveDependencyAvailableParams) - { + $resolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').Parameters.Keys + foreach ($cmdParameter in $resolveDependencyAvailableParams) + { # The parameter has been explicitly used for calling the .build.ps1 - if ($MyInvocation.BoundParameters.ContainsKey($CmdParameter)) + if ($MyInvocation.BoundParameters.ContainsKey($cmdParameter)) { - $ParamValue = $MyInvocation.BoundParameters.ContainsKey($CmdParameter) - Write-Debug " adding $CmdParameter :: $ParamValue [from user-provided parameters to Build.ps1]" - $ResolveDependencyParams.Add($CmdParameter, $ParamValue) + $paramValue = $MyInvocation.BoundParameters.Item($cmdParameter) + + Write-Debug " adding $cmdParameter :: $paramValue [from user-provided parameters to Build.ps1]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) } # Use defaults parameter value from Build.ps1, if any else { - if ($ParamValue = Get-Variable -Name $CmdParameter -ValueOnly -ErrorAction Ignore) + $paramValue = Get-Variable -Name $cmdParameter -ValueOnly -ErrorAction Ignore + + if ($paramValue) { - Write-Debug " adding $CmdParameter :: $ParamValue [from default Build.ps1 variable]" - $ResolveDependencyParams.add($CmdParameter, $ParamValue) + Write-Debug " adding $cmdParameter :: $paramValue [from default Build.ps1 variable]" + + $resolveDependencyParams.Add($cmdParameter, $paramValue) } } } - Write-Host -foregroundColor Green "[pre-build] Starting bootstrap process." - .\Resolve-Dependency.ps1 @ResolveDependencyParams + Write-Host -Object "[pre-build] Starting bootstrap process." -ForegroundColor Green + + .\Resolve-Dependency.ps1 @resolveDependencyParams } - if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1') + if ($MyInvocation.ScriptName -notlike '*Invoke-Build.ps1') { - Write-Verbose "Bootstrap completed. Handing back to InvokeBuild." + Write-Verbose -Message "Bootstrap completed. Handing back to InvokeBuild." + if ($PSBoundParameters.ContainsKey('ResolveDependency')) { - Write-Verbose "Dependency already resolved. Removing task" + Write-Verbose -Message "Dependency already resolved. Removing task." + $null = $PSBoundParameters.Remove('ResolveDependency') } - Write-Host -foregroundColor Green "[build] Starting build with InvokeBuild." + + Write-Host -Object "[build] Starting build with InvokeBuild." -ForegroundColor Green + Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path - Pop-Location -StackName BuildModule + + Pop-Location -StackName 'BuildModule' + return } } diff --git a/build.yaml b/build.yaml index 4eff62d..f329934 100644 --- a/build.yaml +++ b/build.yaml @@ -2,6 +2,7 @@ #################################################### # ModuleBuilder Configuration # #################################################### + CopyPaths: - en-US - tasks @@ -10,18 +11,18 @@ VersionedOutputDirectory: true Prefix: ./prefix.ps1 #################################################### -# ModuleBuilder Dependent Modules Configuration # +# ModuleBuilder Submodules Configuration # #################################################### NestedModule: - DscResource.Common: - CopyOnly: true - Path: ./output/RequiredModules/DscResource.Common - AddToManifest: false - Exclude: PSGetModuleInfo.xml + DscResource.Common: + CopyOnly: true + Path: ./output/RequiredModules/DscResource.Common + AddToManifest: false + Exclude: PSGetModuleInfo.xml #################################################### -# DscResource.DocGenerator Pipeline Configuration # +# Sampler Pipeline Configuration # #################################################### BuildWorkflow: '.': @@ -33,6 +34,7 @@ BuildWorkflow: - Build_Module_ModuleBuilder - Build_NestedModules_ModuleBuilder - Create_changelog_release_output + - Generate_Conceptual_Help - Generate_Wiki_Content pack: @@ -51,11 +53,12 @@ BuildWorkflow: - Merge_CodeCoverage_Files publish: - - Publish_release_to_GitHub - publish_module_to_gallery + - Publish_Release_To_GitHub - Publish_GitHub_Wiki_Content + #################################################### # PESTER Configuration # #################################################### @@ -69,12 +72,13 @@ Pester: - tests/unit ExcludeTag: Tag: - CodeCoverageThreshold: 80 + CodeCoverageThreshold: 85 + CodeCoverageOutputFile: JaCoCo_Merge.xml CodeCoverageOutputFileEncoding: ascii CodeCoverage: CodeCoverageMergedOutputFile: JaCoCo_coverage.xml - CodeCoverageFilePattern: Codecov*.xml + CodeCoverageFilePattern: JaCoCo_Merge.xml DscTest: ExcludeTag: @@ -85,11 +89,6 @@ DscTest: - Modules/DscResource.Common MainGitBranch: main -Resolve-Dependency: - Gallery: '' - AllowPrerelease: false - Verbose: false - ModuleBuildTasks: Sampler: - '*.build.Sampler.ib.tasks' @@ -114,6 +113,9 @@ GitHubConfig: GitHubConfigUserEmail: dsccommunity@outlook.com UpdateChangelogOnPrerelease: false +#################################################### +# DscResource.DocGenerator Configuration # +#################################################### DscResource.DocGenerator: Publish_GitHub_Wiki_Content: Debug: true diff --git a/codecov.yml b/codecov.yml index 02973e2..bff75ea 100644 --- a/codecov.yml +++ b/codecov.yml @@ -25,4 +25,4 @@ coverage: threshold: 5 fixes: - - '\d+\.\d+\.\d+\/::source/' # move path "X.Y.Z/" => "source/" + - '^\d+\.\d+\.\d+::source' # move path "X.Y.Z" => "source" diff --git a/source/Private/Copy-WikiFolder.ps1 b/source/Private/Copy-WikiFolder.ps1 index 926cf1d..dc36c94 100644 --- a/source/Private/Copy-WikiFolder.ps1 +++ b/source/Private/Copy-WikiFolder.ps1 @@ -39,13 +39,13 @@ function Copy-WikiFolder $Force ) - Write-Verbose -Message ($localizedData.CopyWikiFoldersMessage -f ($Path -join ''', ''')) + Write-Verbose -Message ($script:localizedData.CopyWikiFoldersMessage -f ($Path -join ''', ''')) $wikiFiles = Get-ChildItem -Recurse -Path $Path foreach ($file in $wikiFiles) { - Write-Verbose -Message ($localizedData.CopyFileMessage -f $file.Name) + Write-Verbose -Message ($script:localizedData.CopyFileMessage -f $file.Name) if ($file.DirectoryName -eq $Path) { diff --git a/source/Private/Get-TemporaryPath.ps1 b/source/Private/Get-TemporaryPath.ps1 index d1a1af3..db67874 100644 --- a/source/Private/Get-TemporaryPath.ps1 +++ b/source/Private/Get-TemporaryPath.ps1 @@ -45,7 +45,7 @@ function Get-TemporaryPath $temporaryPath = '/tmp' } - Default + default { throw 'Cannot set the temporary path. Unknown operating system.' } diff --git a/source/Private/New-TempFolder.ps1 b/source/Private/New-TempFolder.ps1 index e77c153..24dde73 100644 --- a/source/Private/New-TempFolder.ps1 +++ b/source/Private/New-TempFolder.ps1 @@ -38,7 +38,7 @@ function New-TempFolder if ($retries -gt $MaximumRetries) { - throw ($localizedData.NewTempFolderCreationError -f $tempPath) + throw ($script:localizedData.NewTempFolderCreationError -f $tempPath) } $name = [System.IO.Path]::GetRandomFileName() diff --git a/source/Private/New-WikiFooter.ps1 b/source/Private/New-WikiFooter.ps1 index 5571bf5..b05bf32 100644 --- a/source/Private/New-WikiFooter.ps1 +++ b/source/Private/New-WikiFooter.ps1 @@ -43,7 +43,7 @@ function New-WikiFooter if (-not (Test-Path -Path $wikiFooterWikiSourcePath)) { - Write-Verbose -Message ($localizedData.GenerateWikiFooterMessage -f $BaseName) + Write-Verbose -Message ($script:localizedData.GenerateWikiFooterMessage -f $BaseName) $wikiFooter = @() diff --git a/source/Private/New-WikiSidebar.ps1 b/source/Private/New-WikiSidebar.ps1 index 41103c9..41aee87 100644 --- a/source/Private/New-WikiSidebar.ps1 +++ b/source/Private/New-WikiSidebar.ps1 @@ -52,7 +52,7 @@ function New-WikiSidebar if (-not (Test-Path -Path $wikiSideBarWikiSourcePath)) { - Write-Verbose -Message ($localizedData.GenerateWikiSidebarMessage -f $BaseName) + Write-Verbose -Message ($script:localizedData.GenerateWikiSidebarMessage -f $BaseName) $WikiSidebarContent = @( "# $ModuleName Module" @@ -63,7 +63,7 @@ function New-WikiSidebar foreach ($file in $wikiFiles) { - Write-Verbose -Message ("`t{0}" -f ($localizedData.AddFileToSideBar -f $file.Name)) + Write-Verbose -Message ("`t{0}" -f ($script:localizedData.AddFileToSideBar -f $file.Name)) $WikiSidebarContent += "- [$($file.BaseName)]($($file.BaseName))" } diff --git a/source/Private/Out-GitResult.ps1 b/source/Private/Out-GitResult.ps1 index 6de0673..dd5e40b 100644 --- a/source/Private/Out-GitResult.ps1 +++ b/source/Private/Out-GitResult.ps1 @@ -78,7 +78,7 @@ function Out-GitResult { if ($ExitCode -eq 1) { - Write-Verbose -Message $localizedData.NothingToCommitToWiki + Write-Verbose -Message $script:localizedData.NothingToCommitToWiki } } } diff --git a/source/Public/Invoke-Git.ps1 b/source/Public/Invoke-Git.ps1 index 92104fa..6aba7c3 100644 --- a/source/Public/Invoke-Git.ps1 +++ b/source/Public/Invoke-Git.ps1 @@ -59,6 +59,8 @@ function Invoke-Git 'StandardError' = '' } + Write-Verbose -Message ($script:localizedData.InvokingGitMessage -f ($Arguments -join ' ')) + try { $process = New-Object -TypeName System.Diagnostics.Process diff --git a/source/Public/Publish-WikiContent.ps1 b/source/Public/Publish-WikiContent.ps1 index 2f835ca..ee36fab 100644 --- a/source/Public/Publish-WikiContent.ps1 +++ b/source/Public/Publish-WikiContent.ps1 @@ -140,16 +140,16 @@ function Publish-WikiContent Invoke-Git -WorkingDirectory $tempPath ` -Arguments @( 'remote', 'set-url', 'origin', "https://$($GitUserName):$($GitHubAccessToken)@github.com/$OwnerName/$RepositoryName.wiki.git" ) - Write-Verbose -Message $localizedData.AddWikiContentToGitRepoMessage + Write-Verbose -Message $script:localizedData.AddWikiContentToGitRepoMessage Invoke-Git -WorkingDirectory $tempPath -Arguments @( 'add', '*' ) - Write-Verbose -Message ($localizedData.CommitAndTagRepoChangesMessage -f $ModuleVersion) + Write-Verbose -Message ($script:localizedData.CommitAndTagRepoChangesMessage -f $ModuleVersion) Invoke-Git -WorkingDirectory $tempPath ` - -Arguments @( 'commit', '--message', "`"$($localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"" ) + -Arguments @( 'commit', '--message', "`"$($script:localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"" ) - Write-Verbose -Message $localizedData.PushUpdatedRepoMessage + Write-Verbose -Message $script:localizedData.PushUpdatedRepoMessage Invoke-Git -WorkingDirectory $tempPath ` -Arguments @( 'tag', '--annotate', $ModuleVersion, '--message', $ModuleVersion ) @@ -158,7 +158,7 @@ function Publish-WikiContent Invoke-Git -WorkingDirectory $tempPath -Arguments @( 'push', 'origin', $ModuleVersion ) - Write-Verbose -Message $localizedData.PublishWikiContentCompleteMessage + Write-Verbose -Message $script:localizedData.PublishWikiContentCompleteMessage } finally { diff --git a/source/en-US/DscResource.DocGenerator.strings.psd1 b/source/en-US/DscResource.DocGenerator.strings.psd1 index 1cb23bf..75e3ac8 100644 --- a/source/en-US/DscResource.DocGenerator.strings.psd1 +++ b/source/en-US/DscResource.DocGenerator.strings.psd1 @@ -18,7 +18,7 @@ ConvertFrom-StringData @' PublishWikiContentCompleteMessage = Publish Wiki Content complete. UpdateWikiCommitMessage = Updating Wiki with the content for module version '{0}'. NewTempFolderCreationError = Unable to create a temporary working folder in '{0}'. - InvokingGitMessage = Invoking Git '{0}'. + InvokingGitMessage = Invoking Git using arguments '{0}'. GenerateWikiSidebarMessage = Generating Wiki Sidebar '{0}'. GenerateWikiFooterMessage = Generating Wiki Footer '{0}'. CopyWikiFoldersMessage = Copying Wiki files from '{0}'. diff --git a/tests/unit/private/New-TempFolder.Tests.ps1 b/tests/unit/private/New-TempFolder.Tests.ps1 index 5bdc2d8..d8bcb19 100644 --- a/tests/unit/private/New-TempFolder.Tests.ps1 +++ b/tests/unit/private/New-TempFolder.Tests.ps1 @@ -58,7 +58,7 @@ InModuleScope $script:moduleName { It 'Should throw the correct error' { $tempPath = [System.IO.Path]::GetTempPath() - { New-TempFolder } | Should -Throw ($localizedData.NewTempFolderCreationError -f $tempPath) + { New-TempFolder } | Should -Throw ($script:localizedData.NewTempFolderCreationError -f $tempPath) } It 'Should call the expected mocks' { diff --git a/tests/unit/public/Publish-WikiContent.Tests.ps1 b/tests/unit/public/Publish-WikiContent.Tests.ps1 index f3fc655..d6cbf64 100644 --- a/tests/unit/public/Publish-WikiContent.Tests.ps1 +++ b/tests/unit/public/Publish-WikiContent.Tests.ps1 @@ -99,7 +99,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" + $Arguments[2] -eq "`"$($script:localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" } -Exactly -Times 0 -Scope It Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { @@ -196,7 +196,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" + $Arguments[2] -eq "`"$($script:localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" } -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { @@ -237,7 +237,7 @@ InModuleScope $script:moduleName { } -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"" + $Arguments[2] -eq "`"$($script:localizedData.UpdateWikiCommitMessage -f $ModuleVersion)`"" } } @@ -303,7 +303,7 @@ InModuleScope $script:moduleName { Assert-MockCalled -CommandName Invoke-Git -ParameterFilter { $Arguments[0] -eq 'commit' -and $Arguments[1] -eq '--message' -and - $Arguments[2] -eq "`"$($localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" + $Arguments[2] -eq "`"$($script:localizedData.UpdateWikiCommitMessage -f $mockPublishWikiContentParameters.ModuleVersion)`"" } -Exactly -Times 1 -Scope It Assert-MockCalled -CommandName Invoke-Git -ParameterFilter {