PSSecurity.build.ps1
|
Set-StrictMode -Version Latest $script:Generated = Join-Path -Path $PSScriptRoot -ChildPath 'Generated' $script:SourceFolders = @( (Join-Path -Path $PSScriptRoot -ChildPath 'Public') (Join-Path -Path $PSScriptRoot -ChildPath 'Private') ) #### <h1 style="color: #DCA657;">🎆 PSSecurity.Build</h1> #### #### > Defines the validation, documentation, integrity, and audit workflows for PSSecurity. #### #### Run the default build from the repository root: #### #### ```powershell #### Invoke-Build #### ``` #### #### Run one task by name when an agent needs a specific artifact or check: #### #### ```powershell #### Invoke-Build -Task <task-name> #### ``` #### #### --- #### #### <h2 style="color: #DCA657;">Build context</h2> #### #### The build expects the repository root to be the current directory because several task #### paths are relative. Generated reports are written beneath `Generated`. Pester coverage is #### measured against the PowerShell files in `Public` and `Private`. #### #### <h2 style="color: #DCA657;">Lifecycle commands</h2> #### #### The default task runs all lifecycle commands in policy order. #### #### | Task | Result | #### | --- | --- | #### | `Install` | Install runtime and build-time module dependencies. | #### | `Build` | Regenerate the repository hash indexes. | #### | `Test` | Run the test suite and write test-result and coverage reports. | #### | `Lint` | Analyze the repository and write the findings report. | #### | `Doc` | Regenerate Markdown documentation from source comments. | #### #### Integrity comparison and security audits are on-demand tasks. They do not run as part of #### the default pipeline. #### #### --- #### <h2 style="color: #DCA657;">Install</h2> #### #### Installs every runtime and build-time module declared in `Config/Setting.psd1`. #### Add-BuildTask Install { $settingsPath = Join-Path -Path $PSScriptRoot -ChildPath 'Config' -AdditionalChildPath 'Setting.psd1' $settings = Import-PowerShellDataFile -LiteralPath $settingsPath $dependencies = @($settings.PSSecurityRuntimeModules) + @($settings.PSSecurityBuildtimeModules) foreach ($dependency in $dependencies) { if (-not (Get-InstalledPSResource -Name $dependency.ModuleName -Version $dependency.ModuleVersion ` -ErrorAction SilentlyContinue)) { Install-PSResource -Name $dependency.ModuleName -Version $dependency.ModuleVersion ` -Scope CurrentUser -TrustRepository } } } #### #### <h2 style="color: #DCA657;">Build</h2> #### #### Imports PSSecurity and rebuilds `HashIndex.md` and `HashIndex.json` from the current #### repository contents. This task creates a new baseline; it does not compare against the #### previous index. #### Add-BuildTask Build { $moduleFile = Join-Path -Path "$PSScriptRoot" -ChildPath 'PSSecurity.psd1' Import-Module $moduleFile Write-DirectoryHash $PSScriptRoot } #### #### <h2 style="color: #DCA657;">Test</h2> #### #### Runs Pester from the repository root with plain-text console rendering. The task enables #### pass-through results, test-result output, and code coverage for `Public` and `Private`. #### #### | Artifact | Contents | #### | --- | --- | #### | `Generated/PesterTests.xml` | Test execution results. | #### | `Generated/PesterCodeCoverage.xml` | Coverage results for the module source folders. | #### Add-BuildTask Test { $originalRendering = $PSStyle.OutputRendering $PSStyle.OutputRendering = 'PlainText' Import-Module Pester -ErrorAction SilentlyContinue # get default from static property $configuration = [PesterConfiguration]::Default # PassThru is what makes Invoke-Pester hand back the result object we assert on $configuration.Run.PassThru = $true $configuration.TestResult.Enabled = $true # adding properties & discover via intellisense $configuration.TestResult.OutputPath = Join-Path -Path .\Generated -ChildPath 'PesterTests.xml' $configuration.CodeCoverage.Enabled = $true $configuration.CodeCoverage.OutputPath = Join-Path -Path .\Generated -ChildPath 'PesterCodeCoverage.xml' $configuration.CodeCoverage.Path = $script:SourceFolders [void](New-Item -Path .\Generated -ItemType Directory -Force) Invoke-Pester -Configuration $configuration $PSStyle.OutputRendering = $originalRendering } #### #### <h2 style="color: #DCA657;">Lint</h2> #### #### Runs PSScriptAnalyzer recursively with `PSScriptAnalyzerSettings.psd1`. Suppressed findings #### remain visible in the report. The task writes the findings to #### `Generated/ScriptAnalyzer.txt` and also returns them to the build output. #### Add-BuildTask Lint { Import-Module -Name PSScriptAnalyzer -ErrorAction SilentlyContinue New-Item -Path 'Generated' -ItemType Directory -ErrorAction SilentlyContinue $filePath = '.\PSScriptAnalyzerSettings.psd1' $scriptAnalysis = Invoke-ScriptAnalyzer -Path .\ -Recurse -IncludeSuppressed -Settings $filePath -ErrorAction SilentlyContinue $generatedPath = (Join-Path -Path "$PSScriptRoot" -ChildPath 'Generated' -AdditionalChildPath 'ScriptAnalyzer.txt') $scriptAnalysis | Out-File -FilePath $generatedPath $scriptAnalysis } # #### <h2 style="color: #DCA657;">Doc</h2> # #### # #### Imports Sharpdown and recursively renders the repository's PowerShell documentation into # #### `Doc`. Generated Markdown is derived from the Sharpdown comments in the source files and # #### should not be edited directly. # #### # Add-BuildTask Doc { # Import-Module Sharpdown # ConvertTo-SharpDown -Language PowerShell -Path .\ -OutPath .\Doc\ -Recurse # } #### <h2 style="color: #DCA657;">confirm_hash_index_integrity</h2> #### #### Checks the current repository against the existing `HashIndex.json` baseline: #### #### 1. Move the existing JSON index to the system temporary directory. #### 2. Import PSSecurity and generate a new index from the repository. #### 3. Compare the old and new JSON files line by line. #### 4. Remove the temporary baseline when the comparison completes. #### #### This task is on demand and is not part of the default pipeline. #### #### <b style="color: #C22514;">Throws</b> #### #### - When `HashIndex.json` does not exist before the check. #### - When the regenerated index differs from the existing baseline. #### Add-BuildTask confirm_hash_index_integrity { if (-not (Test-Path -Path .\HashIndex.json)) { throw 'Existing Hash Index is required to compare against.' } New-Item -Path 'Generated' -ItemType Directory -ErrorAction SilentlyContinue Move-Item -Path .\HashIndex.json -Destination "$($env:TEMP)" -Force $moduleFile = Join-Path -Path "$PSScriptRoot" -ChildPath 'PSSecurity.psd1' Import-Module $moduleFile Write-DirectoryHash $PSScriptRoot $difference = Compare-Object (Get-Content "$($env:TEMP)\HashIndex.json") (Get-Content .\HashIndex.json) if ($difference) { $difference Remove-Item "$($env:TEMP)\HashIndex.json" -Force throw 'Integrity violation. Hash indexes do not match.' } Remove-Item "$($env:TEMP)\HashIndex.json" -Force } #### <h2 style="color: #DCA657;">get_application_authenticode_audit</h2> #### #### Imports PSSecurity, audits application signatures, and writes the results as CSV to #### `Generated/AppSigAudit.csv`. This task is on demand and is not part of the default pipeline. #### Add-BuildTask get_application_authenticode_audit { $moduleFile = Join-Path -Path "$PSScriptRoot" -ChildPath 'PSSecurity.psd1' Import-Module $moduleFile Get-ApplicationSignatureAudit | ConvertTo-Csv | Out-File -FilePath .\Generated\AppSigAudit.csv } #### <h2 style="color: #DCA657;">get_acl_item_owner_anomaly_audit</h2> #### #### Recursively reads ACL information beneath the current user's home directory, measures owner #### anomalies, groups those anomalies by owner, and writes the grouped JSON to #### `Generated/AclItemOwnerAnomaly.json`. Hidden items are included. This task is on demand and #### is not part of the default pipeline. #### Add-BuildTask get_acl_item_owner_anomaly_audit { $moduleFile = Join-Path -Path "$PSScriptRoot" -ChildPath 'PSSecurity.psd1' Import-Module $moduleFile Get-ChildItem -Path (Resolve-Path '~') -Recurse -Force | ForEach-Object { $_.FullName | Get-AclItem } | Measure-OwnerAnomaly | Select-Object -ExpandProperty Anomalies | Group-Object -Property Owner | ConvertTo-Json -Depth 10 | Set-Content .\Generated\AclItemOwnerAnomaly.json } #### #### --- #### #### <h2 style="color: #DCA657;">Default task</h2> #### #### The `.` task is the entry point used when `Invoke-Build` is called without a task name. Its #### dependency list defines the default pipeline shown at the top of this document. #### Add-BuildTask . Install, Build, Test, Lint |