forked from andrewmatveychuk/powershell.sample-module
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SampleModule.build.ps1
320 lines (268 loc) · 11.2 KB
/
SampleModule.build.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#requires -modules InvokeBuild
<#
.SYNOPSIS
Build script (https://github.com/nightroman/Invoke-Build)
.DESCRIPTION
This script contains the tasks for building the 'SampleModule' PowerShell module
#>
Param (
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateSet('Debug', 'Release')]
[String]
$Configuration = 'Debug',
[Parameter(ValueFromPipelineByPropertyName = $true)]
[ValidateNotNullOrEmpty()]
[String]
$SourceLocation
)
Set-StrictMode -Version Latest
# Synopsis: Default task
task . Clean, Build
# Install build dependencies
Enter-Build {
# Installing PSDepend for dependency management
if (-not (Get-Module -Name PSDepend -ListAvailable)) {
Install-Module PSDepend -Force
}
Import-Module PSDepend
# Installing dependencies
Invoke-PSDepend -Force
# Setting build script variables
$script:moduleName = 'SampleModule'
$script:moduleSourcePath = Join-Path -Path $BuildRoot -ChildPath $moduleName
$script:moduleManifestPath = Join-Path -Path $moduleSourcePath -ChildPath "$moduleName.psd1"
$script:nuspecPath = Join-Path -Path $moduleSourcePath -ChildPath "$moduleName.nuspec"
$script:buildOutputPath = Join-Path -Path $BuildRoot -ChildPath 'build'
# Setting base module version and using it if building locally
$script:newModuleVersion = New-Object -TypeName 'System.Version' -ArgumentList (0, 0, 1)
# Setting the list of functions ot be exported by module
$script:functionsToExport = (Test-ModuleManifest $moduleManifestPath).ExportedFunctions
}
# Synopsis: Analyze the project with PSScriptAnalyzer
task Analyze {
# Get-ChildItem parameters
$Params = @{
Path = $moduleSourcePath
Recurse = $true
Include = "*.PSSATests.*"
}
$TestFiles = Get-ChildItem @Params
# Pester parameters
$Params = @{
Path = $TestFiles
PassThru = $true
}
# Additional parameters on Azure Pipelines agents to generate test results
if ($env:TF_BUILD) {
if (-not (Test-Path -Path $buildOutputPath -ErrorAction SilentlyContinue)) {
New-Item -Path $buildOutputPath -ItemType Directory
}
$Timestamp = Get-date -UFormat "%Y%m%d-%H%M%S"
$PSVersion = $PSVersionTable.PSVersion.Major
$TestResultFile = "AnalysisResults_PS$PSVersion`_$TimeStamp.xml"
$Params.Add("OutputFile", "$buildOutputPath\$TestResultFile")
$Params.Add("OutputFormat", "NUnitXml")
}
# Invoke all tests
$TestResults = Invoke-Pester @Params
if ($TestResults.FailedCount -gt 0) {
$TestResults | Format-List
throw "One or more PSScriptAnalyzer rules have been violated. Build cannot continue!"
}
}
# Synopsis: Test the project with Pester tests
task Test {
# Get-ChildItem parameters
$Params = @{
Path = $moduleSourcePath
Recurse = $true
Include = "*.Tests.*"
}
$TestFiles = Get-ChildItem @Params
# Pester parameters
$Params = @{
Path = $TestFiles
PassThru = $true
}
# Additional parameters on Azure Pipelines agents to generate test results
if ($env:TF_BUILD) {
if (-not (Test-Path -Path $buildOutputPath -ErrorAction SilentlyContinue)) {
New-Item -Path $buildOutputPath -ItemType Directory
}
$Timestamp = Get-date -UFormat "%Y%m%d-%H%M%S"
$PSVersion = $PSVersionTable.PSVersion.Major
$TestResultFile = "TestResults_PS$PSVersion`_$TimeStamp.xml"
$Params.Add("OutputFile", "$buildOutputPath\$TestResultFile")
$Params.Add("OutputFormat", "NUnitXml")
}
# Invoke all tests
$TestResults = Invoke-Pester @Params
if ($TestResults.FailedCount -gt 0) {
$TestResults | Format-List
throw "One or more Pester tests have failed. Build cannot continue!"
}
}
# Synopsis: Generate a new module version if creating a release build
task GenerateNewModuleVersion -If ($Configuration -eq 'Release') {
# Using the current NuGet package version from the feed as a version base when building via Azure DevOps pipeline
# Define package repository name
$repositoryName = $moduleName + '-repository'
# Register a target PSRepository
try {
Register-PSRepository -Name $repositoryName -SourceLocation $SourceLocation -InstallationPolicy Trusted
}
catch {
throw "Cannot register '$repositoryName' repository with source location '$SourceLocation'!"
}
# Define variable for existing package
$existingPackage = $null
try {
# Look for the module package in the repository
$existingPackage = Find-Module -Name $moduleName -Repository $repositoryName
}
# In no existing module package was found, the base module version defined in the script will be used
catch {
Write-Warning "No existing package for '$moduleName' module was found in '$repositoryName' repository!"
}
# If existing module package was found, try to install the module
if ($existingPackage) {
# Get the largest module version
# $currentModuleVersion = (Get-Module -Name $moduleName -ListAvailable | Measure-Object -Property 'Version' -Maximum).Maximum
$currentModuleVersion = New-Object -TypeName 'System.Version' -ArgumentList ($existingPackage.Version)
# Set module version base numbers
[int]$Major = $currentModuleVersion.Major
[int]$Minor = $currentModuleVersion.Minor
[int]$Build = $currentModuleVersion.Build
try {
# Install the existing module from the repository
Install-Module -Name $moduleName -Repository $repositoryName -RequiredVersion $existingPackage.Version
}
catch {
throw "Cannot import module '$moduleName'!"
}
# Get the count of exported module functions
$existingFunctionsCount = (Get-Command -Module $moduleName | Where-Object -Property Version -EQ $existingPackage.Version | Measure-Object).Count
# Check if new public functions were added in the current build
[int]$sourceFunctionsCount = (Get-ChildItem -Path "$moduleSourcePath\Public\*.ps1" -Exclude "*.Tests.*" | Measure-Object).Count
[int]$newFunctionsCount = [System.Math]::Abs($sourceFunctionsCount - $existingFunctionsCount)
# Increase the minor number if any new public functions have been added
if ($newFunctionsCount -gt 0) {
[int]$Minor = $Minor + 1
[int]$Build = 0
}
# If not, just increase the build number
else {
[int]$Build = $Build + 1
}
# Update the module version object
$Script:newModuleVersion = New-Object -TypeName 'System.Version' -ArgumentList ($Major, $Minor, $Build)
}
}
# Synopsis: Generate list of functions to be exported by module
task GenerateListOfFunctionsToExport {
# Set exported functions by finding functions exported by *.psm1 file via Export-ModuleMember
$params = @{
Force = $true
Passthru = $true
Name = (Resolve-Path (Get-ChildItem -Path $moduleSourcePath -Filter '*.psm1')).Path
}
$PowerShell = [Powershell]::Create()
[void]$PowerShell.AddScript(
{
Param ($Force, $Passthru, $Name)
$module = Import-Module -Name $Name -PassThru:$Passthru -Force:$Force
$module | Where-Object { $_.Path -notin $module.Scripts }
}
).AddParameters($Params)
$module = $PowerShell.Invoke()
$Script:functionsToExport = $module.ExportedFunctions.Keys
}
# Synopsis: Update the module manifest with module version and functions to export
task UpdateModuleManifest GenerateNewModuleVersion, GenerateListOfFunctionsToExport, {
# Update-ModuleManifest parameters
$Params = @{
Path = $moduleManifestPath
ModuleVersion = $newModuleVersion
FunctionsToExport = $functionsToExport
}
# Update the manifest file
Update-ModuleManifest @Params
}
# Synopsis: Update the NuGet package specification with module version
task UpdatePackageSpecification GenerateNewModuleVersion, {
# Load the specification into XML object
$xml = New-Object -TypeName 'XML'
$xml.Load($nuspecPath)
# Update package version
$metadata = Select-XML -Xml $xml -XPath '//package/metadata'
$metadata.Node.Version = $newModuleVersion
# Save XML object back to the specification file
$xml.Save($nuspecPath)
}
# Synopsis: Build the project
task Build UpdateModuleManifest, UpdatePackageSpecification, {
# Warning on local builds
if ($Configuration -eq 'Debug') {
Write-Warning "Creating a debug build. Use it for test purpose only!"
}
# Create versioned output folder
$moduleOutputPath = Join-Path -Path $buildOutputPath -ChildPath $moduleName -AdditionalChildPath $newModuleVersion
if (-not (Test-Path $moduleOutputPath)) {
New-Item -Path $moduleOutputPath -ItemType Directory
}
# Copy-Item parameters
$Params = @{
Path = "$moduleSourcePath\*"
Destination = $moduleOutputPath
Exclude = "*.Tests.*", "*.PSSATests.*"
Recurse = $true
Force = $true
}
# Copy module files to the target build folder
Copy-Item @Params
}
# Synopsis: Verify the code coverage by tests
task CodeCoverage {
$acceptableCodeCoveragePercent = 60
$path = $moduleSourcePath
$files = Get-ChildItem $path -Recurse -Include '*.ps1', '*.psm1' -Exclude '*.Tests.ps1', '*.PSSATests.ps1'
$Params = @{
Path = $path
CodeCoverage = $files
PassThru = $true
Show = 'Summary'
}
# Additional parameters on Azure Pipelines agents to generate code coverage report
if ($env:TF_BUILD) {
if (-not (Test-Path -Path $buildOutputPath -ErrorAction SilentlyContinue)) {
New-Item -Path $buildOutputPath -ItemType Directory
}
$Timestamp = Get-date -UFormat "%Y%m%d-%H%M%S"
$PSVersion = $PSVersionTable.PSVersion.Major
$TestResultFile = "CodeCoverageResults_PS$PSVersion`_$TimeStamp.xml"
$Params.Add("CodeCoverageOutputFile", "$buildOutputPath\$TestResultFile")
}
$result = Invoke-Pester @Params
If ( $result.CodeCoverage ) {
$codeCoverage = $result.CodeCoverage
$commandsFound = $codeCoverage.NumberOfCommandsAnalyzed
# To prevent any "Attempted to divide by zero" exceptions
If ( $commandsFound -ne 0 ) {
$commandsExercised = $codeCoverage.NumberOfCommandsExecuted
[System.Double]$actualCodeCoveragePercent = [Math]::Round(($commandsExercised / $commandsFound) * 100, 2)
}
Else {
[System.Double]$actualCodeCoveragePercent = 0
}
}
# Fail the task if the code coverage results are not acceptable
if ($actualCodeCoveragePercent -lt $acceptableCodeCoveragePercent) {
throw "The overall code coverage by Pester tests is $actualCodeCoveragePercent% which is less than quality gate of $acceptableCodeCoveragePercent%. Pester ModuleVersion is: $((Get-Module -Name Pester -ListAvailable).ModuleVersion)."
}
}
# Synopsis: Clean up the target build directory
task Clean {
if (Test-Path $buildOutputPath) {
Remove-Item –Path $buildOutputPath –Recurse
}
}