Public/Copy-AzureLocalPipelineExample.ps1
|
function Copy-AzureLocalPipelineExample { <# .SYNOPSIS Copy the bundled Automation-Pipeline-Examples folder out of the module install location into a target folder of the user's choice. .DESCRIPTION The AzLocal.UpdateManagement module ships a working set of CI/CD pipeline files (GitHub Actions YAML, Azure DevOps Pipelines YAML, an ITSM example config and ticket-body template, plus a step-by-step README) under its `Automation-Pipeline-Examples/` subfolder. Those files live inside the PowerShell module install path (typically `C:\Program Files\WindowsPowerShell\Modules\AzLocal.UpdateManagement\ <version>\Automation-Pipeline-Examples\` for AllUsers installs or the equivalent under `%USERPROFILE%\Documents\PowerShell\Modules\` for CurrentUser installs), which is awkward to browse manually. This function copies the entire `Automation-Pipeline-Examples` folder out of the module install location into a destination folder the user controls (default: the current working directory) so the YAML files and README can be opened, edited and committed into the user's own CI/CD repo without hunting through the module folder hierarchy. The function is read-only relative to the module install (it never modifies anything under `$module.ModuleBase`). It is destructive only relative to the destination folder, and only when `-Force` is supplied and a non-empty target already exists. Supports `-WhatIf` and `-Confirm`. .PARAMETER Destination Target folder to copy the pipeline examples into. If the folder does not exist it will be created. A subfolder named `Automation-Pipeline-Examples` will be created underneath it (matching the source layout) unless `-Flatten` is supplied. Defaults to the current working directory ($PWD). .PARAMETER Platform Which platform's pipeline files to copy. Valid values: - 'All' - copy everything (default) - 'GitHub' - copy only the `github-actions/` subfolder - 'AzureDevOps' - copy only the `azure-devops/` subfolder The top-level `README.md`, the `.itsm/` sample folder, and any other shared assets are always copied regardless of -Platform, because the README references files in both subfolders and the ITSM samples are platform-agnostic. .PARAMETER Flatten Copy the contents of `Automation-Pipeline-Examples` directly into `-Destination` rather than into a child folder of that name. Useful when the user has already created a dedicated CI folder and wants the YAML files to land there directly. .PARAMETER Force Overwrite existing files in the destination. Required when the destination already contains an `Automation-Pipeline-Examples` folder (or, with -Flatten, when the destination already contains any of the files the copy would write). .PARAMETER PassThru Return the [System.IO.DirectoryInfo] of the destination folder. By default the function writes only informational messages. .OUTPUTS [System.IO.DirectoryInfo] when -PassThru is specified. Nothing otherwise. .EXAMPLE Copy-AzureLocalPipelineExample Copies all pipeline examples into `.\Automation-Pipeline-Examples\` under the current directory. .EXAMPLE Copy-AzureLocalPipelineExample -Destination C:\repos\my-fleet -Platform GitHub Copies only the GitHub Actions YAML files (plus README and .itsm samples) into `C:\repos\my-fleet\Automation-Pipeline-Examples\`. .EXAMPLE New-Item -ItemType Directory .\.github\workflows -Force | Out-Null Copy-AzureLocalPipelineExample -Destination .\.github\workflows -Platform GitHub -Flatten -Force Copies the GitHub Actions YAML files directly into `.\.github\workflows\` (no `Automation-Pipeline-Examples` parent folder), overwriting any files of the same name. .EXAMPLE $dest = Copy-AzureLocalPipelineExample -Destination C:\repos\fleet -PassThru Set-Location $dest Copy the examples and cd into the destination folder. .NOTES Author : Neil Bird, Microsoft Module : AzLocal.UpdateManagement Added in : v0.7.4 #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] [OutputType([System.IO.DirectoryInfo])] param( [Parameter(Position = 0)] [ValidateNotNullOrEmpty()] [string]$Destination = $PWD.Path, [ValidateSet('All', 'GitHub', 'AzureDevOps')] [string]$Platform = 'All', [switch]$Flatten, [switch]$Force, [switch]$PassThru ) # ------------------------------------------------------------------ # 1. Locate the module install folder. We deliberately use # (Get-Module).ModuleBase rather than $PSScriptRoot so the function # works correctly when imported via the .psd1 path AND when the # function is dot-sourced standalone (e.g. in some test scenarios). # ------------------------------------------------------------------ $module = Get-Module -Name 'AzLocal.UpdateManagement' | Sort-Object Version -Descending | Select-Object -First 1 if (-not $module) { # Fallback: walk up from this file (Public/ -> module root) $moduleRoot = Split-Path -Parent $PSScriptRoot } else { $moduleRoot = $module.ModuleBase } $sourceRoot = Join-Path -Path $moduleRoot -ChildPath 'Automation-Pipeline-Examples' if (-not (Test-Path -LiteralPath $sourceRoot -PathType Container)) { throw "Copy-AzureLocalPipelineExample: pipeline examples folder not found at '$sourceRoot'. The module install may be corrupt or this is a development checkout without the sample folder." } # ------------------------------------------------------------------ # 2. Resolve destination. Create parent if missing. # ------------------------------------------------------------------ if (-not (Test-Path -LiteralPath $Destination)) { if ($PSCmdlet.ShouldProcess($Destination, 'Create destination folder')) { $null = New-Item -ItemType Directory -Path $Destination -Force -ErrorAction Stop } } # When -WhatIf is supplied and the destination did not pre-exist, it still # does not exist at this point; fall back to the literal -Destination string # so the rest of the function can describe what *would* happen without # throwing on Resolve-Path. $destResolved = if (Test-Path -LiteralPath $Destination) { (Resolve-Path -LiteralPath $Destination -ErrorAction Stop).ProviderPath } else { # Best-effort absolute path for WhatIf messaging [System.IO.Path]::GetFullPath($Destination) } $targetRoot = if ($Flatten.IsPresent) { $destResolved } else { Join-Path -Path $destResolved -ChildPath 'Automation-Pipeline-Examples' } # ------------------------------------------------------------------ # 3. Decide what to copy based on -Platform. The README, .itsm/ and any # other top-level assets are always included. # ------------------------------------------------------------------ $includePlatformGitHub = $Platform -in @('All', 'GitHub') $includePlatformAdo = $Platform -in @('All', 'AzureDevOps') $sourceItems = Get-ChildItem -LiteralPath $sourceRoot -Force $itemsToCopy = New-Object System.Collections.Generic.List[System.IO.FileSystemInfo] foreach ($item in $sourceItems) { switch ($item.Name) { 'github-actions' { if ($includePlatformGitHub) { [void]$itemsToCopy.Add($item) } } 'azure-devops' { if ($includePlatformAdo) { [void]$itemsToCopy.Add($item) } } default { # README.md, .itsm/, and anything else added later: always copy [void]$itemsToCopy.Add($item) } } } if ($itemsToCopy.Count -eq 0) { Write-Warning "Copy-AzureLocalPipelineExample: nothing to copy for -Platform '$Platform'." return } # ------------------------------------------------------------------ # 4. Pre-flight check: refuse to overwrite an existing populated target # unless -Force was supplied. This prevents accidental clobber of an # in-progress fork of the samples. # ------------------------------------------------------------------ if (Test-Path -LiteralPath $targetRoot) { $existing = @(Get-ChildItem -LiteralPath $targetRoot -Force -ErrorAction SilentlyContinue) if ($existing.Count -gt 0 -and -not $Force.IsPresent) { throw "Copy-AzureLocalPipelineExample: target folder '$targetRoot' already exists and is not empty. Re-run with -Force to overwrite." } } else { if ($PSCmdlet.ShouldProcess($targetRoot, 'Create pipeline-examples folder')) { $null = New-Item -ItemType Directory -Path $targetRoot -Force -ErrorAction Stop } } # ------------------------------------------------------------------ # 5. Perform the copy. One ShouldProcess gate at the operation level # rather than per file - the per-platform filter already limits # scope, and per-file -Confirm would be unusably chatty. # ------------------------------------------------------------------ $copyDescription = "Copy {0} item(s) from '{1}' to '{2}' (Platform='{3}', Flatten={4})" -f ` $itemsToCopy.Count, $sourceRoot, $targetRoot, $Platform, $Flatten.IsPresent if (-not $PSCmdlet.ShouldProcess($targetRoot, $copyDescription)) { return } foreach ($item in $itemsToCopy) { $destItem = Join-Path -Path $targetRoot -ChildPath $item.Name Copy-Item -LiteralPath $item.FullName -Destination $destItem -Recurse -Force:$Force.IsPresent -ErrorAction Stop } Write-Verbose "Copied $($itemsToCopy.Count) item(s) from '$sourceRoot' to '$targetRoot'." # ------------------------------------------------------------------ # 6. Friendly "what now" summary so the user does not have to open # the README first to know what they just copied. Uses Write-Host # (intentional - this is operator-facing UI text, not pipeline # output; per Microsoft guidance Write-Host is appropriate for # interactive cmdlet output that should not pollute the pipeline). # ------------------------------------------------------------------ $readmePath = Join-Path -Path $targetRoot -ChildPath 'README.md' $hasReadme = Test-Path -LiteralPath $readmePath $copiedFileCount = (Get-ChildItem -LiteralPath $targetRoot -Recurse -File -ErrorAction SilentlyContinue).Count Write-Host "" Write-Host "Copy-AzureLocalPipelineExample - copy complete" -ForegroundColor Green Write-Host (" Source : {0}" -f $sourceRoot) Write-Host (" Destination : {0}" -f $targetRoot) Write-Host (" Platform : {0}" -f $Platform) Write-Host (" Files copied: {0}" -f $copiedFileCount) Write-Host "" Write-Host "Next steps:" -ForegroundColor Cyan if ($hasReadme) { Write-Host (" 1. Open the step-by-step setup guide:") Write-Host (" {0}" -f $readmePath) -ForegroundColor Yellow } else { Write-Host (" 1. Refer to the module's online README for the step-by-step setup guide:") Write-Host (" https://github.com/NeilBird/Azure-Local/blob/main/AzLocal.UpdateManagement/Automation-Pipeline-Examples/README.md") -ForegroundColor Yellow } switch ($Platform) { 'GitHub' { Write-Host " 2. Drop the YAML files under '$targetRoot\github-actions\' into your repo's '.github/workflows/' folder." } 'AzureDevOps' { Write-Host " 2. Import the YAML files under '$targetRoot\azure-devops\' into a new Azure DevOps Pipeline." } default { Write-Host " 2. Pick the platform you use and move the YAML into your repo:" Write-Host " - GitHub Actions : copy '$targetRoot\github-actions\*.yml' to '.github/workflows/'" Write-Host " - Azure DevOps : import '$targetRoot\azure-devops\*.yml' into a new Pipeline" } } Write-Host " 3. Wire up authentication (OIDC / Workload Identity / Managed Identity / SP) - see section 3 of the README." Write-Host " 4. Optional: enable the ITSM connector by setting 'raise_itsm_ticket=true' (setup in ITSM/README.md)." Write-Host "" if ($PassThru.IsPresent) { return Get-Item -LiteralPath $targetRoot } } |