psmodulebuildhelper.psm1
#.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildEnvironment { <# .SYNOPSIS Get properties required to build the project. .DESCRIPTION Get the properties required to build the project, or elements of the project. All areas of the build process takes it's details from the data produced by this function. To influence part of the build process the data produced only need be altered. .EXAMPLE Get-BuildEnvironment Get build information for the current or any child directories. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release 1.1 - 04/04/18 - Added CodeCoverageThreshold parameter The idea came from the Indented.Build project (https://github.com/indented-automation/Indented.Build) and heavily modified. Credit should be given to that project. .LINK Get-ProjectEnvironment .LINK Get-TestEnvironment #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", "", Justification = "Easiest way of jumping out of the block when one fails")] [OutputType([PSObject])] [CmdletBinding()] Param ( # The type of release we should be building. If this is 'Unknown' then # the release type will be deteremined from the commit message. [String] [ValidateSet( 'Major', 'Minor', 'Build', 'None', 'Unknown' )] $ReleaseType, # The GitHub Username that has write access to this repo. [string] $GitHubUsername = '', # The GitHub Api Key that has write access to this repo. [string] $GitHubApiKey = '', # The PowerShell Gallery key for publishing the module. [string] $PSGalleryApiKey = '', # Filename of the PowerShell ScriptAnalyzer settings file. This file # will be searched for under the project root. [string] $PSSASettingsName = 'PSScriptAnalyzerSettings.psd1', # The PowerShell ScriptAnalyzer Custom Rules folder. This folder will be # searched for under the project root and any .psd1 files found under # teh folder will be used. [string] $PSSACustomRulesFolderName = 'CustomAnalyzerRules', # The threshold that test code coverage must meet expressed between 0.01 to 1.00. [ValidateRange(0.01, 1.00)] [single] $CodeCoverageThreshold = 0.8 ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } $buildInfo = Get-ProjectEnvironment @( 'LatestVersion', 'ReleaseVersion', 'ReleaseType', 'PSGalleryApiKey', 'RepoBranch', 'RepoLastCommitHash', ` 'RepoLastCommitMessage', 'GitHubUsername', 'GitHubApiKey', 'SourceManifestPath', 'SourceModulePath', ` 'BuildPath', 'BuildManifestPath', 'BuildModulePath', 'PSSASettingsPath', 'PSSACustomRulesPath', ` 'BuildArtifactPath', 'CodeCoverageThreshold', 'ReleaseNotes', 'ReleaseNotesPath' ) | ForEach-Object { $buildInfo | Add-Member -MemberType NoteProperty -Name $_ -Value '' } # ReleaseType & PSGallery API Key if ([string]::IsNullOrEmpty($ReleaseType) -or $ReleaseType -eq 'Unknown') { Write-Verbose "Determining 'ReleaseType' from the last commit message." $buildInfo.ReleaseType = Get-ReleaseType -CommitMessage (Get-GitLastCommitMessage) } else { $buildInfo.ReleaseType = $ReleaseType } $buildInfo.PSGalleryApiKey = $PSGalleryApiKey # Git $buildInfo.GitHubUsername = $GitHubUsername $buildInfo.GitHubApiKey = $GitHubApiKey try { $buildInfo.RepoBranch = Get-GitBranchName -ErrorAction SilentlyContinue $buildInfo.RepoLastCommitHash = Get-GitLastCommitHash -ErrorAction SilentlyContinue $buildInfo.RepoLastCommitMessage = Get-GitLastCommitMessage -ErrorAction SilentlyContinue } catch { } # Source Paths $buildInfo.SourceManifestPath = Join-Path -Path $buildInfo.SourcePath -ChildPath "$($buildInfo.ModuleName).psd1" $buildInfo.SourceModulePath = Join-Path -Path $buildInfo.SourcePath -ChildPath "$($buildInfo.ModuleName).psm1" # Versions if (Test-Path -Path $buildInfo.SourceManifestPath) { $buildInfo.LatestVersion = (Import-PowerShellDataFile -Path $buildInfo.SourceManifestPath).ModuleVersion $buildInfo.ReleaseVersion = Get-NextReleaseVersion -LatestVersion $buildInfo.LatestVersion -ReleaseType $buildInfo.ReleaseType } else { throw "Source manifest '$($buildInfo.SourceManifestPath)' does not exist." } # Build paths $buildPath = Join-Path -Path (Join-Path -Path $buildInfo.ProjectRootPath -ChildPath 'releases') -ChildPath $buildInfo.ReleaseVersion $buildInfo.BuildPath = $buildPath $buildInfo.BuildManifestPath = Join-Path -Path $buildPath -ChildPath "$($buildInfo.ModuleName).psd1" $buildInfo.BuildModulePath = Join-Path -Path $buildPath -ChildPath "$($buildInfo.ModuleName).psm1" # Build Abstract $buildInfo.BuildArtifactPath = Join-Path -Path $buildInfo.ProjectRootPath -ChildPath "$($buildInfo.ModuleName)-$($buildInfo.ReleaseVersion).zip" # PSSA $buildInfo.CodeCoverageThreshold = $CodeCoverageThreshold $settingsPath = Get-ChildItem -Path $PSSASettingsName -File -Recurse | Select-Object -First 1 if ($settingsPath) { $buildInfo.PSSASettingsPath = $settingsPath.FullName } else { Write-Verbose "Could not find PSScriptAnalyzer Settings file '$PSSASettingsName'." } $path = Get-ChildItem -Path $PSSACustomRulesFolderName -Directory -Recurse | Select-Object -First 1 if ($path -and (Test-Path -Path (Join-Path -Path $path.FullName -ChildPath '*.psd1'))) { $buildInfo.PSSACustomRulesPath = $path.FullName } else { Write-Verbose "No PSScriptAnalyzer Custom Rules folder '$PSSACustomRulesFolderName' found." } # ReleaseNotes $path = Join-Path -Path $buildInfo.ProjectRootPath -ChildPath 'CHANGELOG.md' if (Test-Path $path) { $buildInfo.ReleaseNotesPath = $path $buildInfo.ReleaseNotes = Get-BuildReleaseNote -Path $buildInfo.ReleaseNotesPath -Version $buildInfo.ReleaseVersion } $buildInfo } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildItem { <# .SYNOPSIS Gets the files to be used in the build. .DESCRIPTION Gets a list of files to be used in the build from the 'source' folder. .EXAMPLE Get-BuildItem -Type 'Static' -Path 'c:\mymodule\source' Gets a list of the static build items from the path 'c:\mymodule\source' .OUTPUTS [System.IO.FileInfo], [System.IO.DirectoryInfo] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release This function was lifted as is from Indented.Build (https://github.com/indented-automation/Indented.Build) so all credit goes to that project. .LINK #> [CmdletBinding()] [OutputType([System.IO.FileInfo], [System.IO.DirectoryInfo])] Param ( # Gets items by type. # # ShouldMerge - *.ps1 files from enum*, class*, priv*, pub* and # InitializeModule if present. # Static - Files which are not within a well known top-level # folder. Captures help content in en-US, format # files, configuration files, etc. [Parameter(Mandatory)] [ValidateSet('ShouldMerge', 'Static')] [String]$Type, # The path to the module 'source' folder. [Parameter(Mandatory, ValueFromPipeline)] [string]$Path, # Exclude script files containing PowerShell classes. [Switch]$ExcludeClass ) Push-Location $Path $itemTypes = [Ordered]@{ enumeration = 'enum*' class = 'class*' private = 'priv*' public = 'pub*' initialisation = 'InitializeModule.ps1' } if ($Type -eq 'ShouldMerge') { foreach ($itemType in $itemTypes.Keys) { if ($itemType -ne 'class' -or ($itemType -eq 'class' -and -not $ExcludeClass)) { $items = Get-ChildItem $itemTypes[$itemType] -Recurse -ErrorAction SilentlyContinue | Where-Object { -not $_.PSIsContainer -and $_.Extension -eq '.ps1' -and $_.Length -gt 0 } $orderingFilePath = Join-Path $itemTypes[$itemType] 'order.txt' if (Test-Path $orderingFilePath) { [String[]]$order = Get-Content (Resolve-Path $orderingFilePath).Path $items = $items | Sort-Object { $index = $order.IndexOf($_.BaseName) if ($index -eq -1) { [Int32]::MaxValue } else { $index } }, Name } $items } } } elseif ($Type -eq 'Static') { [String[]]$exclude = $itemTypes.Values + '*.config', 'test*', 'doc', 'help', '.build*.ps1' # Should work, fails when testing. # Get-ChildItem -Exclude $exclude foreach ($item in Get-ChildItem) { $shouldExclude = $false foreach ($exclusion in $exclude) { if ($item.Name -like $exclusion) { $shouldExclude = $true } } if (-not $shouldExclude) { $item } } } Pop-Location } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildOperatingSystemDetail { <# .SYNOPSIS Gets the operating system of the build system. .DESCRIPTION Gets the operating system of the build system in the following formt: - OSName - name of the operating system (Microsoft Windows 10 Pro) - OSArchitecture - x86 or x64 (64-bit) - Version - Version number (10.0.16299) The function is essentially a wrapper around Get-CimInstance win32_operatingsystem. .EXAMPLE Get-BuildOperatingSystem Returns the name, architecture and version of the current operating system. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-BuildPowerShellDetail .LINK Get-BuildEnvironment .LINK Get-BuildSystemEnvironment #> [CmdletBinding()] [OutputType([PSObject])] Param () Get-CimInstance -ClassName win32_operatingsystem -Property Caption, OSArchitecture, Version | ` Select-Object -Property @{l = 'OSName'; e = {$_.Caption} }, OSArchitecture, Version } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildPowerShellDetail { <# .SYNOPSIS Return the $PSVersionTable as an object. .DESCRIPTION Return the $PSVersionTable as an object. .EXAMPLE Get-BuildPowerShellDetail Returns the $PSVersionTable as a PSObject. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-BuildOperatingSystemDetail .LINK Get-BuildEnvironment .LINK Get-BuildEnvironmentDetail #> [CmdletBinding()] [OutputType([PSObject])] Param () New-Object -TypeName PSObject -Property $PSVersionTable } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildReleaseNote { <# .SYNOPSIS Gets the release notes from a file. .DESCRIPTION Gets the release notes for this version from a file. The file can be in markdown format or normal text. The format MUST be: ## v0.2 * Some release notes ## v0.0.5 Some more release notes Or: v0.1 Some release notes. v0.0.4 Some more release notes Note the empty line between the versions and no empty line between the version number and the release notes for that version. If using the markdown version (using ## at the start of the version number), they will be removed as the release notes shuld be simple text. Anything else will be retained. .EXAMPLE Get-BuildReleaseNote -Path 'CHANGELOG.md' -Version '0.0.2' Gets the build release notes for version 0.0.2 from CHANGELOG.md .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) Project : PSModuleBuildHelper (https://github.com/pauby/psmodulebuildhelper) History : 1.0 - 06/04/18 - Initial release 1.1 - 21/04/18 - Rewrote the function as I could not find a good enough regex to do the job. It looks clunky but it works. 2.0 - 23/04/18 - The version number line is not included by default so added a parameter to include it. Fixed issue where newlines were being added at the end of the notes. .LINK Get-BuildEnvironment #> [OutputType([String])] [CmdletBinding()] Param ( # The path to file containing the release notes. [Parameter(Mandatory)] [String] [ValidateScript( { Test-Path $_ })] $Path, # Version number to return the notes for. [Parameter(Mandatory)] [ValidateScript( { try { [version]$_ } catch { return $false } $true } )] [string] $Version, # Include the line containing the version number in the notes [switch] $IncludeVersionLine ) # number of matched lines $matched = 0 # loop through each line in the $path until we find a matching 'v$Version' # one we do keep each line until we come to either a blank line or the end # of the file $content = (Get-Content -Path $Path).Trim() foreach ($line in $content) { if ($matched -gt 0) { if ([string]::ISNullOrEmpty($line)) { return $notes } elseif ($matched -eq 1) { $notes += $line } else { $notes += "`r`n$line" } $matched++ } else { if ($line -match "v$Version") { Write-Verbose "Found version '$Version' in '$Path'." $matched++ if ($IncludeVersionLine.IsPresent) { # remove any '#' from markdown if ($line -match "^#*\s*(?<notes>.*)") { $notes += $matches.notes } else { $notes += $line } $notes += "`r`n" } } } } $notes } #.ExternalHelp PSModuleBuildHelper-help.xml $script:ModuleBuildScriptFilename = 'Start-ModuleBuild.ps1' function Get-BuildScript { <# .SYNOPSIS Provides the full path to the module build script. .DESCRIPTION Provides the full path to the module build script that is held in the module folder. .EXAMPLE Get-BuildScript Returns the path to the Start-ModuleBuild.ps1 .OUTPUTS [System.IO.FileInfo] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : v1.0 - 15/03/18 - Initial release .LINK #> [CmdletBinding()] [OutputType([System.IO.FileInfo])] Param() $rootPath = $PSScriptRoot if ((Split-Path -Path $rootPath -Leaf) -eq 'public') { $rootPath = Split-Path -Path $PSScriptRoot -Parent } # returns a [System.IO.FileInfo] object Get-Item -Path (Join-Path -Path $rootPath -ChildPath $script:ModuleBuildScriptFilename) } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildSystem { <# .SYNOPSIS Gets the current build system. .DESCRIPTION Gets the current build system, such as AppVeyor, GitLab CI, Teamcity etc. If a build system cannot be detected (such as runnning on a local machine), then 'Unknown' will be returned. .EXAMPLE Get-BuildSystem Return the current build system. .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-BuildSystemEnvironment .LINK Get-BuildEnvironment #> [CmdletBinding()] [OutputType([String])] Param () # todo Make these values Enums for use elsewhere? $system = switch ((Get-Item env:).name) { 'APPVEYOR_BUILD_FOLDER' { 'AppVeyor'; break } 'GITLAB_CI' { 'GitLab' ; break } 'JENKINS_URL' { 'Jenkins'; break } 'BUILD_REPOSITORY_URI' { 'VSTS'; break } 'TEAMCITY_VERSION' { 'Teamcity'; break } 'BAMBOO_BUILDKEY' { 'Bamboo'; break } 'GOCD_SERVER_URL' { 'GoCD'; break } 'TRAVIS' { 'Travis'; break } } if (-not $system) { $system = 'Unknown' } $system } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-BuildSystemEnvironment { <# .SYNOPSIS Get the current build system environment variables. .DESCRIPTION Get the current build system environment variables. Primarily for use in a CI / CD build environments where it is useful to see. There is a default set of keywords that are searched for which can be replaced. The following CI / CD systems are supported: - AppVeyor - Unknown (retrieves ALL of the current environment variables) The data is returned as a PSObject. .EXAMPLE Get-BuildSystemEnvironment Returns the environment variables for the current build environment. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-BuildSystem .LINK Get-BuildEnvironment #> [CmdletBinding()] [OutputType([PSObject])] Param () $envVars = switch (Get-BuildSystem) { 'AppVeyor' { Get-Item env:APPVEYOR* } 'Unknown' { Get-Item env:* } } # return the environment as an object $envObject = New-Object -TypeName psobject $envVars.GetEnumerator() | ForEach-Object { $envObject | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value } $envObject } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-ChangelogVersion { <# .SYNOPSIS Gets the version from the changelog. .DESCRIPTION Gets the version from the changelog. .EXAMPLE Get-ChangelogVersion -Path 'c:\mymodule\chaneglog.md' This will return the first version listed in your changelog that matches the default regular expression. .EXAMPLE 'Value1', 'Value2' | <FUNCTION> .INPUTS [String] .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-PowerShellGalleryVersion .LINK Get-NextReleaseVersion #> [OutputType([Version])] [CmdletBinding()] Param ( # Path to the changelog. [Parameter(Mandatory)] [ValidateScript ( { Test-Path $_ } )] [string] $Path, # Regular expression to match the version. This assumes that the format # of your changelog (in Markdown) versions are: # # 'hash'hash' v1.0.0 Some text # 'hash'hash' v0.9.0 Some more text # # This will then return the version as 1.0.0. Note that becasue the help # files are in Markdown I cannot type a double hash (which is a pound in # the US). So 'hash' is '#' [string] $VersionRegex = '##\s+v(\d+\.\d+\.\d+)' ) # get the version from the changelog.md if we have one switch -Regex -File ($Path) { $VersionRegex { return [version]$Matches[1] } } # end switch # if we get here we did not find the version [version]'0.0.0' } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-FunctionParameter { <# .SYNOPSIS Gets the parameters and their properties for a specified function. .DESCRIPTION Gets the parameters and their properties for a specified function. The function must exist within the 'Function:' provider so will not work on cmdlets. .EXAMPLE Get-FunctionParameters -Name 'Get-FunctionParameters' Returns the properties of each function parameter for the 'Import-Module' function excluding the common parameters for Advanced Functions. .EXAMPLE Get-FunctionParameters -Name 'Get-FunctionParameters' -Exclude '' Returns the properties of each function parameter for the 'Import-Module' function excluding no parameter names. .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 17/03/18 - Initial release #> [CmdletBinding()] Param ( # Name of the function. The function must exist within the 'Function:' # provider or an exception will be thrown. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name, # Array of parameter names to exclude. By default the Advanced Functions # common parameters are excluded. Pass an empty array to have all # parameters returned. [AllowEmptyCollection()] [string[]] $Exclude = @('Verbose', 'Debug', 'ErrorAction', 'WarningAction', ` 'InformationAction', 'ErrorVariable', 'WarningVariable', 'InformationVariable', ` 'OutVariable', 'OutBuffer', 'PipelineVariable' ) ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } try { (Get-Item -Path "Function:\$Name").Parameters.GetEnumerator() | Where-Object { $Exclude -notcontains $_.key} } catch { throw "Cannot find function '$Name' loaded in the current session." } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-GitBranchName { <# .SYNOPSIS Get the name of the current branch. .DESCRIPTION Get the name of the current branch. .EXAMPLE Get-GitBranchName Returns the current branch name for the current repo. .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK #> [CmdletBinding()] [OutputType([String])] Param () $branch = git rev-parse --abbrev-ref HEAD 2>&1 if (-not $?) { throw 'Cannot determine the current git branch.' } $branch } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-GitChange { <# .SYNOPSIS Gets the git unstaged changed files in the local .DESCRIPTION Gets a list of hte changed files in the local repository. Only the file names are returned not their change status (deleted, modified, unstaged etc.) .EXAMPLE Get-GitChange Gets the lilst of git changed files in the current repository. .OUTPUTS [String[]] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingInvokeExpression", "", Justification = "This will eventually be rewritten")] [CmdletBinding()] [OutputType([String[]])] Param () # todo this really needs rewritten to use something other than the local git command @(Invoke-Expression -Command 'git status -s') | ForEach-Object { if ($_ -match '\S*$') { $matches[0] } } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-GitLastCommitHash { <# .SYNOPSIS Gets the hash of the last commit. .DESCRIPTION Gets the hash of the last commit. .EXAMPLE Get-GitLastCommitHash Gets the hash of hte last commit for the current branch and repo. .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK #> [CmdletBinding()] [OutputType([String])] param () $hash = git log -1 --pretty=%H 2>&1 if (-not $?) { throw 'There are no commits.' } else { ($hash | Where-Object { $_ } | Out-String).Trim() } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-GitLastCommitMessage { <# .SYNOPSIS Getsh the last Git commit message. .DESCRIPTION Gets the last Git commit message. If there are no messages to retrieve then it will throw and exception. Uses the 'git' command to retrieve the messages so this must be installed. .EXAMPLE Get-GitLastCommitMessage Returns the last commit message for the current branch and repo. .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK #> [CmdletBinding()] [OutputType([String])] Param () $message = git log -1 --pretty=%B 2>&1 if (-not $?) { throw 'There are no commit messages.' } else { ($message | Where-Object { $_ } | Out-String).Trim() } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-NextReleaseVersion { <# .SYNOPSIS Get's the next release version. .DESCRIPTION Get's the next release version. .EXAMPLE Get-NextReleaseVersion -LatestVersion [Version]'1.0.0' -ReleaseType 'Minor' Will return a new version number of [Version]'1.1.0' which is a minor version increase from '1.0.0' .OUTPUTS [Version] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-PowerShellGalleryVersion .LINK Get-ChangelogVersion #> [OutputType([Version])] [CmdletBinding()] Param ( # The latest releaase version. [Parameter(Mandatory)] [Version] $LatestVersion, # The type of this release. [Parameter(Mandatory)] [String] $ReleaseType ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } Write-Verbose "Release type is $($ReleaseType)." $version = switch ($ReleaseType) { Major { Write-Verbose "Incrementing major version number." New-Object Version(($LatestVersion.Major + 1), 0, 0) } Minor { Write-Verbose "Incrementing minor version number." New-Object Version($LatestVersion.Major, ($LatestVersion.Minor + 1), 0) } Build { Write-Verbose "Incrementing build version number." New-Object Version($LatestVersion.Major, $LatestVersion.Minor, ($LatestVersion.Build + 1)) } default { # this also catches the ReleaseType of None Write-Verbose 'Not incrementing any version numbers.' New-Object Version($LatestVersion) } } Write-Verbose "New version will be $version" $version } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-PowerShellGalleryVersion { <# .SYNOPSIS Gets the version of the module in the POwerShell Gallery. .DESCRIPTION Gets the version of the module in the POwerShell Gallery. .EXAMPLE Get-PowerShellGalleryVersion -Name 'MyModule' Gets the latest version of the module 'mymodule' listed in the PowerShell Gallery. .OUTPUTS [Version] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-NextReleaseVersion .LINK Get-ChangelogVersion #> [OutputType([Version])] [CmdletBinding()] Param ( # Name of the module. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } # get the version from the PowerShell Gallery try { Write-Verbose "Getting the latest version of '$Name' from the PowerShell Gallery." [version](Find-Module -Name $Name -ErrorAction Stop).Version } catch { Write-Verbose "Did not find module '$Name' in the PowerShell Gallery." [version]'0.0.0' } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-ProjectEnvironment { <# .SYNOPSIS Get the properties of the project environment. .DESCRIPTION Get the properties of the project in the current location. .EXAMPLE Get-ProjectEnvironment Gets the project environment for the current location. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release The idea came from the Indented.Build project (https://github.com/indented-automation/Indented.Build) and heavily modified. Credit should be given to that project. .LINK Get-BuildEnvironment .LINK Get-TestEnvironment #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseConsistentWhitespace", "", Justification = "Causes issue with the large hash table")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAlignAssignmentStatement", "", Justification = "Causes issue with the large hash table")] [OutputType([PSObject])] [CmdletBinding()] Param () if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } $projectRoot = Get-ProjectRoot $sourcePath = Get-SourcePath -ProjectRoot $projectRoot # test path try { $testPath = (Get-ChildItem (Join-Path -Path $projectRoot -ChildPath 'test*') -Directory | ` Select-Object -First 1).ToString() } catch { Write-Warning 'We have no tests folder!' } [PSCustomObject]@{ ModuleName = Split-Path -Path $projectRoot -Leaf BuildSystem = Get-BuildSystem ProjectRootPath = $projectRoot SourcePath = $sourcePath BuildRootPath = Join-Path -Path $projectRoot -ChildPath 'releases' OutputPath = Join-Path -Path $projectRoot -ChildPath 'output' TestPath = $testPath } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-ProjectRoot { <# .SYNOPSIS Gets the project root folder name. .DESCRIPTION Gets the project root folder name by looking for the git repo root folder. It uses the 'git' executable to do this so it must be installed. .EXAMPLE Get-ProjectRoot Returns the root folder for this project / git repository. .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release This function was lifted from the Indented.Build project (https://github.com/indented-automation/Indented.Build) and all credit for it should go to that project. .LINK Get-SourcePath #> [CmdletBinding()] [OutputType([String])] Param () $path = git rev-parse --show-toplevel if ($null -eq $path) { throw 'Not a git repository - cannot provide project root' } else { (Get-Item $path).FullName } } function Get-ReleaseType { <# .SYNOPSIS Gets the release type from the last commit message. .DESCRIPTION Gets the release type from the last commit message. The message should start with: - 'Major release' : 'Major' release type - 'Minor release' : 'Minor' release type - 'Release' : 'Build' release type - None of the above : 'None' release type .EXAMPLE Get-ReleaseType -CommitMessage '' Gets' the release type ofr an empty commit message - this will be 'None' .OUTPUTS [String] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-GitLastCommitMessage .LINK Get-GitLastCommitHash .LINK Get-GitBranchName #> [OutputType([String])] [CmdletBinding()] Param ( # The commit message to be used to determine the release type. # The message should start with: # # - 'Major release' : 'Major' release type # - 'Minor release' : 'Minor' release type # - 'Release' : 'Build' release type # - None of the above : 'None' release type [Parameter(Mandatory)] [AllowEmptyString()] [string]$CommitMessage ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } Write-Verbose "Commit message: $CommitMessage" switch -Regex ($CommitMessage) { '^Major release' { Write-Verbose "Commit message contains 'Major release'. ReleaseType is 'Major'." 'Major' } '^Minor release' { Write-Verbose "Commit message starts with 'Minor release'. Release type is 'Minor'." 'Minor' } '^Release' { Write-Verbose "Commit message contains 'Release'. Release type is 'Build'" 'Build' } default { Write-Verbose "Commit message does not contain any release types. Release type is 'None'" 'None' } } } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-SourcePath { <# .SYNOPSIS Gets the source path of the module. .DESCRIPTION Gets the source path of the module under the project root. The source path can be called any one of the following: - source - src - <NAME>\<NAME>.psd1 Note that the last one is simply a parent folder whose name is the same name as the manifest within it. .EXAMPLE Get-SourcePath -Path 'c:\mymodule' Gets the path to the module source under 'c:\mymodule' .OUTPUTS [System.IO.DirectoryInfo], [System.IO.DirectoryInfo[]] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release Note that this function was lifted from teh Indented.Build module (https://github.com/indented-automation/Indented.Build) and modified to allow 'source' and 'src' folders to be used and replaced Push- and Pop-Location with Set-Location as Invoke-Build recommends not using those cmdlets. All credit should be given to that project. .LINK Get-BuildSystem .LINK Get-BuildEnvironment .LINK Get-ProjectRoot #> [CmdletBinding()] [OutputType([String])] Param ( # Root path for the project. [Parameter(Mandatory, ValueFromPipeline)] [String]$ProjectRoot ) if (Test-Path (Join-Path -Path $ProjectRoot -ChildPath (Split-Path -Path $ProjectRoot -Leaf))) { return (Join-Path -Path $ProjectRoot -ChildPath (Split-Path -Path $ProjectRoot -Leaf)) } else { $folders = 'src', 'source' ForEach ($folder in $folders) { $sourcePath = Join-Path -Path $ProjectRoot -ChildPath $folder if (Test-Path $sourcePath) { return $sourcePath } } #end foreach-object } # end else # we get here we have found nothing throw 'Unable to determine the source path' } #.ExternalHelp PSModuleBuildHelper-help.xml function Get-TestEnvironment { <# .SYNOPSIS Get the properties of the testing environment. .DESCRIPTION Get the properties required to test the project, or elements of the project. Some of the paths require that the module already be built beforehand and will throw an exception if that is not the case. All areas of the test process takes it's details from the data produced by this function. To influence part of the build process the data produced only need be altered. Note that BuildPath, BuildManifestPath and BuildModulePath use the latest build directory based on it's version number. This funciton assumes that the module has already been built .EXAMPLE Get-TestEnvironment Get test information for the current or any child directories. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release The idea came from the Indented.Build project (https://github.com/indented-automation/Indented.Build) and heavily modified. Credit should be given to that project. .LINK Get-ProjectEnvironment .LINK Get-BuildEnvironment #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseConsistentWhitespace", "", Justification = "Causes issue with the large hash table")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAlignAssignmentStatement", "", Justification = "Causes issue with the large hash table")] [OutputType([PSObject])] [CmdletBinding()] Param ( # Filename of the PowerShell ScriptAnalyzer settings file. This file # will be searched for under the project root. [string] $PSSASettingsName = 'PSScriptAnalyzerSettings.psd1', # The PowerShell ScriptAnalyzer Custom Rules folder. This folder will be # searched for under the project root and any .psd1 files found under # teh folder will be used. [string] $PSSACustomRulesFolderName = 'CustomAnalyzerRules' ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } $testInfo = Get-ProjectEnvironment $buildOutput = Join-Path -Path $testInfo.ProjectRootPath -ChildPath 'releases' # get all of the versions built in the 'releases' folder, and sort them # by name and choose the latest one - to sort them by name properly we have # to convert the names to versions (as 0.10.0 comes before 0.9.0 when using # strings) $latestBuildVersion = (Get-Childitem $buildOutput | ` Select-Object -Property @{ l = 'Name'; e = { [version]$_.Name } } | Sort-Object Name -Descending | ` Select-Object -First 1).Name.ToString() if ($latestBuildVersion -eq '') { throw 'Cannot find the latest build of the module. Did you build it beforehand?' } @( @{ name = 'SourceManifestPath' value = (Join-Path -Path $testInfo.SourcePath -ChildPath "$($testInfo.ModuleName).psd1") }, @{ name = 'SourceModulePath' value = (Join-Path -Path $testInfo.SourcePath -ChildPath "$($testInfo.ModuleName).psm1") }, @{ name = 'BuildPath' value = (Join-Path -Path $buildOutput -ChildPath $latestBuildVersion) }, @{ name = 'BuildManifestPath' value = (Join-Path -Path (Join-Path $buildOutput -ChildPath $latestBuildVersion) -ChildPath "$($testInfo.ModuleName).psd1") }, @{ name = 'BuildModulePath' value = (Join-Path -Path (Join-Path $buildOutput -ChildPath $latestBuildVersion) -ChildPath "$($testInfo.ModuleName).psm1") } @{ name = 'PSSASettingsPath' value = '' }, @{ name = 'PSSACustomRulesPath' value = '' } ) | ForEach-Object { $testInfo | Add-Member -MemberType NoteProperty -Name $_.Name -Value $_.Value } # PSSA $settingsPath = Get-ChildItem $PSSASettingsName -Recurse | Select-Object -First 1 if ($settingsPath) { $testInfo.PSSASettingsPath = $settingsPath.FullName } else { Write-Verbose "Could not find PSScriptAnalyzer Settings file '$PSSASettingsName'." } $path = Get-ChildItem -Path $PSSACustomRulesFolderName -Directory -Recurse | Select-Object -First 1 if ($path -and (Test-Path -Path (Join-Path -Path $path.FullName -ChildPath '*.psd1'))) { $testInfo.PSSACustomRulesPath = $path.FullName } else { Write-Verbose "No PSScriptAnalyzer Custom Rules folder '$PSSACustomRulesFolderName' found." } $testInfo } #.ExternalHelp PSModuleBuildHelper-help.xml function Hide-SensitiveData { <# .SYNOPSIS Searches the object property names for keywords and masks their value if it matches. .DESCRIPTION Searches the object property names for keywords and masks their value if it matches. There is a default set of keywords that are searched for which can be replaced. .EXAMPLE $test = New-Object -TypeName PSObject -Property @{ api = 'abcd', name = 'Luke' } $test | Hide-SensitiveData Returns the object with the value of 'api' as '*****' .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release .LINK Get-BuildSystem .LINK Get-BuildEnvironment #> [CmdletBinding()] [OutputType([PSObject])] Param ( # Object to search the keys for matching keywords. Cannot be $null or empty. [Parameter(Mandatory, ValueFromPipeline)] [ValidateScript( { $null -ne $_ -and @($_.psobject.properties).count -ne 0 } )] [PSObject] $InputObject, # Array of regular expressions to match the keys against. [String[]] $Keyword = @('password', 'secret', 'key', 'api', 'token'), # The mask that will be used to replace any matching keyword values. [String] $Mask = '[protected]' ) begin { if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } } process { # objects are passed by reference so any changes to it are made to the original # clone the input object so we don't change it $copyObj = New-Object -TypeName PsObject $InputObject.PSObject.Properties | ForEach-Object { $copyObj | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value } # filter the environment secret vars # if the environment variable is empty then don't mask it ForEach ($obj in $copyObj.PSObject.Properties) { Write-Debug "Checking '$($obj.Name)' for a keyword match." ForEach ($k in $Keyword) { # does the name match the secret and it's not empty if ($obj.Name -match $k) { $obj.Value = $Mask Write-Verbose "Key matched keyword '$k'. Value changed to '$($obj.Value)'." break } } } } end { $copyObj } } #.ExternalHelp PSModuleBuildHelper-help.xml function Initialize-BuildDependency { <# .SYNOPSIS Initializes the build dependencies. .DESCRIPTION Installs the modules, chocolatey packages and other dependencies for the module build. The format of the dependency hashtable depends on it's type but all of them share these keys: - Name - [required] Name of the dependency. This is the module name, chocolatey package name etc. - Version - [optional] Version to be installed - latest by default. - Type - [optional] Type of dependency - 'Module', 'Chocolatey' By default this will be a 'Module' For a 'Module' type there are no additional options. For a 'Chocolatey' type there are these additional options: - PackageParams - [optional] Additional parameters that are passed to choco.exe using the --params parameter. Whatever is put in here will be surrounded by single quotes on the choco command line. .EXAMPLE Initialize-BuildDependency -Dependency $deps Initializes the dependencies from data in $deps .EXAMPLE $deps | Initialize-BuildDependency -Dependency $deps Initializes the dependencies from data in $deps .INPUTS [Hashtable] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 03/04/18 - Initial release .LINK Install-DependentModule .LINK Install-ChocolateyPackage #> [CmdletBinding(SupportsShouldProcess)] Param ( # Dependency data to initialize. [Parameter(Mandatory, ValueFromPipeline)] [ValidateNotNullOrEmpty()] [Hashtable] $Dependency ) Begin { # !if you change this you need to change the default in the switch statement below $DefaultType = 'Module' } Process { ForEach ($dep in $Dependency) { # check we have a type and if not default to $DefaultType $type = $DefaultType if ($dep.Keys -contains 'Type') { $type = $dep.Type } # we need to remove the 'Type' key before splatting $params = $dep.PsObject.Copy() $params.Remove('Type') switch ($type) { 'Module' { Install-DependentModule @params | Out-Null break } 'Chocolatey' { Install-ChocolateyPackage @params | Out-Null break } default { Install-DependentModule @params | Out-Null } } } } } #.ExternalHelp PSModuleBuildHelper-help.xml function Initialize-TestEnvironment { <# .SYNOPSIS Brief synopsis about the function. .DESCRIPTION Detailed explanation of the purpose of this function. .PARAMETER Param1 The purpose of param1. .PARAMETER Param2 The purpose of param2. .EXAMPLE <FUNCTION> -Param1 'Value1', 'Value2' .EXAMPLE 'Value1', 'Value2' | <FUNCTION> .INPUTS [String] .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 16/03/18 - Initial release .LINK Related things #> [CmdletBinding()] Param() if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } $testInfo = Get-TestEnvironment if ($testInfo.BuildSystem -eq 'Unknown') { # re-import the module each time if you are running it locally - using # the SuppressImportModule will only import it once each session Write-Verbose "Removing module '$($testInfo.Modulename)'." Remove-Module $testInfo.ModuleName -Force -ErrorAction SilentlyContinue Write-Verbose "Importing module '$($testInfo.BuildManifestPath)'" Import-Module -FullyQualifiedName $testInfo.BuildManifestPath -Force -Scope Global } elseif (-not (Get-Module -Name $testInfo.ModuleName -ErrorAction SilentlyContinue) -or !(Test-Path Variable:SuppressImportModule) -or !$SuppressImportModule) { # The first time this is called, the module will be forcibly (re-)imported. # After importing it once, the $SuppressImportModule flag should prevent # the module from being imported again for each test file. # -Scope Global is needed when running tests from within a CI environment Import-Module -FullyQualifiedName $testInfo.BuildManifestPath -Scope Global -Force # Set to true so we don't need to import it again for the next test $Script:SuppressImportModule = $true } $testInfo } #.ExternalHelp PSModuleBuildHelper-help.xml function Install-ChocolateyPackage { <# .SYNOPSIS Installs a Chocolatey package. .DESCRIPTION Installs a Chocolatey package and installs Chocolatey if required. .EXAMPLE Install-ChocolateyPackage -Name '7zip' Installs the latest version of 7zip. .EXAMPLE Install-ChocolateyPackage -Name '7zip' -Version '15.0' Installs version 15.0 of 7zip. .EXAMPLE Install-ChocolateyPackage -Name 'dummy' -PackageParams '--noprogress' Installs the latest version of dummy with the package parameters --noprogress. .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 03/04/18 - Initial release We use Invoke-Command in this function rather than the & call operator as we can mock it. .LINK Install-DependentModule .LINK Initialize-BuildDependency #> [CmdletBinding(SupportsShouldProcess)] Param ( # Name of the Chocolatye package to install [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string] $Name, # Version of the package to install. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript( { if ($_ -ne 'latest') { try { [version]$_ } catch { return $false } } $true })] [string] $Version = 'latest', # Chocolatey package parameters to use when installing the package. [ValidateNotNullOrEmpty()] [string] $PackageParams ) Begin { if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } $chocoInstalled = $true try { Invoke-Command -ScriptBlock { choco.exe } | Out-Null } catch { $chocoInstalled = $false } if (-not $chocoInstalled) { try { Write-Verbose 'Chocolatey not installed. Installing.' if ($pscmdlet.ShouldProcess("Chocolatey", "Install")) { # taken from https://chocolatey.org/install Set-ExecutionPolicy Bypass -Scope Process -Force # create a temp file to hold the Chocolatey install script and then execute it do { $tempFile = "$(Join-Path -Path $env:TEMP -ChildPath([System.Guid]::NewGuid().ToString())).ps1" } while (Test-Path $tempFile) Invoke-WebRequest -UseBasicParsing -Uri 'https://chocolatey.org/install.ps1' -OutFile $tempFile Invoke-Command -ScriptBlock { & $tempFile } } } catch { throw 'Could not install Chocolatey' } } else { Write-Verbose "Chocolatey already installed." } } Process { # if we get here chocolatey is installed - install the package $chocoParams = @('install', "$Name", '-y', '--no-progress') if ($Version -ne 'latest') { $chocoParams += "--version=$Version" } if ($PackageParams) { $chocoParams += "--params='$PackageParams'" } if ($pscmdlet.ShouldProcess("'$Name' with parameters '$($chocoParams -join "" "")'", "Installing Chocolatey package")) { # reset the last exit $LASTEXITCODE = 0 Write-Verbose "Installing version '$Version' of '$Name' package with parameters '$($chocoParams -join "" "")'." Invoke-Command -ScriptBlock { choco.exe $chocoParams } if ($LASTEXITCODE -ne 0) { throw "Chocolatey package '$Name' failed to install with command line '$($chocoParams -join "" "")'" } } } End { Write-Verbose 'Refreshing the PATH' refreshenv } } #.ExternalHelp PSModuleBuildHelper-help.xml function Install-DependentModule { <# .SYNOPSIS Installs a module. .DESCRIPTION Installs a module and. if necessary, installs the Nuget Package Provider and trusts the PowerShell Gallery repository. These last two steps are necessary if installing on a bare PowerShell install (such as in CI). .EXAMPLE Install-DependentModule -Name 'Dummy' Installs thelatest version of the module dummy. .OUTPUTS [PSObject] .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 03/04/18 - Initial release .LINK Initialize-BuildDependency .LINK Install-ChocolateyPackage #> [CmdletBinding(SupportsShouldProcess)] Param ( # The module name ot install [Parameter(Mandatory, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string] $Name, # The version to install. To install the latest version do not pass this # parameter or pass the string 'latest'. [Parameter(ValueFromPipelineByPropertyName)] [ValidateScript( { if ($_ -ne 'latest') { try { [version]$_ } catch { return $false } } $true })] [string] $Version = 'latest' ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } # check for install-module cmdlet - this tells us if we have the nuget package provider installed try { Get-Command -Name 'Install-Module' -ErrorAction Stop | Out-Null } catch { Write-Verbose 'Installing the Nuget Package Provider' if ($pscmdlet.ShouldProcess("Nuget Package Provider", "Installing")) { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null } } # check the PSGallery repository is trusted if ((Get-PSRepository -Name PSGallery).InstallationPolicy -ne 'Trusted') { if ($pscmdlet.ShouldProcess("PowerShell Gallery", "Trusting")) { Write-verbose "Trusting PowerShell Gallery" # !there is a problem with mocking this cmdlet within Pester so it is not tested Set-PSRepository -Name PSGallery -InstallationPolicy Trusted } } # install the module if ($pscmdlet.ShouldProcess("$Version version of module $Name", "Installing module")) { if ($Version -ne 'latest') { Write-Verbose "Installing version '$Version' of module '$Name'" Install-Module -Name $Name -RequiredVersion $Version -Scope CurrentUser -Force # return the version of the module we installed Get-Module -Name $Name -ListAvailable | Where-Object { [Version]$_.Version -eq [Version]$Version } } else { Write-Verbose "Installing latest version of module '$Name'" Install-Module -Name $Name -Scope CurrentUser -Force # return only the latest version of this module Get-Module -Name $Name -ListAvailable | Sort-Object Version -Descending | Select-Object -First 1 } } } #.ExternalHelp PSModuleBuildHelper-help.xml function New-GithubRelease { <# .SYNOPSIS Creates a release on GitHub. .DESCRIPTION Creates a release on GitHub using an already created artifact. Note that this does not use the credential store for authentication but uses the GitHub Username and Api Key passed to the function. .EXAMPLE New-GitHubRelease -Version 1.0 -CommitId 'a6fe432' -ArtifactPath 'c:\temp\mymodule-1.0.zip' -GitHubUsername 'me' -GitHubRepository 'mymodule' -GitHubApiKey '123456789' This will create a new version 1.0 relase on GitHub for the Commit ID 'a6fe432' using the artifact 'c:\temp\mymodule-1.0.zip' in the GitHub repository 'mymodule' using the Api Key and Username for authentication. .NOTES Author : Paul Broadwith (https://github.com/pauby) History : 1.0 - 15/03/18 - Initial release #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseConsistentWhitespace", "", Justification = "Causes issue with the open hash tables")] [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')] Param ( # The version number to be used for this release. [Parameter(Mandatory)] [Version]$Version, # The Commit ID corresponding to this release. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$CommitID, # Release notes for this release. [string]$ReleaseNotes = '', # The path to the release artifact. [Parameter(Mandatory)] [ValidateScript( { Test-Path $_ } )] [string]$ArtifactPath, # The GitHub Username to use for authentication. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $GitHubUsername, # The Github API key used for authentication. # See (https://github.com/blog/1509-personal-api-tokens) [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $GitHubApiKey, # Which GitHub repository this release is for. [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $GitHubRepository, # Marks this release as a draft. [Switch] $DraftRelease, # Marks this release as a pre-release. [switch] $PreRelease ) if (-not $PSBoundParameters.ContainsKey('Verbose')) { $VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference') } # Get just the name of the file to attach to this release $artifact = Split-Path $ArtifactPath -Leaf $releaseData = @{ tag_name = "v{0}" -f $Version target_commitish = $CommitId name = "v{0}" -f $Version body = $ReleaseNotes draft = $DraftRelease.IsPresent prerelease = $PreRelease.IsPresent } $releaseParams = @{ Uri = "https://api.github.com/repos/$GitHubUsername/$GitHubRepository/releases" Method = 'POST' Headers = @{ Authorization = 'Basic ' + [Convert]::ToBase64String( [Text.Encoding]::ASCII.GetBytes($GitHubApiKey + ":x-oauth-basic")) } ContentType = 'application/json' Body = (ConvertTo-Json $releaseData -Compress) UseBasicParsing = $true } # force use of TLS 1.2 Write-Verbose 'Forcing using of TLS1.2 for GitHub.' [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Write-Verbose 'Creating tagged Github release.' try { $result = Invoke-RestMethod @releaseParams -OutVariable $errorMsg } catch { throw "Could not create tagged GitHub release - '$_'" } $uploadUri = $result | Select-Object -ExpandProperty upload_url $uploadUri = $uploadUri -replace '\{\?name.*\}', "?name=$artifact" $uploadParams = @{ Uri = $uploadUri Method = 'POST' Headers = @{ Authorization = 'Basic ' + [Convert]::ToBase64String( [Text.Encoding]::ASCII.GetBytes($GitHubApiKey + ":x-oauth-basic")); } ContentType = 'application/zip' InFile = $ArtifactPath } if ($PSCmdlet.ShouldProcess("ShouldProcess?")) { Write-Verbose 'Uploading artifact.' $response = Invoke-RestMethod @uploadParams Write-Verbose "Response from artifact upload: $response" } } |