Last active
January 11, 2021 05:07
-
-
Save scrthq/a99cc06e75eb31769d01b2adddc6d200 to your computer and use it in GitHub Desktop.
Azure Pipelines Helper Functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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