44# Run by changing to the project root directory and run ./Invoke-Build.ps1
55# Uses a master-always-deploys strategy and semantic versioning - http://nvie.com/posts/a-successful-git-branching-model/
66
7- # This variable specifies what modules to bootstrap for the build
8- # It is recommended to only bootstrap BuildHelpers and PSDepend, and use PSDepend for remaining prereqs
9- $BuildHelperModules = " BuildHelpers" , " PSDepend" , " Pester" , " powershell-yaml" , " Microsoft.Powershell.Archive" , " PSScriptAnalyzer"
7+ param (
8+ # Skip publishing to various destinations (Appveyor,Github,PowershellGallery,etc.)
9+ [Switch ]$SkipPublish ,
10+ # Force deployment step even if we are not in master. If you are following GitFlow or GitHubFlow you should never need to do this.
11+ [Switch ]$ForceDeploy ,
12+ # Show detailed environment variables
13+ [Switch ]$ShowEnvironmentVariables ,
14+ # Powershell modules required for the build process
15+ [String []]$BuildHelperModules = @ (" BuildHelpers" , " Pester" , " powershell-yaml" , " Microsoft.Powershell.Archive" , " PSScriptAnalyzer" ),
16+ # Which build files/folders should be excluded from packaging
17+ [String []]$BuildFilesToExclude = @ (" Build" , " BuildOutput" , " Tests" , " .git*" , " appveyor.yml" , " gitversion.yml" , " *.build.ps1" , " .vscode" , " .placeholder" ),
18+ # NuGet API Key for Powershell Gallery Deployment. Defaults to environment variable of the same name
19+ $NuGetAPIKey = $env: NuGetAPIKey ,
20+ # GitHub API Key for Github Releases. Defaults to environment variable of the same name
21+ $GitHubAPIKey = $env: GitHubAPIKey
22+ )
1023
1124# Initialize Build Environment
1225Enter-Build {
26+ # Initialize Script-scope variables
27+ New-Variable ArtifactPaths
28+ New-Variable ProjectVersion
29+ New-Variable ProjectBuildPath
30+
1331 $lines = ' ----------------------------------------------------------------'
1432 function Write-VerboseHeader ([String ]$Message ) {
1533 # Simple function to add lines around a header
@@ -21,7 +39,7 @@ Enter-Build {
2139
2240 # Detect if we are in a continuous integration environment (Appveyor, etc.) or otherwise running noninteractively
2341 if ($ENV: CI -or ([Environment ]::GetCommandLineArgs() -like ' -noni*' )) {
24- write-build Green ' Detected a Noninteractive or CI environment, disabling prompt confirmations'
42+ write-build Green ' Build Initialization: Detected a Noninteractive or CI environment, disabling prompt confirmations'
2543 $SCRIPT :CI = $true
2644 $ConfirmPreference = ' None'
2745 $ProgressPreference = " SilentlyContinue"
@@ -34,7 +52,7 @@ Enter-Build {
3452
3553 foreach ($BuildHelperModuleItem in $BuildHelperModules ) {
3654 if (-not (Get-module $BuildHelperModuleItem - listavailable)) {
37- write-verbose " Installing $BuildHelperModuleItem from Powershell Gallery to your currentuser module directory"
55+ write-verbose " Build Initialization: Installing $BuildHelperModuleItem from Powershell Gallery to your currentuser module directory"
3856 if ($PSVersionTable.PSVersion.Major -lt 5 ) {
3957 write-verboseheader " Bootstrapping Powershell Module: $BuildHelperModuleItem "
4058 Invoke-Command - ArgumentList @ (, $BuildHelperModules ) - ScriptBlock ([scriptblock ]::Create((new-object net.webclient).DownloadString(' https://git.io/PSModBootstrap' )))
@@ -70,23 +88,20 @@ Enter-Build {
7088 write-build Green " Current Branch Name: $BranchName "
7189
7290 if ( ($VerbosePreference -ne ' SilentlyContinue' ) -or ($CI -and ($BranchName -ne ' master' )) ) {
73- write-build Green " Verbose Build Logging Enabled"
91+ write-build Green " Build Initialization: Verbose Build Logging Enabled"
7492 $SCRIPT :VerbosePreference = " Continue"
7593 $PassThruParams.Verbose = $true
7694 }
7795
96+
7897 write-verboseheader " Build Environment Prepared! Environment Information:"
7998 Get-BuildEnvironment | format-list | out-string | write-verbose
99+ if ($ShowEnvironmentVariables ) {
100+ write-verboseheader " Current Environment Variables"
101+ get-childitem env: | out-string | write-verbose
80102
81- write-verboseheader " Current Environment Variables"
82- get-childitem env: | out-string | write-verbose
83-
84- write-verboseheader " Powershell Variables"
85- Get-Variable | select-object name, value, visibility | format-table - autosize | out-string | write-verbose
86-
87- if ($ENV: APPVEYOR ) {
88- write-verboseheader " Detected that we are running in Appveyor! Appveyor Environment Info:"
89- get-item env:/ Appveyor* | out-string | write-verbose
103+ write-verboseheader " Powershell Variables"
104+ Get-Variable | select-object name, value, visibility | format-table - autosize | out-string | write-verbose
90105 }
91106
92107 # Register Nuget
@@ -143,15 +158,16 @@ task Version {
143158 # Fetch GitVersion
144159 # TODO: Use Nuget.exe to fetch to make this v3/v4 compatible
145160 $GitVersionCMDPackageName = " gitversion.commandline"
146- if (! (Get-Package $GitVersionCMDPackageName - erroraction SilentlyContinue)) {
161+ $GitVersionCMDPackage = Get-Package $GitVersionCMDPackageName - erroraction SilentlyContinue
162+ if (! ($GitVersionCMDPackage )) {
147163 write-verbose " Package $GitVersionCMDPackageName Not Found Locally, Installing..."
148- write-verboseheader " Nuget.Org Package Source Info for fetching Gitversion "
164+ write-verboseheader " Nuget.Org Package Source Info for fetching GitVersion "
149165 Get-PackageSource | Format-Table | out-string | write-verbose
150166
151167 # Fetch GitVersion
152- Install-Package $GitVersionCMDPackageName - scope currentuser - source ' nuget.org' - force @PassThruParams | Out-Null
168+ $GitVersionCMDPackage = Install-Package $GitVersionCMDPackageName - scope currentuser - source ' nuget.org' - force @PassThruParams
153169 }
154- $GitVersionEXE = ((get-package $GitVersionCMDPackageName ).source | split-path - Parent) + " \tools\GitVersion.exe"
170+ $GitVersionEXE = (($GitVersionCMDPackage ).source | split-path - Parent) + " \tools\GitVersion.exe"
155171
156172 # Does this project have a module manifest? Use that as the Gitversion starting point (will use this by default unless project is tagged higher)
157173 # Uses Powershell-YAML module to read/write the GitVersion.yaml config file
@@ -172,7 +188,8 @@ task Version {
172188
173189 # Calcuate the GitVersion
174190 write-verbose " Executing GitVersion to determine version info"
175- $GitVersionOutput = & $GitVersionEXE $buildRoot
191+ $GitVersionCommand = " $GitVersionEXE $buildRoot "
192+ $GitVersionOutput = Invoke-BuildExec { & $GitVersionEXE $buildRoot }
176193
177194 # Since GitVersion doesn't return error exit codes, we look for error text in the output in the output
178195 if ($GitVersionOutput -match ' ^[ERROR|INFO] \[' ) {throw " An error occured when running GitVersion.exe $buildRoot " }
@@ -196,8 +213,8 @@ task Version {
196213
197214
198215 $SCRIPT :ProjectSemVersion = $ ($GitVersionInfo.fullsemver )
199- write-build Green " Using Project Version: $ProjectBuildVersion "
200- write-build Green " Using Project Version (Extended): $ ( $GitVersionInfo.fullsemver ) "
216+ write-build Green " Task $ ( $task .name ) `: Using Project Version: $ProjectBuildVersion "
217+ write-build Green " Task $ ( $task .name ) `: Using Project Version (Extended): $ ( $GitVersionInfo.fullsemver ) "
201218}
202219
203220# Copy all powershell module "artifacts" to Build Directory
@@ -208,7 +225,7 @@ task CopyFilesToBuildDir {
208225 # The file or file paths to copy, excluding the powershell psm1 and psd1 module and manifest files which will be autodetected
209226 # TODO: Move this somewhere higher in the hierarchy into a settings file, or rather go the "exclude" route
210227 $FilesToCopy = " lib" , " Public" , " Private" , " Types" , " LICENSE" , " README.md" , " $ ( $Env: BHProjectName ) .psm1" , " $ ( $Env: BHProjectName ) .psd1"
211- copy-item - Recurse - Path $FilesToCopy - Destination $ProjectBuildPath @PassThruParams
228+ copy-item - Recurse - Path $buildRoot \ * - Exclude $BuildFilesToExclude - Destination $ProjectBuildPath @PassThruParams
212229}
213230
214231# Update the Metadata of the Module with the latest Version
@@ -218,7 +235,7 @@ task UpdateMetadata CopyFilesToBuildDir,Version,{
218235 # TODO: Find a cleaner solution, like update Set-ModuleFunctions to use a separate runspace or include a market to know we are in ModuleFunctions so when loading the module we can copy the assemblies to temp files first
219236 $ProjectBuildManifest = ($ProjectBuildPath + " \" + (split-path $env: BHPSModuleManifest - leaf))
220237 $tempModuleDir = [System.IO.Path ]::GetTempFileName()
221- Remove-Item $tempModuleDir
238+ Remove-Item $tempModuleDir - verbose: $false
222239 New-Item - Type Directory $tempModuleDir | out-null
223240 copy-item - recurse $ProjectBuildPath /* $tempModuleDir
224241
@@ -232,7 +249,7 @@ task UpdateMetadata CopyFilesToBuildDir,Version,{
232249
233250 # Are we in the master or develop/development branch? Bump the version based on the powershell gallery if so, otherwise add a build tag
234251 if ($BranchName -match ' ^(master|dev(elop)?(ment)?)$' ) {
235- write-build Green " In Master/Develop branch, adding Tag Version $ProjectBuildVersion to this build"
252+ write-build Green " Task $ ( $task .name ) `: In Master/Develop branch, adding Tag Version $ProjectBuildVersion to this build"
236253 $Script :ProjectVersion = $ProjectBuildVersion
237254 if (-not (git tag - l $ProjectBuildVersion )) {
238255 git tag " $ProjectBuildVersion "
@@ -250,7 +267,7 @@ task UpdateMetadata CopyFilesToBuildDir,Version,{
250267 }
251268 #>
252269 } else {
253- write-build Green " Not in Master/Develop branch, marking this as a feature prelease build"
270+ write-build Green " Task $ ( $task .name ) `: Not in Master/Develop branch, marking this as a feature prelease build"
254271 $Script :ProjectVersion = $ProjectSemVersion
255272 # Set an email address for tag commit to work if it isn't already present
256273 if (-not (git config user.email)) {
@@ -323,20 +340,101 @@ task Pester {
323340}
324341
325342task Package Version, {
343+
326344 $ZipArchivePath = (join-path $env: BHBuildOutput " $env: BHProjectName -$ProjectVersion .zip" )
327- write-build green " Writing Finished Module to $ZipArchivePath "
345+ write-build green " Task $ ( $task .name ) `: Writing Finished Module to $ZipArchivePath "
328346 # Package the Powershell Module
329347 Compress-Archive - Path $ProjectBuildPath - DestinationPath $ZipArchivePath - Force @PassThruParams
330348
349+ $Artifacts += $ZipArchivePath
331350 # If we are in Appveyor, push completed zip to Appveyor Artifact
332351 if ($env: APPVEYOR ) {
333- write-build Green " Detected Appveyor, pushing Powershell Module archive to Artifacts"
352+ write-build Green " Task $ ( $task .name ) `: Detected Appveyor, pushing Powershell Module archive to Artifacts"
334353 Push-AppveyorArtifact $ZipArchivePath
335354 }
336355}
337356
357+ task PreDeploymentChecks {
358+ # Do not proceed if the most recent Pester test is not passing.
359+ $CurrentErrorActionPreference = $ErrorActionPreference
360+ try {
361+ $ErrorActionPreference = " Stop"
362+ $MostRecentPesterTestResult = [xml ]((Get-Content - raw (get-item " $ENV: BHBuildOutput /*-TestResults*.xml" | sort lastwritetime | select - last 1 )))
363+ $MostRecentPesterTestResult = $MostRecentPesterTestResult ." test-results"
364+ if (
365+ $MostRecentPesterTestResult -isnot [System.XML.XMLElement ] -or
366+ $MostRecentPesterTestResult.errors -gt 0 -or
367+ $MostRecentPesterTestResult.failures -gt 0
368+ ) {throw " Fail!" }
369+ } catch {
370+ throw " Unable to detect a clean passing Pester Test xml in the $env: BHBuildOutput directory. Ensure you were successful in the Build and Test phases first."
371+ }
372+ finally {
373+ $ErrorActionPreference = $CurrentErrorActionPreference
374+ }
375+
376+ if ((-not $env: BHBranchName -eq ' Master' ) -or ($ForceDeploy -ne $true )) {
377+ write-build Magenta " Task $ ( $task.name ) `: We are not in master branch, skipping publish. If you wish to deploy anyways such as for testing, run {InvokeBuild Deploy -ForceDeploy:$true }"
378+ $script :SkipPublish = $true
379+ } else {
380+ if (-not (Get-Item $ProjectBuildPath /* .psd1 - erroraction silentlycontinue)) {throw " No Powershell Module Found in $ProjectBuildPath . Skipping deployment. Did you remember to build it first with {Invoke-Build Build}?" }
381+ }
382+ }
383+
384+ task PublishPSGallery {
385+ if (-not $SkipPublish ) {
386+ if ($AppVeyor -and -not $NuGetAPIKey ) {
387+ write-build DarkYellow " Couldn't find NuGetAPIKey in the Appveyor secure environment variables. Did you save your NuGet/Powershell Gallery API key as an Appveyor Secure Variable? https://docs.microsoft.com/en-us/powershell/gallery/psgallery/creating-and-publishing-an-item and https://www.appveyor.com/docs/build-configuration/"
388+ $SkipPublish = $true
389+ }
390+ if (-not $env: NuGetAPIKey ) {
391+ # TODO: Add Windows Credential Store support and some kind of Linux secure storage or caching option
392+ write-build DarkYellow ' $env:NuGetAPIKey was not found as an environment variable. Please specify it or use {Invoke-Build Deploy -NuGetAPIKey "MyAPIKeyString"}. Have you registered for a Powershell Gallery API key yet? https://docs.microsoft.com/en-us/powershell/gallery/psgallery/creating-and-publishing-an-item'
393+ $SkipPublish = $true
394+ }
395+ }
396+
397+ if ($SkipPublish ) {
398+ Write-Build Magenta " Task $ ( $task.name ) `: Skipping Powershell Gallery Publish"
399+ } else {
400+
401+ $publishParams = @ {
402+ Path = $ProjectBuildPath
403+ NuGetApiKey = $NuGetAPIKey
404+ Repository = ' PSGallery'
405+ Force = $true
406+ ErrorAction = ' Stop'
407+ Confirm = $false
408+ }
409+ # TODO: Add Prerelease Logic when message commit says "!prerelease"
410+ Publish-Module @publishParams @PassThruParams
411+ }
412+ }
413+
414+ task PublishGitHubRelease Package, {
415+
416+ # TODO: Add Prerelease Logic when message commit says "!prerelease"
417+ if (-not $SkipPublish ) {
418+ if ($AppVeyor -and -not $GitHubAPIKey ) {
419+ write-build DarkYellow " Task PublishGitHubRelease: Couldn't find GitHubAPIKey in the Appveyor secure environment variables. Did you save your Github API key as an Appveyor Secure Variable? https://docs.microsoft.com/en-us/powershell/gallery/psgallery/creating-and-publishing-an-item and https://github.com/settings/tokens"
420+ $SkipPublish = $true
421+ }
422+ if (-not $env: GitHubAPIKey ) {
423+ # TODO: Add Windows Credential Store support and some kind of Linux secure storage or caching option
424+ write-build DarkYellow ' Task PublishGitHubRelease: $env:GitHubAPIKey was not found as an environment variable. Please specify it or use {Invoke-Build Deploy -NuGetAPIKey "MyAPIKeyString"}. Have you created a GitHub API key with minimum public_repo scope permissions yet? https://github.com/settings/tokens'
425+ $SkipPublish = $true
426+ }
427+ }
428+ if ($SkipPublish ) {
429+ write-build Magenta " Task $ ( $task.name ) : Skipping Publish to GitHub Releases"
430+ } else {
431+ # TODO: Add GitHubRelease Logic
432+ }
433+
434+ }
435+
338436# Deploy Supertask
339- task Deploy Package
437+ task Deploy PreDeploymentChecks , Package, PublishPSGallery , PublishGitHubRelease
340438
341439# Build SuperTask
342440task Build Clean , CopyFilesToBuildDir, UpdateMetadata
0 commit comments