From 44e018499ea32179dfb7d0607c2070c05489bda9 Mon Sep 17 00:00:00 2001 From: James Garriss <52328727+james-garriss@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:22:15 -0400 Subject: [PATCH] Fix publish package errors (#1042) * inject write-error * comment out publish * show manifest error * remove fail silently * debug path version * fix id typo * fixed list * fix needs * fix release prep again * write prereleasetag * get types * fix typo * fix typos * set tags as array * reset tags * debug w string * remove array * gc manifest * get childitem * write module path * Debug manifest * debug more * typecast to string * debug string concat * display hashtable * more debug details * tweak debugging * update install w prerelease * commenting * install required version * add debug * add debug * add version * use 1 if * hard code * install only * force it * remove install * add debug statements * see params * fix parameter set * fix comment typo * use binding * add comments * fix @ * remove cmdlet * uncomm param set * remove param sets * split into 2 * fix push paths * add env back * clean debug statement * fix pipeline issues * add output test * use write-output * clean up minor * back to previous version * use output * more output * use debug * more output * fix ps lint * test returning false * return to old error * hardcode params * fix step name * improve debug statements * back to write host * debug prerelease * changed info to host * switch tag version * write manifest out * update version * fix temp print * bump to 8 * add import back * require version * bump to 10 * bump vers * without tag * bump tag * use find mod * fix unit test * bump tag * trivial change to trigger tests * hide error * v 03 * fix pipeline * return false * add write error * remove details * be silent * be false * cleanup * unhardcode * Exclude Write-Host locally * commented out push trigger * comment out push trigger * update description --- .github/workflows/publish_package.yaml | 0 .../workflows/publish_private_package.yaml | 99 +++++++ .github/workflows/publish_public_package.yaml | 106 +++++++ .github/workflows/run_pipeline.yaml | 62 +---- .github/workflows/run_publish_package.yaml | 203 -------------- .../PowerShell/Utils/DeployUtils.Tests.ps1 | 1 + utils/DeployUtils.ps1 | 261 +++++++++++------- 7 files changed, 366 insertions(+), 366 deletions(-) delete mode 100644 .github/workflows/publish_package.yaml create mode 100644 .github/workflows/publish_private_package.yaml create mode 100644 .github/workflows/publish_public_package.yaml delete mode 100644 .github/workflows/run_publish_package.yaml diff --git a/.github/workflows/publish_package.yaml b/.github/workflows/publish_package.yaml deleted file mode 100644 index e69de29bb..000000000 diff --git a/.github/workflows/publish_private_package.yaml b/.github/workflows/publish_private_package.yaml new file mode 100644 index 000000000..1cd304fb6 --- /dev/null +++ b/.github/workflows/publish_private_package.yaml @@ -0,0 +1,99 @@ +# Purpose: Publish nightly to a a private gallery just to make +# sure that the code can be published. This is like a +# smoke test. + +name: Publish Private Package + +on: + schedule: + - cron: "23 0 * * *" # Execute each day at 00:23 UTC + workflow_dispatch: + # for testing + # push: + # paths: + # - ".github/workflows/publish_private_package.yaml" + # - "utils/DeployUtils.ps1" + +env: + GalleryName: PrivateScubaGearGallery + +jobs: + publish: + name: Publish to Private Gallery + runs-on: windows-latest + environment: Development + permissions: + id-token: write + contents: write + defaults: + run: + shell: powershell + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: repo + - name: Install Azure Signing Tool + run: | + dotnet --version + dotnet tool install --global AzureSignTool --version 4.0.1 + # OIDC Login to Azure Public Cloud with AzPowershell (enableAzPSSession true) + - name: Login to Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + - name: Create Private Gallery + run: | + cd repo + . utils/DeployUtils.ps1 + New-PrivateGallery -GalleryName $env:GalleryName -Trusted + - name: Get Key Vault Info + id: key-vault-info + env: + KEY_VAULT_INFO: ${{ secrets.SCUBA_KEY_VAULT_PROD}} + run: | + $KeyVaultInfo = ${env:KEY_VAULT_INFO} | ConvertFrom-Json + echo "KeyVaultUrl=$($KeyVaultInfo.KeyVault.URL)" >> $env:GITHUB_OUTPUT + echo "KeyVaultCertificateName=$($KeyVaultInfo.KeyVault.CertificateName)" >> $env:GITHUB_OUTPUT + - name: Sign and Publish Module + uses: azure/powershell@v1 + with: + inlineScript: | + # Source the deploy utilities so the functions in it can be called. + . repo/utils/DeployUtils.ps1 + # Remove non-release files + Remove-Item -Recurse -Force repo -Include .git* + # Setup the parameters + $Parameters = @{ + AzureKeyVaultUrl = '${{ steps.key-vault-info.outputs.KeyVaultUrl }}' + CertificateName = '${{ steps.key-vault-info.outputs.KeyVaultCertificateName }}' + ModulePath = 'repo/PowerShell/ScubaGear' + GalleryName = $env:GalleryName + } + # This publishes to a private gallery. + Publish-ScubaGearModule @Parameters + azPSVersion: "latest" + - name: Test Module Publish + run: | + Get-Location + $TestContainers = @() + $TestContainers += New-PesterContainer -Path "repo/Testing/Functional/BuildTest" -Data @{ } + $PesterConfig = @{ + Run = @{ + Container = $TestContainers + } + Output = @{ + Verbosity = 'Detailed' + } + } + $Config = New-PesterConfiguration -Hashtable $PesterConfig + Invoke-Pester -Configuration $Config + # This is a manual test that writes the version to the console. + - name: Print Scuba Version + run: | + Install-Module -Name ScubaGear -SkipPublisherCheck + # Import-Module -Name ScubaGear + Invoke-SCuBA -Version diff --git a/.github/workflows/publish_public_package.yaml b/.github/workflows/publish_public_package.yaml new file mode 100644 index 000000000..41d3805cc --- /dev/null +++ b/.github/workflows/publish_public_package.yaml @@ -0,0 +1,106 @@ +# Purpose: Publish on demand to the real gallery (PSGallery). + +name: Publish Public Package + +on: + workflow_dispatch: + inputs: + OverrideModuleVersion: + description: "Override the version of the release. Restricted to SemVer 1.0 - 3 segments" + required: false + type: string + IsPrerelease: + description: "Is this a prerelease" + required: false + type: boolean + default: false + PrereleaseTag: + description: "The prerelease tag: (-)?[0-9A-Za-z]+ (e.g. -alpha1, -rc2, b1234)" + required: false + type: string + # for testing + # push: + # paths: + # - ".github/workflows/publish_public_package.yaml" + # - "utils/DeployUtils.ps1" + +jobs: + publish: + name: Publish to PSGallery + runs-on: windows-latest + environment: Development + permissions: + id-token: write + contents: write + defaults: + run: + shell: powershell + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: repo + - name: Install Azure Signing Tool + run: | + dotnet --version + dotnet tool install --global AzureSignTool --version 4.0.1 + # OIDC Login to Azure Public Cloud with AzPowershell (enableAzPSSession true) + - name: Login to Azure + uses: azure/login@v1 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + enable-AzPSSession: true + - name: Get Key Vault info + id: key-vault-info + env: + KEY_VAULT_INFO: ${{ secrets.SCUBA_KEY_VAULT_PROD}} + run: | + $KeyVaultInfo = ${env:KEY_VAULT_INFO} | ConvertFrom-Json + echo "KeyVaultUrl=$($KeyVaultInfo.KeyVault.URL)" >> $env:GITHUB_OUTPUT + echo "KeyVaultCertificateName=$($KeyVaultInfo.KeyVault.CertificateName)" >> $env:GITHUB_OUTPUT + - name: Sign and Publish Module + run: | + # Source the deploy utilities so the functions in it can be called. + . repo/utils/DeployUtils.ps1 + # Remove non-release files + Remove-Item -Recurse -Force repo -Include .git* + # Extract the API key used to publish to PSGallery + $ApiKey = az keyvault secret show --id '${{ steps.key-vault-info.outputs.KeyVaultUrl }}/secrets/ScubaGear-PSGAllery-API-Key' --query value -o tsv + if (-Not $ApiKey) + { + Write-Error "Failed to retrieve API key" + } + # Setup the parameters + $Parameters = @{ + AzureKeyVaultUrl = '${{ steps.key-vault-info.outputs.KeyVaultUrl }}' + CertificateName = '${{ steps.key-vault-info.outputs.KeyVaultCertificateName }}' + ModulePath = 'repo/PowerShell/ScubaGear' + GalleryName = 'PSGallery' + NuGetApiKey = $ApiKey + } + # if ('true' -eq '${{ inputs.IsPrerelease }}') + # { + # $Parameters.Add('PrereleaseTag', '${{ inputs.PrereleaseTag }}') + # } + # if (-Not [string]::IsNullOrEmpty('${{ inputs.OverrideModuleVersion }}')) + # { + # $Parameters.Add('OverrideModuleVersion', '${{ inputs.OverrideModuleVersion }}') + # } + # This publishes to PSGallery. + Publish-ScubaGearModule @Parameters + # This is a manual test that simply writes the version to the console + - name: Print Scuba Version + run: | + if ('true' -eq '${{ inputs.IsPrerelease }}') + { + $Version = '${{ inputs.OverrideModuleVersion }}' + '${{ inputs.PrereleaseTag }}' + Write-Host "Checking for prerelease with required version: " + $Version + Find-Module -Name ScubaGear -RequiredVersion $Version -AllowPrerelease + } + else + { + Write-Host "Installing latest version" + Find-Module -Name ScubaGear + } diff --git a/.github/workflows/run_pipeline.yaml b/.github/workflows/run_pipeline.yaml index 699eb8450..f0fe4c6b8 100644 --- a/.github/workflows/run_pipeline.yaml +++ b/.github/workflows/run_pipeline.yaml @@ -7,77 +7,25 @@ on: push: pull_request: workflow_dispatch: - inputs: - # When set to true, it will run every step in the pipeline, regardless of - # what files have changed. - doEverything: - description: "Run every workflow in the pipeline." - required: false - type: boolean - default: true jobs: - test-files: - name: Test for Changes - runs-on: ubuntu-latest - # This condition prevents duplicate runs. - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Check for Changes - uses: dorny/paths-filter@v3 - id: file-changes - with: - base: ${{ github.ref }} - filters: | - yaml-files: - - added|modified: "**.yml" - - added|modified: "**.yaml" - powershell-files: - - added|modified: "**.ps1" - - added|modified: "**.psm1" - - added|modified: "**.psd1" - - added|modified: "**.pssc" - - added|modified: "**.psrc" - - added|modified: "**.ps1xml" - - added|modified: "**.cdxml" - markdown-files: - - added|modified: "PowerShell/ScubaGear/baselines/*.md" - rego-files: - - added|modified: "**.rego" - outputs: - yaml-changes: ${{ steps.file-changes.outputs.yaml-files || inputs.doEverything }} - powershell-changes: ${{ steps.file-changes.outputs.powershell-files || inputs.doEverything }} - markdown-changes: ${{ steps.file-changes.outputs.markdown-files || inputs.doEverything }} - rego-changes: ${{ steps.file-changes.outputs.rego-files || inputs.doEverything }} lint-yaml: name: Lint - needs: - - test-files - if: needs.test-files.outputs.yaml-changes == 'true' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name uses: ./.github/workflows/lint_yaml.yaml lint-powershell: name: Lint - needs: - - test-files - if: needs.test-files.outputs.powershell-changes == 'true' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name uses: ./.github/workflows/lint_powershell.yaml syntax: name: Syntax - needs: - - test-files - if: needs.test-files.outputs.markdown-changes == 'true' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name uses: ./.github/workflows/syntax_check_markdown.yaml unit-powershell: name: Unit - needs: - - test-files - if: needs.test-files.outputs.powershell-changes == 'true' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name uses: ./.github/workflows/unit_test_powershell.yaml unit-opa: name: Unit - needs: - - test-files - if: needs.test-files.outputs.rego-changes == 'true' + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name uses: ./.github/workflows/unit_test_opa.yaml diff --git a/.github/workflows/run_publish_package.yaml b/.github/workflows/run_publish_package.yaml deleted file mode 100644 index de528cd2c..000000000 --- a/.github/workflows/run_publish_package.yaml +++ /dev/null @@ -1,203 +0,0 @@ -name: Publish Package - -on: - schedule: - - cron: "23 0 * * *" # Execute each day at 00:23 UTC - workflow_dispatch: - inputs: - OverrideModuleVersion: - description: "Override the version of the release. Restricted to SemVer 1.0 - 3 segments" - required: false - type: string - IsPrerelease: - description: "Is this a prerelease" - required: false - type: boolean - default: false - PrereleaseTag: - description: "The prerelease tag: [0-9A-Za-z]+" - required: false - type: string - push: - paths: - - ".github/workflows/run_publish_package.yaml" - - "utils/DeployUtils.ps1" - -env: - GalleryName: PrivateScubaGearGallery - ModuleName: ScubaGear - -jobs: - ReleasePrep: - runs-on: windows-latest - environment: Development - permissions: - id-token: write - contents: write - defaults: - run: - shell: powershell - outputs: - KeyVaultUrl: ${{ steps.key_vault_info.outputs.KeyVaultUrl}} - KeyVaultCertificateName: ${{ steps.key_vault_info.outputs.KeyVaultCertificateName}} - steps: - - name: Get Key Vault info - env: - KEY_VAULT_INFO: ${{ secrets.SCUBA_KEY_VAULT_PROD}} - run: | - $KeyVaultInfo = ${env:KEY_VAULT_INFO} | ConvertFrom-Json - echo "KeyVaultUrl=$($KeyVaultInfo.KeyVault.URL)" >> $env:GITHUB_OUTPUT - echo "KeyVaultCertificateName=$($KeyVaultInfo.KeyVault.CertificateName)" >> $env:GITHUB_OUTPUT - id: "key_vault_info" - - - Publish-Private-Package: - if: github.event.schedule == '23 0 * * *' - needs: [ReleasePrep] - runs-on: windows-latest - environment: Development - permissions: - id-token: write - contents: write - defaults: - run: - shell: powershell - steps: - - name: Install Azure Signing Tool - run: | - dotnet --version - dotnet tool install --global AzureSignTool --version 4.0.1 - - name: Checkout - uses: actions/checkout@v4 - with: - path: repo - - name: OIDC Login to Azure Public Cloud with AzPowershell (enableAzPSSession true) - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - enable-AzPSSession: true - - name: Create temporary private gallery - run: | - cd repo - . utils/DeployUtils.ps1 - New-PrivateGallery -GalleryName $env:GalleryName -Trusted - - name: Sign and publish module to private gallery - uses: azure/powershell@v1 - with: - inlineScript: | - . repo/utils/DeployUtils.ps1 - # Remove non-release files - Remove-Item -Recurse -Force repo -Include .git* - Publish-ScubaGearModule ` - -AzureKeyVaultUrl ${{ needs.ReleasePrep.outputs.KeyVaultUrl}} ` - -CertificateName ${{ needs.ReleasePrep.outputs.KeyVaultCertificateName}} ` - -ModulePath repo/PowerShell/ScubaGear ` - -GalleryName $env:GalleryName - azPSVersion: "latest" - - name: Test Module Publish - run: | - Get-Location - $TestContainers = @() - $TestContainers += New-PesterContainer -Path "repo/Testing/Functional/BuildTest" -Data @{ } - $PesterConfig = @{ - Run = @{ - Container = $TestContainers - } - Output = @{ - Verbosity = 'Detailed' - } - } - $Config = New-PesterConfiguration -Hashtable $PesterConfig - Invoke-Pester -Configuration $Config - - name: Check Scuba Version - run: | - Install-Module -Name ScubaGear -SkipPublisherCheck - Import-Module -Name ScubaGear - Invoke-SCuBA -Version - - name: Sign and publish prerelease module to private gallery - uses: azure/powershell@v1 - env: - KEY_VAULT_INFO: ${{ secrets.SCUBA_KEY_VAULT_DEV}} - with: - inlineScript: | - Write-Output "$(pwd)" - . repo/utils/DeployUtils.ps1 - $KeyVaultInfo = ${env:KEY_VAULT_INFO} | ConvertFrom-Json - # Remove non-release files - Remove-Item -Recurse -Force repo -Include .git* - Publish-ScubaGearModule ` - -AzureKeyVaultUrl $($KeyVaultInfo.KeyVault.URL) ` - -CertificateName $($KeyVaultInfo.KeyVault.CertificateName) ` - -ModulePath repo/PowerShell/ScubaGear ` - -GalleryName $env:GalleryName ` - -OverrideModuleVersion '9.9.9' ` - -Prerelease 'alpha' - azPSVersion: "latest" - - name: Check Scuba Version - run: | - $ScubaModules = Find-Module -Name ScubaGear -AllVersions -AllowPrerelease - ($ScubaModules).Version - if ($ScubaModules.Count -eq 2) { - Exit 0 - } - else { - Exit 1 - } - - Publish-To-PSGallery: - if: github.event_name == 'workflow_dispatch' - needs: [ReleasePrep] - runs-on: windows-latest - environment: Development - permissions: - id-token: write - contents: write - defaults: - run: - shell: powershell - steps: - - name: Install Azure Signing Tool - run: | - dotnet --version - dotnet tool install --global AzureSignTool --version 4.0.1 - - name: Checkout - uses: actions/checkout@v4 - with: - path: repo - - name: OIDC Login to Azure Public Cloud with AzPowershell (enableAzPSSession true) - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - enable-AzPSSession: true - - name: Sign and publish module to PSGallery - run: | - . repo/utils/DeployUtils.ps1 - # Remove non-release files - Remove-Item -Recurse -Force repo -Include .git* - $ApiKey = az keyvault secret show --id '${{ needs.ReleasePrep.outputs.KeyVaultUrl }}/secrets/ScubaGear-PSGAllery-API-Key' --query value -o tsv - if (-Not $ApiKey){ - Write-Error "Failer to retrieve API key" - } - $PublishSplat = @{ - AzureKeyVaultUrl = '${{ needs.ReleasePrep.outputs.KeyVaultUrl }}' - CertificateName = '${{ needs.ReleasePrep.outputs.KeyVaultCertificateName }}' - ModulePath = 'repo/PowerShell/ScubaGear' - GalleryName = 'PSGallery' - NuGetApiKey = $ApiKey - } - if ('true' -eq '${{ inputs.IsPrerelease }}'){ - $PublishSplat.Add('PrereleaseTag', '${{ inputs.PrereleaseTag }}') - } - if (-Not [string]::IsNullOrEmpty('${{ inputs.OverrideModuleVersion }}')){ - $PublishSplat.Add('OverrideModuleVersion', '${{ inputs.OverrideModuleVersion }}') - } - Publish-ScubaGearModule @PublishSplat - - name: Check Scuba Version - run: | - Install-Module -Name ScubaGear - Import-Module -Name ScubaGear - Invoke-SCuBA -Version diff --git a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Utils/DeployUtils.Tests.ps1 b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Utils/DeployUtils.Tests.ps1 index f364f0517..509539731 100644 --- a/PowerShell/ScubaGear/Testing/Unit/PowerShell/Utils/DeployUtils.Tests.ps1 +++ b/PowerShell/ScubaGear/Testing/Unit/PowerShell/Utils/DeployUtils.Tests.ps1 @@ -107,6 +107,7 @@ Context "Unit Test for ConfigureScubaGearModule" { } Copy-Item -Recurse -Path $ModulePath -Destination $env:TEMP -Force $ManifestPath = Join-Path -Path $env:TEMP -ChildPath 'ScubaGear\ScubaGear.psd1' -Resolve + # 99.1 is an intentionally invalid number Get-Content "$ModulePath\ScubaGear.psd1" | ForEach-Object { $_ -replace '5.1', '99.1' } | Set-Content $ManifestPath Mock -CommandName Write-Error {} } diff --git a/utils/DeployUtils.ps1 b/utils/DeployUtils.ps1 index 220dc0726..410711985 100644 --- a/utils/DeployUtils.ps1 +++ b/utils/DeployUtils.ps1 @@ -16,11 +16,11 @@ function New-PrivateGallery { #> [CmdletBinding(SupportsShouldProcess)] param ( - [Parameter(Mandatory=$false)] - [ValidateScript({Test-Path -Path $_ -IsValid})] + [Parameter(Mandatory = $false)] + [ValidateScript({ Test-Path -Path $_ -IsValid })] [string] $GalleryRootPath = $env:TEMP, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $GalleryName = 'PrivateScubaGearGallery', @@ -29,21 +29,21 @@ function New-PrivateGallery { ) $GalleryPath = Join-Path -Path $GalleryRootPath -ChildPath $GalleryName - if (Test-Path $GalleryPath){ + if (Test-Path $GalleryPath) { Write-Debug "Removing private gallery at $GalleryPath" Remove-Item -Recursive -Force $GalleryPath } New-Item -Path $GalleryPath -ItemType Directory - if (-not (IsRegistered -RepoName $GalleryName)){ + if (-not (IsRegistered -RepoName $GalleryName)) { Write-Debug "Attempting to register $GalleryName repository" $Splat = @{ - Name = $GalleryName - SourceLocation = $GalleryPath - PublishLocation = $GalleryPath - InstallationPolicy = if ($Trusted) {'Trusted'} else {'Untrusted'} + Name = $GalleryName + SourceLocation = $GalleryPath + PublishLocation = $GalleryPath + InstallationPolicy = if ($Trusted) { 'Trusted' } else { 'Untrusted' } } Register-PSRepository @Splat @@ -53,240 +53,290 @@ function New-PrivateGallery { } } -function Publish-ScubaGearModule{ +function Publish-ScubaGearModule { <# .Description Publish ScubaGear module to private package repository + .Parameter AzureKeyVaultUrl + The URL of the key vault with the code signing certificate + .Parameter CertificateName + The name of the code signing certificate .Parameter ModulePath Path to module root directory .Parameter GalleryName Name of the private package repository (i.e., gallery) .Parameter OverrideModuleVersion Optional module version. If provided it will use as module version. Otherwise, the current version from the manifest with a revision number is added instead. + .Parameter PrereleaseTag + The identifier that will be used in place of a version to identify the module in the gallery + .Parameter NuGetApiKey + Specifies the API key that you want to use to publish a module to the online gallery. #> param ( - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$true)] - [ValidateScript({[uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https'})] + [Parameter(Mandatory = $true)] + [ValidateScript({ [uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https' })] [System.Uri] $AzureKeyVaultUrl, - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $CertificateName, - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path -Path $_ -PathType Container})] + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] [string] $ModulePath, - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $GalleryName = 'PrivateScubaGearGallery', - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $OverrideModuleVersion = "", - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(ParameterSetName = 'PrivateGallery')] - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $PrereleaseTag = "", - [Parameter(ParameterSetName = 'PublicGallery')] - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $NuGetApiKey ) + Write-Output "Publishing ScubaGear module..." $ModuleBuildPath = Build-ScubaModule -ModulePath $ModulePath -OverrideModuleVersion $OverrideModuleVersion -PrereleaseTag $PrereleaseTag - if (SignScubaGearModule -AzureKeyVaultUrl $AzureKeyVaultUrl -CertificateName $CertificateName -ModulePath $ModuleBuildPath){ - $PublishSplat = @{ - Path = $ModuleBuildPath + Write-Output "The module build path is " + Write-Output $ModuleBuildPath + + if (SignScubaGearModule -AzureKeyVaultUrl $AzureKeyVaultUrl -CertificateName $CertificateName -ModulePath $ModuleBuildPath) { + $Parameters = @{ + Path = $ModuleBuildPath Repository = $GalleryName } - - if ($GalleryName -eq 'PSGallery'){ - $PublishSplat.Add('NuGetApiKey', $NuGetApiKey) + if ($GalleryName -eq 'PSGallery') { + $Parameters.Add('NuGetApiKey', $NuGetApiKey) } - Publish-Module @PublishSplat + Publish-Module @Parameters } else { Write-Error "Failed to sign module." } } -function Build-ScubaModule{ +function Build-ScubaModule { <# .NOTES Internal helper function #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] param ( - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path -Path $_ -PathType Container})] + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] [string] $ModulePath, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $OverrideModuleVersion = "", - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $PrereleaseTag = "" ) + Write-Host "Building ScubaGear module..." + $Leaf = Split-Path -Path $ModulePath -Leaf $ModuleBuildPath = Join-Path -Path $env:TEMP -ChildPath $Leaf - if (Test-Path -Path $ModuleBuildPath -PathType Container){ + if (Test-Path -Path $ModuleBuildPath -PathType Container) { Remove-Item -Recurse -Force $ModuleBuildPath } Copy-Item $ModulePath -Destination $env:TEMP -Recurse - if (-not (ConfigureScubaGearModule -ModulePath $ModuleBuildPath -OverrideModuleVersion $OverrideModuleVersion -PrereleaseTag $PrereleaseTag)){ + if (-not (ConfigureScubaGearModule -ModulePath $ModuleBuildPath -OverrideModuleVersion $OverrideModuleVersion -PrereleaseTag $PrereleaseTag)) { Write-Error "Failed to configure scuba module for publishing." } return $ModuleBuildPath } -function ConfigureScubaGearModule{ +function ConfigureScubaGearModule { <# .NOTES Internal helper function #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '')] param ( - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path -Path $_ -PathType Container})] + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] $ModulePath, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $OverrideModuleVersion = "", - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyString()] [string] $PrereleaseTag = "" ) + Write-Host "Configuring ScubaGear module..." #TODO: Add any module configuration needed (e.g., adjust Module Version) + # Verify that the module path folder exists + if (Test-Path -Path $ModulePath) { + Write-Host "The module dir exists at " + Write-Host $ModulePath + } + else { + Write-Warning "The module dir does not exist at " + Write-Warning $ModulePath + Write-Error "Failing..." + } + $ManifestPath = Join-Path -Path $ModulePath -ChildPath "ScubaGear.psd1" + + # Verify that the manifest file exists + if (Test-Path -Path $ManifestPath) { + Write-Host "The manifest file exists at " + Write-Host $ManifestPath + } + else { + Write-Warning "The manifest file does not exist at " + Write-Warning $ManifestPath + Write-Error "Failing..." + } + $ModuleVersion = $OverrideModuleVersion - if ([string]::IsNullOrEmpty($OverrideModuleVersion)){ + if ([string]::IsNullOrEmpty($OverrideModuleVersion)) { $CurrentModuleVersion = (Import-PowerShellDataFile $ManifestPath).ModuleVersion $TimeStamp = [int32](Get-Date -UFormat %s) $ModuleVersion = "$CurrentModuleVersion.$TimeStamp" } + Write-Host "The prerelease tag is" + Write-Host $PrereleaseTag + Write-Host "The module version is" + Write-Host $ModuleVersion + + $ProjectUri = "https://github.com/cisagov/ScubaGear" + $LicenseUri = "https://github.com/cisagov/ScubaGear/blob/main/LICENSE" # Tags cannot contain spaces + $Tags = 'CISA', 'O365', 'M365', 'AzureAD', 'Configuration', 'Exchange', 'Report', 'Security', 'SharePoint', 'Defender', 'Teams', 'PowerPlatform', 'OneDrive' + $ManifestUpdates = @{ - Path = $ManifestPath + Path = $ManifestPath ModuleVersion = $ModuleVersion - ProjectUri = "https://github.com/cisagov/ScubaGear" - LicenseUri = "https://github.com/cisagov/ScubaGear/blob/main/LICENSE" - Tags = 'CISA', 'O365', 'M365', 'AzureAD', 'Configuration', 'Exchange', 'Report', 'Security', 'SharePoint', 'Defender', 'Teams', 'PowerPlatform', 'OneDrive' + ProjectUri = $ProjectUri + LicenseUri = $LicenseUri + Tags = $Tags } - - if (-Not [string]::IsNullOrEmpty($PrereleaseTag)){ + if (-Not [string]::IsNullOrEmpty($PrereleaseTag)) { $ManifestUpdates.Add('Prerelease', $PrereleaseTag) } try { + $CurrentErrorActionPreference = $ErrorActionPreference + $ErrorActionPreference = "SilentlyContinue" Update-ModuleManifest @ManifestUpdates + $ErrorActionPreference = $CurrentErrorActionPreference + } + catch { + Write-Warning "Error: Cannot update module manifest:" + Write-Warning "Stacktrace:" + Write-Warning $_.ScriptStackTrace + Write-Warning "Exception:" + Write-Warning $_.Exception + Write-Error "Failed to update module manifest" + return $False + } + try { $CurrentErrorActionPreference = $ErrorActionPreference $ErrorActionPreference = "SilentlyContinue" - $Result = Test-ModuleManifest -Path $ManifestPath + Test-ModuleManifest -Path $ManifestPath $ErrorActionPreference = $CurrentErrorActionPreference } catch { - Write-Error "Manifest is not valid" - $Result = $null + Write-Warning "Warning: Cannot test module manifest:" + Write-Warning "Stacktrace:" + Write-Warning $_.ScriptStackTrace + Write-Warning "Exception:" + Write-Warning $_.Exception + Write-Error "Failed to test module manifest" + return $False } - return $null -ne $Result + # True indicates that the updating and testing were successful. + return $True } -function CreateFileList{ +function CreateFileList { <# .NOTES Internal function #> param( - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $SourcePath, - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [AllowEmptyCollection()] [array] $Extensions = @() ) - $FileNames = @() - - if ($Extensions.Count -gt 0){ + if ($Extensions.Count -gt 0) { $FileNames += Get-ChildItem -Recurse -Path $SourcePath -Include $Extensions } - Write-Debug "Found $($FileNames.Count) files to sign" - $FileList = New-TemporaryFile $FileNames.FullName | Out-File -FilePath $($FileList.FullName) -Encoding utf8 -Force Write-Debug "Files: $(Get-Content $FileList)" return $FileList.FullName } -function CallAzureSignTool{ +function CallAzureSignTool { <# .NOTES Internal function #> param ( - [Parameter(Mandatory=$true)] - [ValidateScript({[uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https'})] + [Parameter(Mandatory = $true)] + [ValidateScript({ [uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https' })] [System.Uri] $AzureKeyVaultUrl, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $CertificateName, - [Parameter(Mandatory=$false)] - [ValidateScript({[uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'http','https'})] + [Parameter(Mandatory = $false)] + [ValidateScript({ [uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'http', 'https' })] $TimeStampServer = 'http://timestamp.digicert.com', - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path -Path $_ -PathType Leaf})] + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Leaf })] $FileList ) $SignArguments = @( 'sign', '-coe', - '-fd',"sha256", + '-fd', "sha256", '-tr', $TimeStampServer, - '-kvu',$AzureKeyVaultUrl, - '-kvc',$CertificateName, + '-kvu', $AzureKeyVaultUrl, + '-kvc', $CertificateName, '-kvm' - '-ifl',$FileList + '-ifl', $FileList ) - Write-Debug "Calling AzureSignTool: $SignArguments" + Write-Output "Calling AzureSignTool: $SignArguments" $ToolPath = (Get-Command AzureSignTool).Path & $ToolPath $SignArguments } -function SignScubaGearModule{ +function SignScubaGearModule { <# .SYNOPSIS Code sign the specified module @@ -314,36 +364,37 @@ function SignScubaGearModule{ https://github.com/dell/OpenManage-PowerShell-Modules/blob/main/Sign-Module.ps1 #> param ( - [Parameter(Mandatory=$true)] - [ValidateScript({[uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https'})] + [Parameter(Mandatory = $true)] + [ValidateScript({ [uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'https' })] [System.Uri] $AzureKeyVaultUrl, - [Parameter(Mandatory=$true)] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $CertificateName, - [Parameter(Mandatory=$true)] - [ValidateScript({Test-Path -Path $_ -PathType Container})] + [Parameter(Mandatory = $true)] + [ValidateScript({ Test-Path -Path $_ -PathType Container })] $ModulePath, - [Parameter(Mandatory=$false)] - [ValidateScript({[uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'http','https'})] + [Parameter(Mandatory = $false)] + [ValidateScript({ [uri]::IsWellFormedUriString($_, 'Absolute') -and ([uri] $_).Scheme -in 'http', 'https' })] $TimeStampServer = 'http://timestamp.digicert.com' ) + Write-Output "Signing ScubaGear module..." # Digitally sign scripts, manifest, and modules - $FileList = CreateFileList -SourcePath $ModulePath -Extensions "*.ps1","*.psm1","*.psd1" + $FileList = CreateFileList -SourcePath $ModulePath -Extensions "*.ps1", "*.psm1", "*.psd1" CallAzureSignTool ` - -AzureKeyVaultUrl $AzureKeyVaultUrl ` - -CertificateName $CertificateName ` - -TimeStampServer $TimeStampServer ` - -FileList $FileList + -AzureKeyVaultUrl $AzureKeyVaultUrl ` + -CertificateName $CertificateName ` + -TimeStampServer $TimeStampServer ` + -FileList $FileList # Create and sign catalog $CatalogFileName = 'ScubaGear.cat' $CatalogPath = Join-Path -Path $ModulePath -ChildPath $CatalogFileName - if (Test-Path -Path $CatalogPath -PathType Leaf){ + if (Test-Path -Path $CatalogPath -PathType Leaf) { Remove-Item -Path $CatalogPath -Force } @@ -352,22 +403,22 @@ function SignScubaGearModule{ $CatalogPath.FullName | Out-File -FilePath $CatalogList -Encoding utf8 -Force CallAzureSignTool ` - -AzureKeyVaultUrl $AzureKeyVaultUrl ` - -CertificateName $CertificateName ` - -TimeStampServer $TimeStampServer ` - -FileList $CatalogList + -AzureKeyVaultUrl $AzureKeyVaultUrl ` + -CertificateName $CertificateName ` + -TimeStampServer $TimeStampServer ` + -FileList $CatalogList $TestResult = Test-FileCatalog -CatalogFilePath $CatalogPath return 'Valid' -eq $TestResult } -function IsRegistered{ +function IsRegistered { <# .NOTES Internal helper function #> param ( - [Parameter(Mandatory=$false)] + [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string] $RepoName = 'PrivateScubaGearGallery' @@ -375,13 +426,11 @@ function IsRegistered{ Write-Debug "Looking for $RepoName local repository" $Registered = $false - - try{ + try { $Registered = (Get-PSRepository).Name -contains $RepoName } catch { Write-Error "Failed to check IsRegistered: $_" } - return $Registered }