Sampler.PowerShellUniversalTasks.psm1
|
#Region './prefix.ps1' -1 <# Defines aliases used by InvokeBuild task discovery. These aliases point to task files shipped in the built module's Tasks directory and do not depend on functions declared later in the merged root module. #> $taskPath = Join-Path -Path $PSScriptRoot -ChildPath 'Tasks' $taskPath = Join-Path -Path $taskPath -ChildPath 'Publish.PowerShellUniversal.build.ps1' Set-Alias -Name 'Task.Publish_PowerShellUniversal' -Value $taskPath #EndRegion './prefix.ps1' 10 #Region './Private/Copy-UniversalRepositoryModule.ps1' -1 function Copy-UniversalRepositoryModule { <# .SYNOPSIS Copies a module and its dependencies into a PSU repository layout. .DESCRIPTION Uses a queue to copy a module and each transitive RequiredModules dependency into versioned folders below the repository Modules directory. First-discovery order is preserved, and a selected module is replaced only when a later requirement resolves to a higher version. .PARAMETER Module Module information for the module that is copied. .PARAMETER ModulesDestinationPath Destination Modules directory in the staged repository. .PARAMETER Visited Hashtable used to prevent duplicate copies and dependency cycles. .EXAMPLE Copy-UniversalRepositoryModule -Module $module -ModulesDestinationPath $path -Visited @{} #> [CmdletBinding()] [OutputType([System.Void])] param ( [Parameter(Mandatory = $true)] [System.Management.Automation.PSModuleInfo] $Module, [Parameter(Mandatory = $true)] [System.String] $ModulesDestinationPath, [Parameter(Mandatory = $true)] [System.Collections.Hashtable] $Visited ) $moduleQueue = [System.Collections.Queue]::new() $selectedModules = @{ } $moduleDiscoveryOrder = [System.Collections.ArrayList]::new() $inspectedModuleVersions = @{ } $moduleQueue.Enqueue($Module) while ($moduleQueue.Count -gt 0) { $currentModule = [System.Management.Automation.PSModuleInfo] $moduleQueue.Dequeue() $moduleVersionKey = '{0}|{1}' -f $currentModule.Name, $currentModule.Version if ($inspectedModuleVersions.ContainsKey($moduleVersionKey)) { continue } $inspectedModuleVersions[$moduleVersionKey] = $true if (-not $selectedModules.ContainsKey($currentModule.Name)) { $selectedModules[$currentModule.Name] = $currentModule $null = $moduleDiscoveryOrder.Add($currentModule.Name) } elseif ($currentModule.Version -gt $selectedModules[$currentModule.Name].Version) { $selectedModules[$currentModule.Name] = $currentModule } $manifestPath = Join-Path -Path $currentModule.ModuleBase -ChildPath ('{0}.psd1' -f $currentModule.Name) if (-not (Test-Path -Path $manifestPath)) { continue } $moduleInfo = Get-SamplerModuleInfo -ModuleManifestPath $manifestPath foreach ($requiredModule in @($moduleInfo.RequiredModules)) { if (-not $requiredModule) { continue } $moduleSpecification = [Microsoft.PowerShell.Commands.ModuleSpecification] $requiredModule if ($selectedModules.ContainsKey($moduleSpecification.Name)) { $requiredVersion = $moduleSpecification.RequiredVersion if (-not $requiredVersion) { $requiredVersion = $moduleSpecification.Version } if (-not $requiredVersion -or $selectedModules[$moduleSpecification.Name].Version -ge $requiredVersion) { continue } } $resolvedModule = Get-Module -ListAvailable -FullyQualifiedName $moduleSpecification | Sort-Object -Property Version -Descending | Select-Object -First 1 if (-not $resolvedModule) { throw "Required module '$($moduleSpecification.Name)' for '$($currentModule.Name)' was not found on PSModulePath." } $moduleQueue.Enqueue($resolvedModule) } } foreach ($selectedModuleName in $moduleDiscoveryOrder) { $selectedModule = [System.Management.Automation.PSModuleInfo] $selectedModules[$selectedModuleName] $Visited[$selectedModule.Name] = $true $moduleDestination = Join-Path -Path $ModulesDestinationPath -ChildPath $selectedModule.Name $moduleDestination = Join-Path -Path $moduleDestination -ChildPath $selectedModule.Version.ToString() $null = New-Item -Path $moduleDestination -ItemType Directory -Force Copy-Item -Path (Join-Path -Path $selectedModule.ModuleBase -ChildPath '*') -Destination $moduleDestination -Recurse -Force } } #EndRegion './Private/Copy-UniversalRepositoryModule.ps1' 122 #Region './Private/New-UniversalAutomationRepositoryManifest.ps1' -1 function New-UniversalAutomationRepositoryManifest { <# .SYNOPSIS Creates the descriptor for a PowerShell Universal repository package. .DESCRIPTION Writes repository.psd1 with the module name and metadata required by PowerShell Universal when activating an offline automation repository. .PARAMETER Path Destination path for repository.psd1. .PARAMETER Module Built module information used to populate repository metadata. .PARAMETER ModuleVersion Module version written to the repository descriptor. .EXAMPLE New-UniversalAutomationRepositoryManifest -Path $path -Module $module -ModuleVersion $version #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.Void])] param ( [Parameter(Mandatory = $true)] [System.String] $Path, [Parameter(Mandatory = $true)] [System.Management.Automation.PSModuleInfo] $Module, [Parameter(Mandatory = $true)] [System.String] $ModuleVersion ) $manifestParameters = @{ Path = $Path RootModule = $Module.Name ModuleVersion = ($ModuleVersion -replace '-.*$', '') Guid = (New-Guid) Author = $Module.Author CompanyName = $Module.CompanyName Copyright = $Module.Copyright Description = $Module.Description FunctionsToExport = '*' CmdletsToExport = '*' VariablesToExport = '*' AliasesToExport = '*' } if ($PSCmdlet.ShouldProcess($Path, 'Create PowerShell Universal repository manifest')) { New-ModuleManifest @manifestParameters } } #EndRegion './Private/New-UniversalAutomationRepositoryManifest.ps1' 60 #Region './Public/Get-UniversalDeploymentError.ps1' -1 function Get-UniversalDeploymentError { <# .SYNOPSIS Gets PowerShell Universal deployment error notifications. .DESCRIPTION Queries recent PowerShell Universal notifications and returns deployment-related errors created on or after a specified time. .PARAMETER ServerUrl Base URL of the PowerShell Universal server. .PARAMETER AppToken Application token used to authenticate the notification request. .PARAMETER Since Earliest notification creation time to include. .PARAMETER FilterText Optional text that must appear in the notification title or description. .EXAMPLE Get-UniversalDeploymentError -ServerUrl $url -AppToken $token -Since $startedAt #> [CmdletBinding()] [OutputType([System.Management.Automation.PSObject])] param ( [Parameter(Mandatory = $true)] [System.String] $ServerUrl, [Parameter(Mandatory = $true)] [System.String] $AppToken, [Parameter(Mandatory = $true)] [System.DateTimeOffset] $Since, [Parameter()] [System.String] $FilterText ) $requestParameters = @{ Uri = '{0}/api/v1/notification/last' -f $ServerUrl.TrimEnd('/') Headers = @{ Authorization = 'Bearer {0}' -f $AppToken } Method = 'Get' } $response = Invoke-RestMethod @requestParameters $notifications = if ($null -ne $response.page) { @($response.page) } elseif ($response -is [System.Array]) { @($response) } elseif ($null -ne $response) { @($response) } else { @() } foreach ($notification in $notifications) { $createdTime = [System.DateTimeOffset]::MinValue if (-not [System.DateTimeOffset]::TryParse( [System.String] $notification.CreatedTime, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal, [ref] $createdTime )) { continue } if ($createdTime -lt $Since) { continue } $title = [System.String] $notification.Title $description = [System.String] $notification.Description $level = [System.String] $notification.Level $notificationText = '{0} {1}' -f $title, $description $isDeploymentNotification = $notificationText -match '(?i)deployment|module|configuration' $isErrorNotification = $level -match '(?i)^error$' -or $notificationText -match '(?i)\bfailed\b|\berror\b|invalid configuration' if (-not $isDeploymentNotification -or -not $isErrorNotification) { continue } if (-not [System.String]::IsNullOrWhiteSpace($FilterText) -and $notificationText -notmatch [System.Text.RegularExpressions.Regex]::Escape($FilterText)) { continue } $notification } } #EndRegion './Public/Get-UniversalDeploymentError.ps1' 112 #Region './Public/Install-UniversalModuleFromRepository.ps1' -1 function Install-UniversalModuleFromRepository { <# .SYNOPSIS Installs a module on PowerShell Universal from a resource repository. .DESCRIPTION Reconciles the requested PowerShell resource repository, triggers a synchronous module deployment, and optionally removes the repository. .PARAMETER ServerUrl Base URL of the PowerShell Universal server. .PARAMETER AppToken Application token used to authenticate API requests. .PARAMETER ModuleName Name of the module to install. .PARAMETER ModuleVersion Version of the module to install. .PARAMETER RepositoryName Name of the PowerShell resource repository on the server. .PARAMETER RepositoryUrl URL or local path used by the PowerShell resource repository. .PARAMETER RepositoryAutoRemove Whether to remove the repository after the deployment attempt. .EXAMPLE Install-UniversalModuleFromRepository @installParameters #> [CmdletBinding()] [OutputType([System.Void])] param ( [Parameter(Mandatory = $true)] [System.String] $ServerUrl, [Parameter(Mandatory = $true)] [System.String] $AppToken, [Parameter(Mandatory = $true)] [System.String] $ModuleName, [Parameter(Mandatory = $true)] [System.String] $ModuleVersion, [Parameter(Mandatory = $true)] [System.String] $RepositoryName, [Parameter(Mandatory = $true)] [System.String] $RepositoryUrl, [Parameter()] [System.Boolean] $RepositoryAutoRemove = $true ) $headers = @{ Authorization = 'Bearer {0}' -f $AppToken Accept = 'application/json' } $repositoryEndpoint = '{0}/api/v1/resourceRepository' -f $ServerUrl $repositories = @(Invoke-RestMethod -Uri $repositoryEndpoint -Headers $headers -Method Get) $existingRepository = $repositories | Where-Object -FilterScript { $_.name -eq $RepositoryName } | Select-Object -First 1 if ($existingRepository) { $sameUrl = [System.String]::Equals( [System.String] $existingRepository.url, $RepositoryUrl, [System.StringComparison]::OrdinalIgnoreCase ) if ((-not $sameUrl -or -not [System.Boolean] $existingRepository.trusted) -and $RepositoryAutoRemove) { $deleteUri = '{0}/{1}' -f $repositoryEndpoint, [System.Uri]::EscapeDataString($RepositoryName) $null = Invoke-RestMethod -Uri $deleteUri -Headers $headers -Method Delete $existingRepository = $null } } if (-not $existingRepository) { $body = @{ name = $RepositoryName url = $RepositoryUrl trusted = $true id = 0 } | ConvertTo-Json -Depth 5 $null = Invoke-RestMethod -Uri $repositoryEndpoint -Headers $headers -Method Post -Body $body -ContentType 'application/json; charset=utf-8' } try { $deployUri = '{0}/api/v1/deployment/module/{1}/{2}?repository={3}&synchronous=true' -f @( $ServerUrl $ModuleName $ModuleVersion [System.Uri]::EscapeDataString($RepositoryName) ) $null = Invoke-RestMethod -Uri $deployUri -Headers $headers -Method Put -ContentType 'application/octet-stream; charset=utf-8' } finally { if ($RepositoryAutoRemove) { $deleteUri = '{0}/{1}' -f $repositoryEndpoint, [System.Uri]::EscapeDataString($RepositoryName) try { $null = Invoke-RestMethod -Uri $deleteUri -Headers $headers -Method Delete } catch { Write-Warning -Message ("Failed to remove PowerShell resource repository '{0}': {1}" -f $RepositoryName, $_.Exception.Message) } } } } #EndRegion './Public/Install-UniversalModuleFromRepository.ps1' 132 #Region './Public/Invoke-UniversalDeploymentUpload.ps1' -1 function Invoke-UniversalDeploymentUpload { <# .SYNOPSIS Uploads a deployment package to PowerShell Universal. .DESCRIPTION Sends a module package or automation repository archive to a PowerShell Universal deployment endpoint using bearer authentication. .PARAMETER Uri Complete PowerShell Universal deployment endpoint URI. .PARAMETER AppToken Application token used to authenticate the deployment request. .PARAMETER Path Path to the package that is uploaded as the request body. .EXAMPLE Invoke-UniversalDeploymentUpload -Uri $uri -AppToken $token -Path $packagePath #> [CmdletBinding()] [OutputType([System.Object])] param ( [Parameter(Mandatory = $true)] [System.String] $Uri, [Parameter(Mandatory = $true)] [System.String] $AppToken, [Parameter(Mandatory = $true)] [System.String] $Path ) if (-not (Test-Path -Path $Path)) { throw "Deployment package '$Path' was not found." } $requestParameters = @{ Uri = $Uri Headers = @{ Authorization = 'Bearer {0}' -f $AppToken } InFile = $Path Method = 'Put' ContentType = 'application/octet-stream; charset=utf-8' } Invoke-RestMethod @requestParameters } #EndRegion './Public/Invoke-UniversalDeploymentUpload.ps1' 57 #Region './Public/New-UniversalAutomationRepositoryPackage.ps1' -1 function New-UniversalAutomationRepositoryPackage { <# .SYNOPSIS Creates an offline PowerShell Universal automation repository package. .DESCRIPTION Stages the built module and its required modules using the PowerShell Universal repository layout, creates repository.psd1, and compresses it. .PARAMETER BuiltModuleManifest Path to the manifest of the built project module. .PARAMETER OutputDirectory Directory where the staging folder and zip package are created. .PARAMETER ModuleVersion Built module version used for the repository manifest and default zip name. .PARAMETER StagingDirectoryName Name of the temporary repository staging directory. .PARAMETER ZipName Optional explicit name for the generated zip package. .EXAMPLE New-UniversalAutomationRepositoryPackage @packageParameters -Confirm:$false #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([System.IO.FileInfo])] param ( [Parameter(Mandatory = $true)] [System.String] $BuiltModuleManifest, [Parameter(Mandatory = $true)] [System.String] $OutputDirectory, [Parameter(Mandatory = $true)] [System.String] $ModuleVersion, [Parameter()] [System.String] $StagingDirectoryName = 'PsuRepository', [Parameter()] [System.String] $ZipName ) if (-not (Test-Path -Path $BuiltModuleManifest)) { throw "Built module manifest '$BuiltModuleManifest' was not found." } $module = Test-ModuleManifest -Path $BuiltModuleManifest -ErrorAction Stop $stagingDirectory = Join-Path -Path $OutputDirectory -ChildPath $StagingDirectoryName $modulesDestination = Join-Path -Path $stagingDirectory -ChildPath 'Modules' $resolvedZipName = if ([System.String]::IsNullOrWhiteSpace($ZipName)) { '{0}.{1}.zip' -f $module.Name, $ModuleVersion } else { $ZipName } $zipPath = Join-Path -Path $OutputDirectory -ChildPath $resolvedZipName if ($PSCmdlet.ShouldProcess($zipPath, 'Create PowerShell Universal automation repository package')) { if (Test-Path -Path $stagingDirectory) { Remove-Item -Path $stagingDirectory -Recurse -Force } $null = New-Item -Path $modulesDestination -ItemType Directory -Force Copy-UniversalRepositoryModule -Module $module -ModulesDestinationPath $modulesDestination -Visited @{ } $repositoryManifestPath = Join-Path -Path $stagingDirectory -ChildPath 'repository.psd1' New-UniversalAutomationRepositoryManifest -Path $repositoryManifestPath -Module $module -ModuleVersion $ModuleVersion -Confirm:$false if (Test-Path -Path $zipPath) { Remove-Item -Path $zipPath -Force } Compress-Archive -Path (Join-Path -Path $stagingDirectory -ChildPath '*') -DestinationPath $zipPath -Force Get-Item -Path $zipPath } } #EndRegion './Public/New-UniversalAutomationRepositoryPackage.ps1' 94 #Region './Public/Resolve-UniversalServerConfiguration.ps1' -1 function Resolve-UniversalServerConfiguration { <# .SYNOPSIS Resolves PowerShell Universal deployment settings for build tasks. .DESCRIPTION Combines explicit task parameters, the UniversalServer section from build.yaml, and build environment variables into one validated object. .PARAMETER BuildInfo Build configuration data loaded by Sampler from build.yaml. .PARAMETER ServerUrl Explicit PowerShell Universal server URL. .PARAMETER AppToken Explicit PowerShell Universal application token. .PARAMETER RepositoryName Explicit PowerShell resource repository name. .PARAMETER RepositoryUrl Explicit PowerShell resource repository URL or local path. .PARAMETER RepositoryAutoRemove Whether the resource repository is removed after deployment. .PARAMETER Unpinned Whether the uploaded automation repository is deployed unpinned. .PARAMETER RepositoryAutoRemoveWasBound Indicates that RepositoryAutoRemove was supplied explicitly. .PARAMETER UnpinnedWasBound Indicates that Unpinned was supplied explicitly. .PARAMETER RequireRepository Requires and resolves resource repository settings. .EXAMPLE Resolve-UniversalServerConfiguration -BuildInfo $BuildInfo #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter()] [System.Collections.Hashtable] $BuildInfo = @{ }, [Parameter()] [System.String] $ServerUrl, [Parameter()] [System.String] $AppToken, [Parameter()] [System.String] $RepositoryName, [Parameter()] [System.String] $RepositoryUrl, [Parameter()] [System.Boolean] $RepositoryAutoRemove = $true, [Parameter()] [System.Boolean] $Unpinned = $true, [Parameter()] [System.Boolean] $RepositoryAutoRemoveWasBound = $false, [Parameter()] [System.Boolean] $UnpinnedWasBound = $false, [Parameter()] [System.Management.Automation.SwitchParameter] $RequireRepository ) $server = $BuildInfo.UniversalServer if ([System.String]::IsNullOrWhiteSpace($ServerUrl)) { $ServerUrl = $server.UniversalServerUrl } if ([System.String]::IsNullOrWhiteSpace($ServerUrl)) { $ServerUrl = $env:UniversalServerUrl } if ([System.String]::IsNullOrWhiteSpace($AppToken)) { $AppToken = $server.UniversalServerAppToken } if ([System.String]::IsNullOrWhiteSpace($AppToken)) { $AppToken = $env:UniversalServerAppToken } if ([System.String]::IsNullOrWhiteSpace($ServerUrl)) { throw 'UniversalServerUrl is required. Set UniversalServer.UniversalServerUrl in build.yaml or provide the build parameter.' } if ([System.String]::IsNullOrWhiteSpace($AppToken)) { throw 'UniversalServerAppToken is required. Provide it through the build environment or a local secrets file.' } if ([System.String]::IsNullOrWhiteSpace($RepositoryName)) { $RepositoryName = $server.UniversalPSResourceRepositoryName } if ([System.String]::IsNullOrWhiteSpace($RepositoryUrl)) { $RepositoryUrl = $server.UniversalPSResourceRepositoryUrl } if ($RequireRepository -and [System.String]::IsNullOrWhiteSpace($RepositoryName)) { $RepositoryName = 'output' } if ($RequireRepository -and [System.String]::IsNullOrWhiteSpace($RepositoryUrl)) { $RepositoryUrl = './output/' } if (-not $RepositoryAutoRemoveWasBound -and $null -ne $server.UniversalPSResourceRepositoryAutoRemove) { $RepositoryAutoRemove = [System.Convert]::ToBoolean($server.UniversalPSResourceRepositoryAutoRemove) } if (-not $UnpinnedWasBound -and $null -ne $server.UniversalUnpinned) { $Unpinned = [System.Convert]::ToBoolean($server.UniversalUnpinned) } [PSCustomObject]@{ ServerUrl = $ServerUrl.TrimEnd('/') AppToken = $AppToken RepositoryName = $RepositoryName RepositoryUrl = $RepositoryUrl RepositoryAutoRemove = $RepositoryAutoRemove Unpinned = $Unpinned } } #EndRegion './Public/Resolve-UniversalServerConfiguration.ps1' 160 |