Skip to content
Open
9 changes: 9 additions & 0 deletions .azure-pipelines/config/credscan/credscan-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@
"tools\\Tests\\loadEnv.md"
],
"_justification": "[ToolsTest] Examples contain random values recognized as secret"
},
{
"file": [
"src\\Applications\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs",
"src\\Groups\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs",
"src\\Identity.DirectoryManagement\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs",
"src\\Users\\wrapper\\v1.0\\Client\\Models\\SynchronizationSecret.cs"
],
"_justification": "[Wrapper] Kiota-generated model for Graph's synchronizationSecret entity enumerates the API's secret-key names (Oauth2ClientSecret and similar) as enum member strings; these are schema vocabulary from the OpenAPI document, not secret values"
}
]
}
197 changes: 197 additions & 0 deletions .azure-pipelines/wrapper-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

name: $(BuildDefinitionName)_$(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

parameters:
- name: BuildAgent
default: 1es-windows-ps-compute-m
displayName: Build Agent
- name: PackageVersion
type: string
default: 3.0.0
displayName: Package version
- name: Sign
type: boolean
default: true
- name: Publish
type: boolean
default: false
- name: InternalFeed
type: string
# The feed is PROJECT-scoped (dev.azure.com/microsoftgraph/Graph Developer Experiences/
# _artifacts/feed/MSGraph_PowerShell_V3_Build), so publishVstsFeed needs the project
# qualifier - a bare feed name only resolves for organization-scoped feeds.
default: Graph Developer Experiences/MSGraph_PowerShell_V3_Build
displayName: Internal NuGet feed

variables:
BuildAgent: ${{ parameters.BuildAgent }}
WrapperConfiguration: Release
WrapperPrerelease: alpha$(Build.BuildId)

trigger: none
pr: none

resources:
repositories:
- repository: 1ESPipelineTemplates
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release

extends:
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
parameters:
pool: $(BuildAgent)
settings:
networkIsolationPolicy: Permissive
sdl:
binskim:
enabled: false
justificationForDisabling: "Matches sdk-release.yml; BinSkim currently blocks internal-feed publishing."
credscan:
suppressionsFile: $(Build.SourcesDirectory)/.azure-pipelines/config/credscan/credscan-suppressions.json
policheck:
exclusionFile: $(Build.SourcesDirectory)/.azure-pipelines/config/policheck/policheck-exclusions.xml
customBuildTags:
- ES365AIMigrationTooling
stages:
- stage: Build
displayName: Build wrapper modules
jobs:
- job: Wrapper_Build
displayName: Generate, build, pack, and sign wrapper modules
timeoutInMinutes: 840
templateContext:
outputs:
- output: pipelineArtifact
displayName: Publish wrapper module artifacts
targetPath: $(Build.ArtifactStagingDirectory)
artifactName: drop
publishLocation: Container
steps:
- script: git submodule update --init --recursive
displayName: Initialize submodules

# Deliberately NOT install-tools.yml: that template is the AutoRest toolchain - Node,
# the private npm feed, AutoRest, Rush and a full rush rebuild - none of which this
# pipeline uses. The wrapper modules compile from committed sources, so the whole
# toolchain is the .NET SDK, feed auth for restore, and the kiota CLI.
- task: UseDotNet@2
displayName: Use .NET SDK 10
retryCountOnTaskFailure: 2
inputs:
version: 10.x
- task: NuGetAuthenticate@1
# Under 1ES network isolation api.nuget.org is not reliably reachable, so every restore
# on this pipeline - the kiota tool below and the module build after it - resolves
# through the team's own feed first, whose upstream proxies nuget.org. The config is
# written at the sources root so dotnet picks it up everywhere; NuGetAuthenticate above
# supplies the credentials.
- task: PowerShell@2
displayName: Route NuGet through the internal feed
inputs:
targetType: inline
pwsh: true
script: |
$cfg = @'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="MSGraphV3" value="https://pkgs.dev.azure.com/microsoftgraph/Graph%20Developer%20Experiences/_packaging/MSGraph_PowerShell_V3_Build/nuget/v3/index.json" />
</packageSources>
</configuration>
'@
Set-Content -Path '$(Build.SourcesDirectory)/nuget.config' -Value $cfg -Encoding utf8
Write-Host "wrote $(Build.SourcesDirectory)/nuget.config"
# Build-WrapperModule.ps1 refuses to run without kiota on PATH even under -SkipKiota
# (the guard is unconditional), and future -Generate runs need it anyway.
- task: PowerShell@2
displayName: Install kiota CLI
retryCountOnTaskFailure: 2
inputs:
targetType: inline
pwsh: true
workingDirectory: $(Build.SourcesDirectory)
script: |
dotnet tool install --global Microsoft.OpenApi.Kiota --configfile '$(Build.SourcesDirectory)/nuget.config' --ignore-failed-sources
if ($LASTEXITCODE -ne 0) { throw "kiota install failed with exit code $LASTEXITCODE" }
Write-Host "##vso[task.prependpath]$env:USERPROFILE\.dotnet\tools"
- template: .azure-pipelines/common-templates/security-pre-checks.yml@self

# Version and prerelease go to the script directly (-ModuleVersion/-Prerelease); the
# script owns package identity. Nothing here edits ModuleMetadata.json - that file
# belongs to the v2 release train.
- task: PowerShell@2
displayName: Build and pack wrapper modules
inputs:
targetType: inline
pwsh: true
script: |
$params = @{
ApiVersion = 'v1.0'
Configuration = '$(WrapperConfiguration)'
ArtifactsLocation = '$(Build.ArtifactStagingDirectory)'
ModuleVersion = '${{ parameters.PackageVersion }}'
Prerelease = '$(WrapperPrerelease)'
# Deliberately NOT -SkipKiota: the pipeline runs the whole chain - kiota client
# generation from the committed OpenAPI docs, wrapper generation on top, then
# compile - so a change in any step is built and tested end to end, and the run
# can never fail on committed clients lagging the docs. Clients are still
# committed to the repo for reviewable diffs and clean local checkouts.
Pack = $true
}
& '$(Build.SourcesDirectory)/tools/Build-WrapperModule.ps1' @params
if ($LASTEXITCODE -ne 0) { throw "Wrapper build failed with exit code $LASTEXITCODE." }

- task: PowerShell@2
displayName: Verify wrapper packages were produced
inputs:
targetType: inline
pwsh: true
script: |
$packages = @(Get-ChildItem '$(Build.ArtifactStagingDirectory)' -Recurse -Filter 'Microsoft.Graph.Wrapper.*.nupkg')
Write-Host "Wrapper packages produced: $($packages.Count)"
$packages | ForEach-Object { Write-Host " $($_.FullName)" }
if ($packages.Count -eq 0) { throw 'No wrapper packages were produced.' }

- template: .azure-pipelines/common-templates/guardian-analyzer.yml@self

- ${{ if eq(parameters.Sign, true) }}:
- template: .azure-pipelines/common-templates/esrp/codesign-nuget.yml@self
parameters:
FolderPath: $(Build.ArtifactStagingDirectory)
Pattern: Microsoft.Graph.Wrapper.*.nupkg

- template: .azure-pipelines/common-templates/security-post-checks.yml@self

- ${{ if eq(parameters.Publish, true) }}:
- stage: Deploy_to_Internal_Feed
displayName: Deploy wrapper packages to internal feed
dependsOn: Build
jobs:
- deployment: DeployToInternalFeed
displayName: Publish to MSGraph PowerShell V3 build feed
environment: PowerShellInternalFeed
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
artifactName: drop
targetPath: $(System.DefaultWorkingDirectory)/drop
strategy:
runOnce:
deploy:
steps:
- task: 1ES.PublishNuget@1
displayName: Publish wrapper packages to internal feed
inputs:
useDotNetTask: false
packageParentPath: $(System.DefaultWorkingDirectory)
packagesToPush: $(System.DefaultWorkingDirectory)/**/drop/**/Microsoft.Graph.Wrapper.*.nupkg
publishVstsFeed: ${{ parameters.InternalFeed }}
nuGetFeedType: internal
allowPackageConflicts: false
84 changes: 84 additions & 0 deletions docs/install-wrapper-modules-from-feed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Install wrapper modules from the internal feed

The wrapper release pipeline publishes `Microsoft.Graph.Wrapper.*` prerelease packages to the
`MSGraph_PowerShell_V3_Build` Azure Artifacts feed for team validation.

## Prerequisites

- Access to the Azure DevOps project and read permission on the feed.
- PowerShellGet 2.x.
- `Microsoft.Graph.Authentication` installed at the version used by the build.

Get the feed's NuGet v2 URL from **Azure Artifacts > Connect to feed > NuGet > v2**. PowerShellGet
2.x requires the v2 endpoint when registering a `PSRepository`.

## Register the feed

Use an Azure DevOps personal access token with Packaging Read permission. Enter the token only at
the secure prompt; do not put it in a script, command history, or source-controlled file.

```powershell
$secureToken = Read-Host 'Azure DevOps PAT (Packaging: Read)' -AsSecureString
$credential = [pscredential]::new('AzureDevOps', $secureToken)
$feedUrl = 'https://pkgs.dev.azure.com/<organization>/<project>/_packaging/MSGraph_PowerShell_V3_Build/nuget/v2'

Register-PSRepository `
-Name MSGraphPowerShellV3Build `
-SourceLocation $feedUrl `
-PublishLocation $feedUrl `
-InstallationPolicy Trusted `
-Credential $credential
```

Replace `<organization>/<project>` with the values from **Connect to feed**. Do not infer them from
the feed's display name.

## Install and validate a package

Every pipeline run publishes a version such as `3.0.0-alpha12345`. `-AllowPrerelease` is therefore
required.

```powershell
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force

Install-Module `
-Name Microsoft.Graph.Wrapper.Users `
-Repository MSGraphPowerShellV3Build `
-Credential $credential `
-AllowPrerelease `
-Scope CurrentUser `
-Force

Import-Module Microsoft.Graph.Wrapper.Users -Force

$module = Get-Module Microsoft.Graph.Wrapper.Users
$commands = @(Get-Command -Module $module.Name)
if (-not $module -or $commands.Count -eq 0) {
throw 'The wrapper module did not import or export any commands.'
}

$module | Select-Object Name, Version, Path
Write-Host "Exported commands: $($commands.Count)"
```

For an operation test, start a clean PowerShell process, import only the wrapper module under test,
connect with the least privileges needed, and invoke a read-only command. Loading the shipping and
wrapper workload modules together can create command-name conflicts, even though their module and
package IDs differ.

## Find or update packages

```powershell
Find-Module Microsoft.Graph.Wrapper.* `
-Repository MSGraphPowerShellV3Build `
-Credential $credential `
-AllowPrerelease

Update-Module Microsoft.Graph.Wrapper.Users -AllowPrerelease -Force
```

Remove the repository registration when testing is complete:

```powershell
Unregister-PSRepository -Name MSGraphPowerShellV3Build
```
Loading