From bd5ab3d4388cf1de195e74015c9e33c8c626eea3 Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Wed, 29 Jul 2026 16:31:01 -0700 Subject: [PATCH 1/5] feat: publish PowerShell packages to ACR Add reusable PSResourceGet publishing for signed CI and release packages, with build-specific CI prerelease versions and path-based deployment gating. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: af247b04-ecf8-4873-a78c-33d6437bb0d0 --- .azure-pipelines/ci-build.yml | 51 +++++++- .../publish-psresources-acr.yml | 112 ++++++++++++++++++ .azure-pipelines/sdk-release.yml | 22 ++++ tools/AcrPipelineHelpers.ps1 | 57 +++++++++ tools/Tests/AcrPipelineHelpers.Tests.ps1 | 51 ++++++++ 5 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 .azure-pipelines/common-templates/publish-psresources-acr.yml create mode 100644 tools/AcrPipelineHelpers.ps1 create mode 100644 tools/Tests/AcrPipelineHelpers.Tests.ps1 diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 9f55a86f88..78356233ea 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -10,7 +10,7 @@ parameters: default: true - name: Pack type: boolean - default: false + default: true - name: Sign type: boolean default: true @@ -74,6 +74,32 @@ extends: artifactName: 'drop' publishLocation: 'Container' steps: + - checkout: self + fetchDepth: 2 + - task: PowerShell@2 + name: DetectAcrChanges + displayName: Detect ACR publishing changes + inputs: + targetType: inline + pwsh: true + script: | + . $(System.DefaultWorkingDirectory)/tools/AcrPipelineHelpers.ps1 + $changedPaths = @(git diff --name-only HEAD^1 HEAD) + if ($LASTEXITCODE -ne 0) { + throw "Failed to determine changed paths with exit code $LASTEXITCODE." + } + $shouldPublish = Test-AcrPublishPath -Path $changedPaths + Write-Host "Changed paths:`n$($changedPaths -join "`n")" + Write-Host "##vso[task.setvariable variable=ShouldPublishToAcr;isOutput=true]$($shouldPublish.ToString().ToLowerInvariant())" + - task: PowerShell@2 + displayName: Set CI module prerelease version + inputs: + targetType: inline + pwsh: true + script: | + . $(System.DefaultWorkingDirectory)/tools/AcrPipelineHelpers.ps1 + $prerelease = Set-CiModulePrerelease -MetadataPath '$(System.DefaultWorkingDirectory)/config/ModuleMetadata.json' -BuildId '$(Build.BuildId)' + Write-Host "Building PowerShell packages with prerelease suffix '$prerelease'." - script: | git submodule update --init --recursive - template: .azure-pipelines/common-templates/install-tools.yml@self @@ -105,4 +131,25 @@ extends: FolderPath: "$(Build.ArtifactStagingDirectory)" Pattern: "Microsoft.Graph*.nupkg" - - template: .azure-pipelines/common-templates/security-post-checks.yml@self \ No newline at end of file + - template: .azure-pipelines/common-templates/security-post-checks.yml@self + - ${{ if and(eq(parameters.Pack, true), eq(parameters.Sign, true)) }}: + - stage: Deploy_to_ACR + displayName: Deploy PowerShell packages to ACR + dependsOn: stage + condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), ne(variables['Build.Reason'], 'PullRequest'), eq(dependencies.stage.outputs['MsGraphPsSdkCiBuild.DetectAcrChanges.ShouldPublishToAcr'], 'true')) + jobs: + - deployment: DeployPowerShellPackagesToAcr + displayName: Deploy PowerShell packages to ACR + environment: PowerShellAcr + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: drop + targetPath: '$(System.DefaultWorkingDirectory)/drop' + strategy: + runOnce: + deploy: + steps: + - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self \ No newline at end of file diff --git a/.azure-pipelines/common-templates/publish-psresources-acr.yml b/.azure-pipelines/common-templates/publish-psresources-acr.yml new file mode 100644 index 0000000000..cd4f13121e --- /dev/null +++ b/.azure-pipelines/common-templates/publish-psresources-acr.yml @@ -0,0 +1,112 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +parameters: + - name: AzureSubscription + type: string + default: 'ACR Images Push Service Connection' + - name: RegistryUri + type: string + default: 'https://msgraphprodregistry.azurecr.io' + - name: RepositoryName + type: string + default: 'MicrosoftGraphAcr' + - name: ArtifactPath + type: string + default: '$(System.DefaultWorkingDirectory)/drop' + +steps: + - task: AzurePowerShell@5 + displayName: 'Publish PowerShell packages to ACR' + inputs: + azureSubscription: ${{ parameters.AzureSubscription }} + ScriptType: InlineScript + azurePowerShellVersion: LatestVersion + pwsh: true + Inline: | + $ErrorActionPreference = 'Stop' + $minimumPsResourceGetVersion = [version]'1.1.1' + $installedModule = Get-Module -ListAvailable -Name Microsoft.PowerShell.PSResourceGet | + Where-Object Version -GE $minimumPsResourceGetVersion | + Sort-Object Version -Descending | + Select-Object -First 1 + + if ($null -eq $installedModule) { + Install-Module -Name Microsoft.PowerShell.PSResourceGet -MinimumVersion $minimumPsResourceGetVersion -Scope CurrentUser -Force -AllowClobber + } + Import-Module Microsoft.PowerShell.PSResourceGet -MinimumVersion $minimumPsResourceGetVersion -Force + + $repositoryName = '${{ parameters.RepositoryName }}' + $existingRepository = Get-PSResourceRepository -Name $repositoryName -ErrorAction SilentlyContinue + if ($null -ne $existingRepository) { + Unregister-PSResourceRepository -Name $repositoryName + } + Register-PSResourceRepository -Name $repositoryName -Uri '${{ parameters.RegistryUri }}' + + Add-Type -AssemblyName System.IO.Compression.FileSystem + function Get-PackageMetadata { + param([Parameter(Mandatory)][string] $PackagePath) + + $archive = [System.IO.Compression.ZipFile]::OpenRead($PackagePath) + try { + $nuspecEntry = $archive.Entries | Where-Object FullName -Like '*.nuspec' | Select-Object -First 1 + if ($null -eq $nuspecEntry) { + throw "Package '$PackagePath' doesn't contain a nuspec file." + } + + $reader = [System.IO.StreamReader]::new($nuspecEntry.Open()) + try { + [xml] $nuspec = $reader.ReadToEnd() + } + finally { + $reader.Dispose() + } + + return [pscustomobject]@{ + Id = [string]$nuspec.package.metadata.id + Version = [string]$nuspec.package.metadata.version + Path = $PackagePath + } + } + finally { + $archive.Dispose() + } + } + + $packages = @(Get-ChildItem -Path '${{ parameters.ArtifactPath }}' -Recurse -File -Filter 'Microsoft.Graph*.nupkg' | + ForEach-Object { Get-PackageMetadata -PackagePath $_.FullName }) + if ($packages.Count -eq 0) { + throw "No Microsoft Graph PowerShell packages were found under '${{ parameters.ArtifactPath }}'." + } + + $packages = $packages | Sort-Object @{ + Expression = { + if ($_.Id -eq 'Microsoft.Graph.Authentication') { 0 } + elseif ($_.Id -in @('Microsoft.Graph', 'Microsoft.Graph.Beta')) { 2 } + else { 1 } + } + }, Id + + foreach ($package in $packages) { + $existingPackage = Find-PSResource -Name $package.Id -Version $package.Version -Repository $repositoryName -ErrorAction SilentlyContinue + if ($null -ne $existingPackage) { + Write-Host "$($package.Id) $($package.Version) already exists in $repositoryName; continuing the idempotent deployment." + continue + } + + Write-Host "Publishing $($package.Id) $($package.Version) to $repositoryName." + Publish-PSResource -NupkgPath $package.Path -Repository $repositoryName -ErrorAction Stop + } + + foreach ($package in $packages) { + $foundPackage = $null + for ($attempt = 1; $attempt -le 6 -and $null -eq $foundPackage; $attempt++) { + $foundPackage = Find-PSResource -Name $package.Id -Version $package.Version -Repository $repositoryName -ErrorAction SilentlyContinue + if ($null -eq $foundPackage -and $attempt -lt 6) { + Start-Sleep -Seconds 10 + } + } + if ($null -eq $foundPackage) { + throw "Published package '$($package.Id)' version '$($package.Version)' couldn't be found in ACR." + } + } diff --git a/.azure-pipelines/sdk-release.yml b/.azure-pipelines/sdk-release.yml index 6b7e8a0b8b..33ab0c3b78 100644 --- a/.azure-pipelines/sdk-release.yml +++ b/.azure-pipelines/sdk-release.yml @@ -158,6 +158,28 @@ extends: nuGetFeedType: external publishFeedCredentials: 'microsoftgraph PowerShell Gallery connection' + - ${{ if and(eq(parameters.Pack, true), eq(parameters.Sign, true)) }}: + - stage: Deploy_to_ACR + displayName: Deploy PowerShell packages to ACR + dependsOn: stage + condition: succeeded() + jobs: + - deployment: DeployPowerShellPackagesToAcr + displayName: Deploy PowerShell packages to ACR + environment: PowerShellAcr + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: drop + targetPath: '$(System.DefaultWorkingDirectory)/drop' + strategy: + runOnce: + deploy: + steps: + - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self + - stage: PushDockerImageToRegistry condition: and(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), eq(variables['Build.SourceBranch'], variables['PREVIEW_BRANCH'])), not(contains(variables['Build.SourceBranch'], '-preview'))) dependsOn: stage diff --git a/tools/AcrPipelineHelpers.ps1 b/tools/AcrPipelineHelpers.ps1 new file mode 100644 index 0000000000..672813ef29 --- /dev/null +++ b/tools/AcrPipelineHelpers.ps1 @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Set-CiModulePrerelease { + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] $MetadataPath, + + [Parameter(Mandatory)] + [ValidatePattern('^\d+$')] + [string] $BuildId + ) + + $metadata = Get-Content -Path $MetadataPath -Raw | ConvertFrom-Json -AsHashtable + $prerelease = "ci.$BuildId" + + foreach ($versionMetadata in $metadata.versions.Values) { + $versionMetadata.prerelease = $prerelease + } + + $metadata | ConvertTo-Json -Depth 100 | Set-Content -Path $MetadataPath -Encoding utf8 + return $prerelease +} + +function Test-AcrPublishPath { + [CmdletBinding()] + param( + [Parameter(Mandatory, ValueFromPipeline)] + [AllowEmptyString()] + [string[]] $Path + ) + + begin { + $isRelevant = $false + $patterns = @( + '^src/Authentication(?:/|$)', + '^config(?:/|$)', + '^autorest\.powershell(?:/|$)', + '^openApiDocs(?:/|$)' + ) + } + + process { + foreach ($item in $Path) { + $normalizedPath = $item.Replace('\', '/') + if ($patterns.Where({ $normalizedPath -match $_ }, 'First').Count -gt 0) { + $isRelevant = $true + } + } + } + + end { + return $isRelevant + } +} diff --git a/tools/Tests/AcrPipelineHelpers.Tests.ps1 b/tools/Tests/AcrPipelineHelpers.Tests.ps1 new file mode 100644 index 0000000000..fc79cfe264 --- /dev/null +++ b/tools/Tests/AcrPipelineHelpers.Tests.ps1 @@ -0,0 +1,51 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +BeforeAll { + . (Join-Path $PSScriptRoot '..\AcrPipelineHelpers.ps1') +} + +Describe 'Set-CiModulePrerelease' { + It 'sets every module prerelease value to the build-specific suffix' { + $metadataPath = Join-Path $TestDrive 'ModuleMetadata.json' + @{ + versions = @{ + authentication = @{ version = '2.39.0'; prerelease = '' } + beta = @{ version = '2.39.0'; prerelease = '' } + 'v1.0' = @{ version = '2.39.0'; prerelease = '' } + } + } | ConvertTo-Json -Depth 10 | Set-Content -Path $metadataPath + + Set-CiModulePrerelease -MetadataPath $metadataPath -BuildId '12345' | Should -Be 'ci.12345' + + $metadata = Get-Content -Path $metadataPath -Raw | ConvertFrom-Json + $metadata.versions.authentication.prerelease | Should -Be 'ci.12345' + $metadata.versions.beta.prerelease | Should -Be 'ci.12345' + $metadata.versions.'v1.0'.prerelease | Should -Be 'ci.12345' + } +} + +Describe 'Test-AcrPublishPath' { + It 'matches relevant source and generation inputs' -ForEach @( + 'src/Authentication/Authentication/Microsoft.Graph.Authentication.psd1' + 'SRC\AUTHENTICATION\Authentication.Core\Authentication.cs' + 'config/ModuleMetadata.json' + 'autorest.powershell/packages/autorest.powershell/package.json' + 'openApiDocs/v1.0/Users.yml' + ) { + Test-AcrPublishPath -Path $_ | Should -BeTrue + } + + It 'does not match unrelated paths' -ForEach @( + 'docs/readme.md' + 'samples/1-Users.ps1' + 'src/Users/v1.0/readme.md' + '.azure-pipelines/ci-build.yml' + ) { + Test-AcrPublishPath -Path $_ | Should -BeFalse + } + + It 'returns true when any changed path is relevant' { + Test-AcrPublishPath -Path @('docs/readme.md', 'config/ModulesMapping.jsonc') | Should -BeTrue + } +} From b6773099b3f65e123d3735bfeea9fc92ee33cc3f Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Wed, 29 Jul 2026 16:45:18 -0700 Subject: [PATCH 2/5] fix: configure ACR publishing variables Use shared registry and service connection variables and consume artifacts without requiring a separately provisioned Azure Pipelines environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: af247b04-ecf8-4873-a78c-33d6437bb0d0 --- .azure-pipelines/ci-build.yml | 12 ++------ .../publish-psresources-acr.yml | 28 +++++++------------ .azure-pipelines/sdk-release.yml | 12 ++------ 3 files changed, 16 insertions(+), 36 deletions(-) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 78356233ea..e9e3857301 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -138,18 +138,12 @@ extends: dependsOn: stage condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'), ne(variables['Build.Reason'], 'PullRequest'), eq(dependencies.stage.outputs['MsGraphPsSdkCiBuild.DetectAcrChanges.ShouldPublishToAcr'], 'true')) jobs: - - deployment: DeployPowerShellPackagesToAcr + - job: DeployPowerShellPackagesToAcr displayName: Deploy PowerShell packages to ACR - environment: PowerShellAcr templateContext: - type: releaseJob - isProduction: true inputs: - input: pipelineArtifact artifactName: drop targetPath: '$(System.DefaultWorkingDirectory)/drop' - strategy: - runOnce: - deploy: - steps: - - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self \ No newline at end of file + steps: + - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self \ No newline at end of file diff --git a/.azure-pipelines/common-templates/publish-psresources-acr.yml b/.azure-pipelines/common-templates/publish-psresources-acr.yml index cd4f13121e..fa685d8109 100644 --- a/.azure-pipelines/common-templates/publish-psresources-acr.yml +++ b/.azure-pipelines/common-templates/publish-psresources-acr.yml @@ -1,25 +1,17 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -parameters: - - name: AzureSubscription - type: string - default: 'ACR Images Push Service Connection' - - name: RegistryUri - type: string - default: 'https://msgraphprodregistry.azurecr.io' - - name: RepositoryName - type: string - default: 'MicrosoftGraphAcr' - - name: ArtifactPath - type: string - default: '$(System.DefaultWorkingDirectory)/drop' +variables: + ACR_SERVICE_CONNECTION: 'ACR Images Push Service Connection' + REGISTRY: 'msgraphprodregistry.azurecr.io' + REGISTRY_NAME: 'msgraphprodregistry' + ARTIFACT_PATH: '$(System.DefaultWorkingDirectory)/drop' steps: - task: AzurePowerShell@5 displayName: 'Publish PowerShell packages to ACR' inputs: - azureSubscription: ${{ parameters.AzureSubscription }} + azureSubscription: $(ACR_SERVICE_CONNECTION) ScriptType: InlineScript azurePowerShellVersion: LatestVersion pwsh: true @@ -36,12 +28,12 @@ steps: } Import-Module Microsoft.PowerShell.PSResourceGet -MinimumVersion $minimumPsResourceGetVersion -Force - $repositoryName = '${{ parameters.RepositoryName }}' + $repositoryName = '$(REGISTRY_NAME)' $existingRepository = Get-PSResourceRepository -Name $repositoryName -ErrorAction SilentlyContinue if ($null -ne $existingRepository) { Unregister-PSResourceRepository -Name $repositoryName } - Register-PSResourceRepository -Name $repositoryName -Uri '${{ parameters.RegistryUri }}' + Register-PSResourceRepository -Name $repositoryName -Uri 'https://$(REGISTRY)' Add-Type -AssemblyName System.IO.Compression.FileSystem function Get-PackageMetadata { @@ -73,10 +65,10 @@ steps: } } - $packages = @(Get-ChildItem -Path '${{ parameters.ArtifactPath }}' -Recurse -File -Filter 'Microsoft.Graph*.nupkg' | + $packages = @(Get-ChildItem -Path '$(ARTIFACT_PATH)' -Recurse -File -Filter 'Microsoft.Graph*.nupkg' | ForEach-Object { Get-PackageMetadata -PackagePath $_.FullName }) if ($packages.Count -eq 0) { - throw "No Microsoft Graph PowerShell packages were found under '${{ parameters.ArtifactPath }}'." + throw "No Microsoft Graph PowerShell packages were found under '$(ARTIFACT_PATH)'." } $packages = $packages | Sort-Object @{ diff --git a/.azure-pipelines/sdk-release.yml b/.azure-pipelines/sdk-release.yml index 33ab0c3b78..fbd08b17fb 100644 --- a/.azure-pipelines/sdk-release.yml +++ b/.azure-pipelines/sdk-release.yml @@ -164,21 +164,15 @@ extends: dependsOn: stage condition: succeeded() jobs: - - deployment: DeployPowerShellPackagesToAcr + - job: DeployPowerShellPackagesToAcr displayName: Deploy PowerShell packages to ACR - environment: PowerShellAcr templateContext: - type: releaseJob - isProduction: true inputs: - input: pipelineArtifact artifactName: drop targetPath: '$(System.DefaultWorkingDirectory)/drop' - strategy: - runOnce: - deploy: - steps: - - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self + steps: + - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self - stage: PushDockerImageToRegistry condition: and(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), eq(variables['Build.SourceBranch'], variables['PREVIEW_BRANCH'])), not(contains(variables['Build.SourceBranch'], '-preview'))) From 6f524763d0f3a5a555e70e3e7a27ea7308d1bcfd Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Wed, 29 Jul 2026 16:50:32 -0700 Subject: [PATCH 3/5] fix: define ACR variables at pipeline scope Keep the publishing step template parameter-free while declaring registry and service connection variables in the Azure Pipelines that consume it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: af247b04-ecf8-4873-a78c-33d6437bb0d0 --- .azure-pipelines/ci-build.yml | 2 ++ .../common-templates/publish-psresources-acr.yml | 11 +++-------- .azure-pipelines/sdk-release.yml | 2 ++ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index e9e3857301..3c9511e638 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -27,7 +27,9 @@ variables: BuildAgent: ${{ parameters.BuildAgent }} GitUserEmail: "GraphTooling@service.microsoft.com" GitUserName: "Microsoft Graph DevX Tooling" + ACR_SERVICE_CONNECTION: 'ACR Images Push Service Connection' REGISTRY: 'msgraphprodregistry.azurecr.io' + REGISTRY_NAME: 'msgraphprodregistry' IMAGE_NAME: 'public/microsoftgraph/powershell' trigger: diff --git a/.azure-pipelines/common-templates/publish-psresources-acr.yml b/.azure-pipelines/common-templates/publish-psresources-acr.yml index fa685d8109..cb114cc210 100644 --- a/.azure-pipelines/common-templates/publish-psresources-acr.yml +++ b/.azure-pipelines/common-templates/publish-psresources-acr.yml @@ -1,12 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -variables: - ACR_SERVICE_CONNECTION: 'ACR Images Push Service Connection' - REGISTRY: 'msgraphprodregistry.azurecr.io' - REGISTRY_NAME: 'msgraphprodregistry' - ARTIFACT_PATH: '$(System.DefaultWorkingDirectory)/drop' - steps: - task: AzurePowerShell@5 displayName: 'Publish PowerShell packages to ACR' @@ -65,10 +59,11 @@ steps: } } - $packages = @(Get-ChildItem -Path '$(ARTIFACT_PATH)' -Recurse -File -Filter 'Microsoft.Graph*.nupkg' | + $artifactPath = '$(System.DefaultWorkingDirectory)/drop' + $packages = @(Get-ChildItem -Path $artifactPath -Recurse -File -Filter 'Microsoft.Graph*.nupkg' | ForEach-Object { Get-PackageMetadata -PackagePath $_.FullName }) if ($packages.Count -eq 0) { - throw "No Microsoft Graph PowerShell packages were found under '$(ARTIFACT_PATH)'." + throw "No Microsoft Graph PowerShell packages were found under '$artifactPath'." } $packages = $packages | Sort-Object @{ diff --git a/.azure-pipelines/sdk-release.yml b/.azure-pipelines/sdk-release.yml index fbd08b17fb..0809074eab 100644 --- a/.azure-pipelines/sdk-release.yml +++ b/.azure-pipelines/sdk-release.yml @@ -27,7 +27,9 @@ variables: BuildAgent: ${{ parameters.BuildAgent }} GitUserEmail: "GraphTooling@service.microsoft.com" GitUserName: "Microsoft Graph DevX Tooling" + ACR_SERVICE_CONNECTION: 'ACR Images Push Service Connection' REGISTRY: 'msgraphprodregistry.azurecr.io' + REGISTRY_NAME: 'msgraphprodregistry' IMAGE_NAME: 'public/microsoftgraph/powershell' PREVIEW_BRANCH: 'refs/heads/main' # Updated to target your branch From 026e72fcf407d52aa3241c222f0f9a36c68712dc Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Wed, 29 Jul 2026 17:02:52 -0700 Subject: [PATCH 4/5] refactor: pass ACR variables to publish template Expose the service connection and registry values as explicit step-template parameters while retaining their definitions in the parent pipelines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: af247b04-ecf8-4873-a78c-33d6437bb0d0 --- .azure-pipelines/ci-build.yml | 6 +++++- .../common-templates/publish-psresources-acr.yml | 14 +++++++++++--- .azure-pipelines/sdk-release.yml | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index 3c9511e638..835d60c9fd 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -148,4 +148,8 @@ extends: artifactName: drop targetPath: '$(System.DefaultWorkingDirectory)/drop' steps: - - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self \ No newline at end of file + - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self + parameters: + AzureSubscription: $(ACR_SERVICE_CONNECTION) + Registry: $(REGISTRY) + RegistryName: $(REGISTRY_NAME) \ No newline at end of file diff --git a/.azure-pipelines/common-templates/publish-psresources-acr.yml b/.azure-pipelines/common-templates/publish-psresources-acr.yml index cb114cc210..8635405333 100644 --- a/.azure-pipelines/common-templates/publish-psresources-acr.yml +++ b/.azure-pipelines/common-templates/publish-psresources-acr.yml @@ -1,11 +1,19 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. +parameters: + - name: AzureSubscription + type: string + - name: Registry + type: string + - name: RegistryName + type: string + steps: - task: AzurePowerShell@5 displayName: 'Publish PowerShell packages to ACR' inputs: - azureSubscription: $(ACR_SERVICE_CONNECTION) + azureSubscription: ${{ parameters.AzureSubscription }} ScriptType: InlineScript azurePowerShellVersion: LatestVersion pwsh: true @@ -22,12 +30,12 @@ steps: } Import-Module Microsoft.PowerShell.PSResourceGet -MinimumVersion $minimumPsResourceGetVersion -Force - $repositoryName = '$(REGISTRY_NAME)' + $repositoryName = '${{ parameters.RegistryName }}' $existingRepository = Get-PSResourceRepository -Name $repositoryName -ErrorAction SilentlyContinue if ($null -ne $existingRepository) { Unregister-PSResourceRepository -Name $repositoryName } - Register-PSResourceRepository -Name $repositoryName -Uri 'https://$(REGISTRY)' + Register-PSResourceRepository -Name $repositoryName -Uri 'https://${{ parameters.Registry }}' Add-Type -AssemblyName System.IO.Compression.FileSystem function Get-PackageMetadata { diff --git a/.azure-pipelines/sdk-release.yml b/.azure-pipelines/sdk-release.yml index 0809074eab..4f8e382761 100644 --- a/.azure-pipelines/sdk-release.yml +++ b/.azure-pipelines/sdk-release.yml @@ -175,6 +175,10 @@ extends: targetPath: '$(System.DefaultWorkingDirectory)/drop' steps: - template: .azure-pipelines/common-templates/publish-psresources-acr.yml@self + parameters: + AzureSubscription: $(ACR_SERVICE_CONNECTION) + Registry: $(REGISTRY) + RegistryName: $(REGISTRY_NAME) - stage: PushDockerImageToRegistry condition: and(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), eq(variables['Build.SourceBranch'], variables['PREVIEW_BRANCH'])), not(contains(variables['Build.SourceBranch'], '-preview'))) From 1692fbcf56b1ada798b6d9f5e1ab902bdda2820a Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Thu, 30 Jul 2026 11:12:19 -0700 Subject: [PATCH 5/5] fix: use valid CI prerelease suffix Use the alphanumeric ci format accepted by Update-ModuleManifest and cover it with a real manifest validation test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: af247b04-ecf8-4873-a78c-33d6437bb0d0 --- tools/AcrPipelineHelpers.ps1 | 2 +- tools/Tests/AcrPipelineHelpers.Tests.ps1 | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/tools/AcrPipelineHelpers.ps1 b/tools/AcrPipelineHelpers.ps1 index 672813ef29..0141196c04 100644 --- a/tools/AcrPipelineHelpers.ps1 +++ b/tools/AcrPipelineHelpers.ps1 @@ -14,7 +14,7 @@ function Set-CiModulePrerelease { ) $metadata = Get-Content -Path $MetadataPath -Raw | ConvertFrom-Json -AsHashtable - $prerelease = "ci.$BuildId" + $prerelease = "ci$BuildId" foreach ($versionMetadata in $metadata.versions.Values) { $versionMetadata.prerelease = $prerelease diff --git a/tools/Tests/AcrPipelineHelpers.Tests.ps1 b/tools/Tests/AcrPipelineHelpers.Tests.ps1 index fc79cfe264..49f6ec035e 100644 --- a/tools/Tests/AcrPipelineHelpers.Tests.ps1 +++ b/tools/Tests/AcrPipelineHelpers.Tests.ps1 @@ -16,12 +16,18 @@ Describe 'Set-CiModulePrerelease' { } } | ConvertTo-Json -Depth 10 | Set-Content -Path $metadataPath - Set-CiModulePrerelease -MetadataPath $metadataPath -BuildId '12345' | Should -Be 'ci.12345' + $prerelease = Set-CiModulePrerelease -MetadataPath $metadataPath -BuildId '12345' + $prerelease | Should -Be 'ci12345' $metadata = Get-Content -Path $metadataPath -Raw | ConvertFrom-Json - $metadata.versions.authentication.prerelease | Should -Be 'ci.12345' - $metadata.versions.beta.prerelease | Should -Be 'ci.12345' - $metadata.versions.'v1.0'.prerelease | Should -Be 'ci.12345' + $metadata.versions.authentication.prerelease | Should -Be 'ci12345' + $metadata.versions.beta.prerelease | Should -Be 'ci12345' + $metadata.versions.'v1.0'.prerelease | Should -Be 'ci12345' + + $manifestPath = Join-Path $TestDrive 'TestModule.psd1' + Set-Content -Path (Join-Path $TestDrive 'TestModule.psm1') -Value '' + New-ModuleManifest -Path $manifestPath -RootModule 'TestModule.psm1' -ModuleVersion '1.0.0' + { Update-ModuleManifest -Path $manifestPath -Prerelease $prerelease } | Should -Not -Throw } }