Bringing it all together

With all the components introduced, we can use the template structure for our module development:

  • ModuleNamePublic
    • Contains all publicly visible module functions
    • Each function is in a separate script, called NameOfFunction.ps1
  • ModuleNamePrivate
    • Contains all internally visible functions
    • Each function is in a separate script, called NameOfFunction.ps1
  • ModuleNameTypes
    • Contains all .NET/PowerShell classes the module requires
  • ModuleNameModuleName.psm1
    • Your script module
  • ModuleNameModuleName.psd1
    • Your module manifest
  • Test
    • Contains all unit and integration tests to execute, possibly sorted into subfolders
    • Usually one *.Test.ps1 file per function
  • ModuleName.psdeploy.ps1
  • appveyor.yml
  • build.ps1
  • psake.ps1
  • README.md
  • LICENSE

As the central entry point, the build script kicks off your deployment process. We like to use Psake and PSDeploy, but you can also simply use your build script to create a Nuget package, execute tests, and so on. As an example, AutomatedLab.Common—a module collecting helper functions for infrastructure—uses the following combination of build, Psake, and Deploy scripts:

## Build.ps1
Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null

Resolve-Module Psake, PSDeploy, Pester, BuildHelpers, PSScriptAnalyzer

Set-BuildEnvironment

Invoke-psake .psake.ps1
exit ( [int]( -not $psake.build_success ) )

The build script invokes Psake, a PowerShell module to run tasks defined in yet another domain-specific language. Psake is a task-based module with which you can automate your build process to be run in a pipeline:

## Psake.ps1
# PSake makes variables declared here available in other scriptblocks
# Init some things
Properties {
# Find the build folder based on build system
$ProjectRoot = $ENV:BHProjectPath
if (-not $ProjectRoot)
{
$ProjectRoot = $PSScriptRoot
}

$Timestamp = Get-date -uformat "%Y%m%d-%H%M%S"
$PSVersion = $PSVersionTable.PSVersion.Major
$TestFile = "TestResults_PS$PSVersion`_$TimeStamp.xml"
$lines = '----------------------------------------------------------------------'
}

The first portion of the build script just sets variables to be made available in other script blocks. By adding to the properties block, these variables will be available elsewhere in your build script:

Task Default -Depends Deploy

Task Init {
$lines
Set-Location $ProjectRoot
"Build System Details:"
Get-Item ENV:BH*
"`n"
}

The first task inside the build script ensures that the build is executed at the project root and not somewhere else in the file system. Additionally, the build system's details, such as CI tool name and Git commit ID, are displayed:

Task Test -Depends Init {
$lines
"`n`tSTATUS: Testing with PowerShell $PSVersion"

# Run Script Analyzer
$start = Get-Date
If ($ENV:BHBuildSystem -eq 'AppVeyor') {Add-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Running}
$scriptAnalyerResults = Invoke-ScriptAnalyzer -Path (Join-Path $ENV:BHProjectPath $ENV:BHProjectName) -Recurse -Severity Error -ErrorAction SilentlyContinue
$end = Get-Date
if ($scriptAnalyerResults -and $ENV:BHBuildSystem -eq 'AppVeyor')
{
Add-AppveyorMessage -Message "PSScriptAnalyzer output contained one or more result(s) with 'Error' severity." -Category Error
Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Failed -ErrorMessage ($scriptAnalyerResults | Out-String) -Duration ([long]($end - $start).TotalMilliSeconds)
}
elseif ($ENV:BHBuildSystem -eq 'AppVeyor')
{
Update-AppveyorTest -Name "PsScriptAnalyzer" -Outcome Passed -Duration ([long]($end - $start).TotalMilliSeconds)
}

# Gather test results. Store them in a variable and file
$TestResults = Invoke-Pester -Path $ProjectRootTests -PassThru -OutputFormat NUnitXml -OutputFile "$ProjectRoot$TestFile"

# In Appveyor? Upload our tests!
If ($ENV:BHBuildSystem -eq 'AppVeyor')
{
(New-Object 'System.Net.WebClient').UploadFile(
"https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)",
"$ProjectRoot$TestFile" )
}

Remove-Item "$ProjectRoot$TestFile" -Force -ErrorAction SilentlyContinue

# Failed tests?
# Need to tell psake or it will proceed to the deployment. Danger!
if ($TestResults.FailedCount -gt 0)
{
Write-Error "Failed '$($TestResults.FailedCount)' tests, build failed"
}
"`n"
}

The Test task depends on the init task to finish first. The task is responsible for executing all of the Pester tests that have been discovered. On certain build systems, it will upload test results. Other systems simply use the resulting NUnit XML file:

Task Build -Depends Test {
$lines

# Load the module, read the exported functions, update the psd1 FunctionsToExport
Set-ModuleFunctions -Verbose

# Bump the module version
Update-Metadata -Path $env:BHPSModuleManifest -Verbose -Value $env:APPVEYOR_BUILD_VERSION
}

Task Deploy -Depends Build {
$lines
"Starting deployment with files inside $ProjectRoot"

$Params = @{
Path = $ProjectRoot
Force = $true
Recurse = $false # We keep psdeploy artifacts, avoid deploying those : )
Verbose = $true
}
Invoke-PSDeploy @Params
}

Inside its build tasks, the Psake script eventually uses PSDeploy to deploy to the PowerShell Gallery and to the CI system. This means that artifacts such as Nuget packages, MSI installers, and ZIP files are uploaded as build artifacts that can be deployed and tested inside the pipeline:

## AutomatedLab.Common.psdeploy

# Set-BuildEnvironment from BuildHelpers module has populated ENV:BHProjectName

# Publish to gallery with a few restrictions
if (
(Join-Path $ENV:BHProjectPath $ENV:BHProjectName) -and
$env:BHBuildSystem -ne 'Unknown' -and
$env:BHBranchName -eq "master"
)
{
Deploy Module {
By PSGalleryModule {
FromSource (Join-Path $ENV:BHProjectPath $ENV:BHProjectName)
To PSGallery
WithOptions @{
ApiKey = $ENV:NugetApiKey
}
}
}
}

When using AppVeyor, you can use the appveyor.yml file to control the CI settings. The same can be done from within the web interface. The YML structure is well-documented at https://www.appveyor.com/docs/appveyor-yml.

If everything went well, you will be left with an automated build process that you can kick off manually at any time, and which can be moved to any CI tool that can trigger PowerShell scripts and work with NUnit XML files.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset