Skip to content

Instantly share code, notes, and snippets.

@scrthq
Last active January 11, 2021 05:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scrthq/a99cc06e75eb31769d01b2adddc6d200 to your computer and use it in GitHub Desktop.
Save scrthq/a99cc06e75eb31769d01b2adddc6d200 to your computer and use it in GitHub Desktop.
Azure Pipelines Helper Functions
Param(
[Parameter(Position = 0)]
[String]
$ProjectName = $(Split-Path $PWD.Path -Leaf)
)
$env:_BuildStart = Get-Date -Format 'o'
New-Variable -Name IsCI -Value $($IsCI -or (Test-Path Env:\TF_BUILD)) -Scope Global -Force -Option AllScope
function Install-NuGetDependencies {
[CmdletBinding()]
Param (
[parameter()]
[String[]]
$Destination,
[parameter()]
[Switch]
$PlaceInDestinationRoot,
[parameter()]
[String[]]
$TargetFrameworks = @('net45','netstandard1.3'),
[parameter()]
[String[]]
$AddlSearchString,
[parameter()]
[String]
$CIFolder = $PSScriptRoot
)
try {
Import-Module PackageManagement -Force
$updateDepsPath = [System.IO.Path]::Combine($CIFolder,"UpdateNuGetDependenciesJson.ps1")
if (Test-Path $updateDepsPath) { . $updateDepsPath }
$dllStgPath = [System.IO.Path]::Combine($CIFolder,"NuGetStaging")
$packagesToInstall = Get-Content (Join-Path $CIFolder "NuGetDependencies.json") -Raw | ConvertFrom-Json | Sort-Object BaseName
if (-not (Test-Path $dllStgPath)) {
New-Item $dllStgPath -Force -ItemType Directory | Out-Null
}
$nugHash = @{}
Register-PackageSource -Name NuGet -Location https://www.nuget.org/api/v2 -ProviderName NuGet -Force -Trusted -ForceBootstrap
foreach ($search in $AddlSearchString) {
Write-BuildLog "Finding NuGet packages matching SearchString: $search"
PackageManagement\Find-Package $search -Source NuGet -AllowPrereleaseVersions:$false | Where-Object {$_.Name -in $packagesToInstall.BaseName} | ForEach-Object {
Write-BuildLog "Matched package: $($_.Name)"
$nugHash[$_.Name] = $_
}
}
$webClient = New-Object System.Net.WebClient
foreach ($inst in $packagesToInstall) {
try {
$pkg = if ($nugHash.ContainsKey($inst.BaseName)) {
$nugHash[$inst.BaseName]
}
else {
[PSCustomObject]@{
Name = $inst.BaseName
Version = $inst.LatestVersion
TagId = $inst.BaseName + '#' + $inst.LatestVersion
}
}
Write-BuildLog ("[{0}.{1}] Downloading latest package from NuGet" -f $pkg.Name,$pkg.Version)
$extPath = [System.IO.Path]::Combine($dllStgPath,"$($pkg.Name.ToLower().TrimEnd('.dll')).$($pkg.Version)")
if (Test-Path ($extPath)) {
Remove-Item $extPath -Recurse -Force
}
New-Item -Path $extPath -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
$zipPath = $extPath.TrimEnd('.dll') + '.zip'
if (Test-Path ($zipPath)) {
Remove-Item $zipPath -Force
}
$pkgUrl = 'https://www.nuget.org/api/v2/package/'+ ($pkg.TagId.Replace('#','/'))
$i = 0
do {
$webClient.DownloadFile($pkgUrl,$zipPath)
$i++
}
until ((Test-Path $zipPath) -or $i -ge 5)
if ($i -ge 5) {
throw "Failed to download NuGet package from URL: $pkgUrl"
}
Unblock-File $zipPath
Add-Type -AssemblyName System.IO.Compression
[System.IO.Compression.ZipFile]::ExtractToDirectory($zipPath,$extPath)
foreach ($dest in $Destination) {
foreach ($target in $TargetFrameworks) {
$targetPath = if ($PlaceInDestinationRoot) {
if ($TargetFrameworks.Count -eq 1) {
$dest
}
else {
[System.IO.Path]::Combine($dest,$target)
}
}
else {
[System.IO.Path]::Combine($dest,'lib',$target)
}
if (-not (Test-Path $targetPath)) {
New-Item -Path $targetPath -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
}
$sourcePath = if ($pkg.Name -eq 'BouncyCastle.Crypto.dll') {
[System.IO.Path]::Combine($extPath,'lib')
}
else {
[System.IO.Path]::Combine($extPath,'lib',$target)
}
if (Test-Path $sourcePath) {
Get-ChildItem $sourcePath -Filter '*.dll' | Copy-Item -Destination $targetPath -Force
}
else {
Write-BuildError "$($pkg.Name) was not downloaded successfully from NuGet!" -ErrorAction Stop
exit 1
}
}
}
}
catch {
Write-Warning "Error when trying [$($inst.BaseName)]: $($_.Exception.Message)"
}
finally {
if (Test-Path ($zipPath)) {
Remove-Item $zipPath -Force
}
if (Test-Path ($extPath)) {
Remove-Item $extPath -Recurse -Force
}
}
}
$webClient.Dispose()
}
finally {
if (Test-Path $dllStgPath) {
Remove-Item $dllStgPath -Recurse -Force
}
}
}
function Add-Heading {
param(
[parameter(Position = 0,ValueFromRemainingArguments)]
[String]
$Title,
[Switch]
$Passthru
)
$date = "[$((Get-Date).ToString("HH:mm:ss")) +$(((Get-Date) - (Get-Date $env:_BuildStart)).ToString())]"
$msgList = @(
''
"##[section] $date $Title"
)
if ($Passthru) {
$msgList
}
else {
$msgList | Write-Host -ForegroundColor Cyan
}
}
function Add-EnvironmentSummary {
param(
[parameter(Position = 0,ValueFromRemainingArguments)]
[String]
$State
)
Add-Heading Build Environment Summary
@(
"Project : $ProjectName"
"State : $State"
"Engine : PowerShell $($PSVersionTable.PSVersion.ToString())"
"Host OS : $(if($PSVersionTable.PSVersion.Major -le 5 -or $IsWindows){"Windows"}elseif($IsLinux){"Linux"}elseif($IsMacOS){"macOS"}else{"[UNKNOWN]"})"
"PWD : $PWD"
''
) | Write-Host
}
function Write-BuildWarning {
param(
[parameter(Mandatory,Position = 0,ValueFromRemainingArguments,ValueFromPipeline)]
[System.String]
$Message
)
Process {
Write-Warning $Message
if ($IsCI) {
Write-Host "##vso[task.logissue type=warning;]$Message"
}
else {
}
}
}
function Write-BuildError {
param(
[parameter(Mandatory,Position = 0,ValueFromRemainingArguments,ValueFromPipeline)]
[System.String]
$Message
)
Process {
if ($IsCI) {
Write-Host "##vso[task.logissue type=error;]$Message"
exit 1
}
else {
Write-Error $Message
exit 1
}
}
}
function Write-BuildLog {
[CmdletBinding()]
param(
[parameter(Mandatory,Position = 0,ValueFromRemainingArguments,ValueFromPipeline)]
[System.Object]
$Message,
[parameter()]
[Alias('c','Command')]
[Switch]
$Cmd,
[parameter()]
[Alias('w')]
[Switch]
$Warning,
[parameter()]
[Alias('s','e')]
[Switch]
$Severe,
[parameter()]
[Alias('x','nd','n')]
[Switch]
$Clean
)
Begin {
if ($PSBoundParameters.ContainsKey('Debug') -and $PSBoundParameters['Debug'] -eq $true) {
$fg = 'Yellow'
$lvl = '##[debug] '
}
elseif ($PSBoundParameters.ContainsKey('Verbose') -and $PSBoundParameters['Verbose'] -eq $true) {
$fg = if ($Host.UI.RawUI.ForegroundColor -eq 'Gray') {
'White'
}
else {
'Gray'
}
$lvl = '##[verbose] '
}
elseif ($Severe) {
$fg = 'Red'
$lvl = '##[error] '
}
elseif ($Warning) {
$fg = 'Yellow'
$lvl = '##[warning] '
}
elseif ($Cmd) {
$fg = 'Magenta'
$lvl = '##[command] '
}
else {
$fg = if ($Host.UI.RawUI.ForegroundColor -eq 'Gray') {
'White'
}
else {
'Gray'
}
$lvl = '##[info] '
}
}
Process {
$fmtMsg = if ($Clean){
$Message -split "[\r\n]" | Where-Object {$_} | ForEach-Object {
$lvl + $_
}
}
else {
$date = "[$((Get-Date).ToString("HH:mm:ss")) +$(((Get-Date) - (Get-Date $env:_BuildStart)).ToString())] "
if ($Cmd) {
$i = 0
$Message -split "[\r\n]" | Where-Object {$_} | ForEach-Object {
if ($i -eq 0) {
$startIndex = if ($_ -match "^\s+") {
$new = $_ -replace "^\s+"
$_.Length - $new.Length
}
else {
0
}
$tag = 'PS > '
}
else {
$tag = ' >> '
}
$finalIndex = if ($startIndex -lt $_.Length) {
$startIndex
}
else{
0
}
$lvl + $date + $tag + ([String]$_).Substring($finalIndex)
$i++
}
}
else {
$Message -split "[\r\n]" | Where-Object {$_} | ForEach-Object {
$lvl + $date + $_
}
}
}
Write-Host -ForegroundColor $fg $($fmtMsg -join "`n")
}
}
function Invoke-CommandWithLog {
[CmdletBinding()]
Param (
[parameter(Mandatory,Position=0)]
[ScriptBlock]
$ScriptBlock
)
Write-BuildLog -Command ($ScriptBlock.ToString() -join "`n")
$ScriptBlock.Invoke()
}
function Set-EnvironmentVariable {
param(
[parameter(Position = 0)]
[String]
$Name,
[parameter(Position = 1,ValueFromRemainingArguments)]
[String[]]
$Value
)
$fullVal = $Value -join " "
Write-BuildLog "Setting env variable '$Name' to '$fullVal'"
Set-Item -Path Env:\$Name -Value $fullVal -Force
if ($IsCI) {
"##vso[task.setvariable variable=$Name]$fullVal" | Write-Host
}
}
function Set-BuildVariables {
$gitVars = if ($IsCI) {
@{
BHBranchName = $env:BUILD_SOURCEBRANCHNAME
BHPSModuleManifest = (Resolve-Path "$ProjectName\$ProjectName.psd1").Path
BHPSModulePath = (Resolve-Path $ProjectName).Path
BHProjectName = $ProjectName
BHBuildNumber = $env:BUILD_BUILDNUMBER
BHModulePath = (Resolve-Path $ProjectName).Path
BHBuildOutput = "BuildOutput"
BHBuildSystem = 'VSTS'
BHProjectPath = $PWD.Path
BHCommitMessage = $env:BUILD_SOURCEVERSIONMESSAGE
}
}
else {
@{
BHBranchName = $((git rev-parse --abbrev-ref HEAD).Trim())
BHPSModuleManifest = (Resolve-Path "$ProjectName\$ProjectName.psd1").Path
BHPSModulePath = (Resolve-Path $ProjectName).Path
BHProjectName = $ProjectName
BHBuildNumber = 'Unknown'
BHModulePath = (Resolve-Path $ProjectName).Path
BHBuildOutput = "BuildOutput"
BHBuildSystem = [System.Environment]::MachineName
BHProjectPath = $PWD.Path
BHCommitMessage = $((git log --format=%B -n 1).Trim())
}
}
Add-Heading 'Setting environment variables (if needed)'
foreach ($var in $gitVars.Keys) {
if (-not (Test-Path Env:\$var) -or (Get-Item Env:\$var).Value -ne $gitVars[$var]) {
Set-EnvironmentVariable $var $gitVars[$var]
}
}
}
function Publish-GitHubRelease {
<#
.SYNOPSIS
Publishes a release to GitHub Releases. Borrowed from https://www.herebedragons.io/powershell-create-github-release-with-artifact
#>
[CmdletBinding()]
Param (
[parameter(Mandatory = $true)]
[String]
$VersionNumber,
[parameter(Mandatory = $false)]
[String]
$CommitId = 'master',
[parameter(Mandatory = $true)]
[String]
$ReleaseNotes,
[parameter(Mandatory = $true)]
[ValidateScript( {Test-Path $_})]
[String[]]
$ArtifactPath,
[parameter(Mandatory = $true)]
[String]
$GitHubUsername,
[parameter(Mandatory = $true)]
[String]
$GitHubRepository,
[parameter(Mandatory = $true)]
[String]
$GitHubApiKey,
[parameter(Mandatory = $false)]
[Switch]
$PreRelease,
[parameter(Mandatory = $false)]
[Switch]
$Draft
)
$releaseData = @{
tag_name = [string]::Format("v{0}", $VersionNumber)
target_commitish = $CommitId
name = [string]::Format("$($env:BHProjectName) v{0}", $VersionNumber)
body = $ReleaseNotes
draft = [bool]$Draft
prerelease = [bool]$PreRelease
}
$auth = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($gitHubApiKey + ":x-oauth-basic"))
$releaseParams = @{
Uri = "https://api.github.com/repos/$GitHubUsername/$GitHubRepository/releases"
Method = 'POST'
Headers = @{
Authorization = $auth
}
ContentType = 'application/json'
Body = (ConvertTo-Json $releaseData -Compress)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$result = Invoke-RestMethod @releaseParams
$uploadUri = $result | Select-Object -ExpandProperty upload_url
$uploadUri = $uploadUri -creplace '\{\?name,label\}'
foreach ($item in $ArtifactPath) {
$artifact = Get-Item $item
$uploadUri = $uploadUri + "?name=$($artifact.Name)"
$uploadFile = $artifact.FullName
$uploadParams = @{
Uri = $uploadUri
Method = 'POST'
Headers = @{
Authorization = $auth
}
ContentType = 'application/zip'
InFile = $uploadFile
}
$result = Invoke-RestMethod @uploadParams
}
}
function Get-VersionToDeploy {
[CmdletBinding()]
Param(
[Parameter(Mandatory,Position = 0)]
[String]
$ModuleName,
[Parameter(Mandatory,Position = 1)]
[String]
$ManifestPath
)
Process {
$commParsed = $env:BHCommitMessage | Select-String -Pattern '\sv\d+\.\d+\.\d+\s'
if ($commParsed) {
$commitVer = $commParsed.Matches.Value.Trim().Replace('v','')
}
$curVer = (Import-PowerShellDataFile $ManifestPath).ModuleVersion
if ($IsCi) {
try {
$moduleInGallery = Find-Module $ModuleName -Repository PSGallery -ErrorAction Stop | Where-Object {$_.Name -eq $ModuleName}
$galVer = $moduleInGallery.Version.ToString()
Write-BuildLog "Current $ModuleName version on the PSGallery is: $galVer"
$galVerSplit = $galVer.Split('.')
$nextGalVer = [System.Version](($galVerSplit[0..($galVerSplit.Count - 2)] -join '.') + '.' + ([int]$galVerSplit[-1] + 1))
}
catch {
$nextGalVer = $galVer = $curVer
}
$versionToDeploy = if ($commitVer -and ([System.Version]$commitVer -lt $nextGalVer)) {
Write-BuildWarning "Version in commit message is $commitVer, which is less than the next Gallery version and would result in an error. Possible duplicate deployment build, skipping module bump and negating deployment"
$env:BHCommitMessage = $env:BHCommitMessage.Replace('!deploy','')
$null
}
elseif ($commitVer -and ([System.Version]$commitVer -gt $nextGalVer)) {
Write-BuildLog "Module version to deploy: $commitVer [from commit message]"
[System.Version]$commitVer
}
elseif ($curVer -ge $nextGalVer) {
Write-BuildLog "Module version to deploy: $curVer [from manifest]"
$curVer
}
elseif ($env:BHCommitMessage -match '!hotfix') {
Write-BuildLog "Module version to deploy: $nextGalVer [commit message match '!hotfix']"
$nextGalVer
}
elseif ($env:BHCommitMessage -match '!minor') {
$minorVers = [System.Version]("{0}.{1}.{2}" -f $nextGalVer.Major,([int]$nextGalVer.Minor + 1),0)
Write-BuildLog "Module version to deploy: $minorVers [commit message match '!minor']"
$minorVers
}
elseif ($env:BHCommitMessage -match '!major') {
$majorVers = [System.Version]("{0}.{1}.{2}" -f ([int]$nextGalVer.Major + 1),0,0)
Write-BuildLog "Module version to deploy: $majorVers [commit message match '!major']"
$majorVers
}
else {
Write-BuildLog "Module version to deploy: $nextGalVer [PSGallery next version]"
$nextGalVer
}
if ($versionToDeploy) {
return $versionToDeploy
}
else {
Write-BuildWarning "No module version matched! Negating deployment to prevent errors"
$env:BHCommitMessage = $env:BHCommitMessage.Replace('!deploy','')
return
}
}
else {
return $curVer
}
}
}
function Get-PSGalleryVersion {
[CmdletBinding()]
Param (
[Parameter(Mandatory,Position = 0)]
[String]
$Module
)
Process {
$Uri = "https://www.powershellgallery.com/api/v2/Packages?`$filter=Id eq '$Module' and IsLatestVersion"
Invoke-RestMethod $URI |
Select-Object @{n = 'Name';ex = { $_.title.('#text') } },
@{n = 'Author';ex = { $_.author.name } },
@{n = 'Version';ex = {
if ($_.properties.NormalizedVersion) {
$_.properties.NormalizedVersion
}
else {
$_.properties.Version
}
}
},
@{n = 'Uri';ex = { $_.Content.src } },
@{n = 'Description';ex = { $_.properties.Description } },
@{n = 'Properties';ex = { $_.properties } }
}
}
function Get-NextModuleVersion {
[CmdletBinding()]
Param (
[Parameter(Mandatory)]
[AllowNull()]
[AllowEmptyString()]
[string]
$GalleryVersion,
[Parameter(Mandatory)]
[string]
$ManifestVersion,
[Parameter()]
[Switch]
$IsScheduled,
[Parameter()]
[Switch]
$IsPrerelease
)
Begin {
$dateString = Get-Date -Format 'yyyyMMdd'
}
Process {
if ([string]::IsNullOrEmpty($GalleryVersion)) {
$GalleryVersion = "0.0.1.$($dateString)"
}
if ([System.Version]$ManifestVersion -gt [System.Version]$GalleryVersion) {
$GalleryVersion = $ManifestVersion
$split = $ManifestVersion.Split('.')
$UsingManifestVersion = $true
}
else {
$split = $GalleryVersion.Split('.')
$UsingManifestVersion = $false
}
$third = if ($UsingManifestVersion -or $IsScheduled -or $env:BUILD_REASON -eq 'Schedule') {
$split[2]
}
else {
[int]$split[2] + 1
}
if ($IsPrerelease) {
Write-BuildLog "This is a prerelease! Returning 3 part version to comply with prerelease versioning requirements. Date will be appended to the prerelease string instead."
'{0}.{1}.{2}' -f $split[0],$split[1],$third
}
else {
'{0}.{1}.{2}.{3}' -f $split[0],$split[1],$third,$dateString
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment