From 00db04466cdc97b8fcbf4298c23497504bb7fd81 Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Thu, 30 Jul 2026 17:20:46 -0700 Subject: [PATCH 1/3] Refactor weekly docs pipeline into isolated per-module stages Each Graph workload module now generates in its own Azure DevOps stage per SDK profile (~86 independent stages), giving every module a fresh agent and a clean assembly-load context. This structurally eliminates the "assembly with same name is already loaded" collisions that produced mass-deletion / no-op refresh PRs. - GenerateMarkDown.ps1: add -GraphProfileFilter and -ModuleFilter scoping; in scoped runs do a complete delete + full regen of the single module, throw before any deletion on import failure (fail-safe), and emit a ModuleGenerated signal only on success. - 9 post-processing scripts: add matching profile/module scoping and empty-commit guards so each touches only the target module folder. - update-template.yml: parameterized install (InstallProfile/Module) that installs only Authentication + the single workload submodule for isolated runs; install failures warn instead of failing. - New common-templates/generate-module-docs-steps.yml: full single-module sequence (install -> generate -> post-process -> push -> PR). Generate step is continueOnError and every later step is gated on ModuleGenerated, so a failing module deletes nothing, opens no PR, and never fails the overall pipeline. - New common-templates/module-stages.yml: emits one dependsOn:[] stage per module. - powershell-docs.yml: invoke module-stages.yml for v1.0 (incl. Authentication) and beta; each stage opens its own pull request. ms.date changes every run (StabilizeMsDate is intentionally not run) for reviewable PRs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4d27d82f-3809-40d2-b4e7-6623436ff2ef --- .../generate-module-docs-steps.yml | 219 ++++++++++++++++ .../common-templates/module-stages.yml | 50 ++++ azure-pipelines/powershell-docs.yml | 248 ++++++++---------- azure-pipelines/update-template.yml | 60 ++++- scripts/CorrectRelatedLinks-AllFiles.ps1 | 10 +- scripts/EscapeDisallowedHtmlTags.ps1 | 16 +- scripts/GenerateAliasedDocs.ps1 | 11 +- scripts/GenerateMarkDown.ps1 | 93 +++++-- scripts/GeneratePermissionsTable.ps1 | 15 +- scripts/MsSubserviceUpdate.ps1 | 7 +- scripts/RemoveBoilerPlateCode.ps1 | 34 ++- scripts/RemoveInvalidFullStops.ps1 | 16 +- .../RepairExamplesDescriptionsAndTitle.ps1 | 14 +- scripts/UpdateLinks.ps1 | 9 +- 14 files changed, 600 insertions(+), 202 deletions(-) create mode 100644 azure-pipelines/common-templates/generate-module-docs-steps.yml create mode 100644 azure-pipelines/common-templates/module-stages.yml diff --git a/azure-pipelines/common-templates/generate-module-docs-steps.yml b/azure-pipelines/common-templates/generate-module-docs-steps.yml new file mode 100644 index 0000000000000..290ee79651d59 --- /dev/null +++ b/azure-pipelines/common-templates/generate-module-docs-steps.yml @@ -0,0 +1,219 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# +# Reusable steps that generate and post-process documentation for a SINGLE module in a SINGLE +# SDK profile, then open a dedicated pull request for it. Designed to run inside an isolated +# per-module stage so an import/assembly conflict for one module can never affect another. +# +# Failure containment: the generate step runs with continueOnError, and every step after it is +# gated on the ModuleGenerated variable (set only when generation actually succeeds). If the +# module fails to import, nothing is deleted, no PR is opened, and the stage is marked +# partially-succeeded WITHOUT failing the overall pipeline or any other module's stage. + +parameters: + - name: Module + type: string + - name: GraphProfileFilter + type: string + - name: BranchPrefix + type: string + default: WeeklyDocs + - name: BaseBranch + type: string + default: main + +steps: + - template: ../update-template.yml + parameters: + InstallProfile: ${{ parameters.GraphProfileFilter }} + Module: ${{ parameters.Module }} + + - task: PowerShell@2 + name: ComputeBranch + displayName: 'Compute branch name' + inputs: + targetType: 'inline' + pwsh: true + script: | + git submodule update --init --recursive + $branch = "{0}-{1}-{2}_{3}" -f "${{ parameters.BranchPrefix }}", "${{ parameters.GraphProfileFilter }}", "${{ parameters.Module }}", (Get-Date -Format yyyyMMddHHmm) + Write-Host "Compute branch: $branch" + Write-Host "##vso[task.setvariable variable=ModuleBranch;isOutput=true]$branch" + + - task: Bash@3 + displayName: 'Create module refresh branch' + inputs: + targetType: inline + script: | + git status + git branch $(ComputeBranch.ModuleBranch) + git checkout $(ComputeBranch.ModuleBranch) + git status + + - task: PowerShell@2 + displayName: 'Create missing external docs folder' + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/CreateExternalDocsFolder.ps1 + + - task: PowerShell@2 + name: GenerateDocs + displayName: 'Generate markdown (${{ parameters.Module }} / ${{ parameters.GraphProfileFilter }})' + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/GenerateMarkDown.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Escape disallowed html tags' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/EscapeDisallowedHtmlTags.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Update metadata header' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/MsSubserviceUpdate.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Generate permissions table' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/GeneratePermissionsTable.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Update Links' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/UpdateLinks.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Repair Examples and Descriptions and Title' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/RepairExamplesDescriptionsAndTitle.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Remove boiler plate code' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/RemoveBoilerPlateCode.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Remove invalid fullstops' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/RemoveInvalidFullStops.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Generate missing docs for aliased cmdlets' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/GenerateAliasedDocs.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: PowerShell@2 + displayName: 'Correct related links section' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + inputs: + targetType: 'filePath' + pwsh: true + filePath: scripts/CorrectRelatedLinks-AllFiles.ps1 + arguments: -GraphProfileFilter "${{ parameters.GraphProfileFilter }}" -ModuleFilter "${{ parameters.Module }}" + + - task: AzureKeyVault@2 + displayName: "Azure Key Vault: Get GitHub App secrets" + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + inputs: + azureSubscription: "Federated AKV Managed Identity Connection" + KeyVaultName: akv-prod-eastus + SecretsFilter: "microsoft-graph-devx-bot-appid,microsoft-graph-devx-bot-privatekey" + + - task: PowerShell@2 + displayName: 'Push ${{ parameters.Module }} (${{ parameters.GraphProfileFilter }}) branch' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + env: + GhAppId: $(microsoft-graph-devx-bot-appid) + GhAppKey: $(microsoft-graph-devx-bot-privatekey) + inputs: + targetType: inline + pwsh: true + script: | + git config --global user.email "GraphTooling@service.microsoft.com" + git config --global user.name "Microsoft Graph DevX Tooling" + git config --system core.longpaths true + # The microsoft-graph-devx-bot GitHub App must have contents:write on this repo. + $token = & "$(Build.SourcesDirectory)/scripts/Generate-Github-Token.ps1" -AppClientId $env:GhAppId -AppPrivateKeyContents $env:GhAppKey -Repository "MicrosoftDocs/microsoftgraph-docs-powershell" + if ([string]::IsNullOrWhiteSpace($token)) { throw "Failed to generate GitHub App installation token." } + # Mask the token so it is never surfaced in pipeline logs. + Write-Host "##vso[task.setsecret]$token" + git status + git add . + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Updating reference docs for ${{ parameters.Module }} (${{ parameters.GraphProfileFilter }})" + } + else { + $global:LASTEXITCODE = 0 + } + git push --set-upstream "https://x-access-token:$token@github.com/MicrosoftDocs/microsoftgraph-docs-powershell.git" $(ComputeBranch.ModuleBranch) + git status + + - task: PowerShell@2 + displayName: 'Create PR for ${{ parameters.Module }} (${{ parameters.GraphProfileFilter }})' + condition: and(succeeded(), eq(variables['ModuleGenerated'], 'true')) + continueOnError: true + env: + GhAppId: $(microsoft-graph-devx-bot-appid) + GhAppKey: $(microsoft-graph-devx-bot-privatekey) + inputs: + targetType: inline + pwsh: true + script: | + $BaseBranchName = "${{ parameters.BaseBranch }}" + $Head = "MicrosoftDocs:$(ComputeBranch.ModuleBranch)" + $Title = "Weekly Docs Refresh: ${{ parameters.Module }} (${{ parameters.GraphProfileFilter }})" + $Body = "This pull request was automatically created by Azure Pipelines for the ``${{ parameters.Module }}`` module (``${{ parameters.GraphProfileFilter }}`` profile). **Important** Check for unexpected deletions or changes in this PR." + # The microsoft-graph-devx-bot GitHub App must have pull_requests:write on this repo. + $env:GITHUB_TOKEN = & "$(Build.SourcesDirectory)/scripts/Generate-Github-Token.ps1" -AppClientId $env:GhAppId -AppPrivateKeyContents $env:GhAppKey -Repository "MicrosoftDocs/microsoftgraph-docs-powershell" + if ([string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN)) { throw "Failed to generate GitHub App installation token." } + # Mask the token so it is never surfaced in pipeline logs. + Write-Host "##vso[task.setsecret]$env:GITHUB_TOKEN" + gh pr create -t $Title -b $Body -B $BaseBranchName -H $Head diff --git a/azure-pipelines/common-templates/module-stages.yml b/azure-pipelines/common-templates/module-stages.yml new file mode 100644 index 0000000000000..f8a1f19f2cfc1 --- /dev/null +++ b/azure-pipelines/common-templates/module-stages.yml @@ -0,0 +1,50 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +# +# Emits one fully independent stage per module for a given SDK profile. Each stage runs on its +# own agent (clean assembly-load context), generates + post-processes docs for that single +# module, and opens its own pull request. All stages have dependsOn: [] so they run in +# parallel and one module can never block or fail another. + +parameters: + - name: GraphProfileFilter + type: string + # Short label used in stage names (stage names cannot contain '.'), e.g. "v1" or "beta". + - name: ProfileLabel + type: string + - name: Modules + type: object + default: [] + - name: BranchPrefix + type: string + default: WeeklyDocs + - name: BaseBranch + type: string + default: main + - name: PipelineTimeout + type: number + default: 120 + +stages: + - ${{ each module in parameters.Modules }}: + - stage: "Gen_${{ parameters.ProfileLabel }}_${{ replace(module, '.', '_') }}" + displayName: '${{ parameters.GraphProfileFilter }} - ${{ module }}' + dependsOn: [] + jobs: + - job: GenerateModuleDocs + timeoutInMinutes: ${{ parameters.PipelineTimeout }} + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish missing external docs' + targetPath: missingexternaldocsurl + artifactName: "MissingExternalDocs-${{ parameters.ProfileLabel }}-${{ replace(module, '.', '-') }}" + publishLocation: 'Container' + condition: succeededOrFailed() + steps: + - template: generate-module-docs-steps.yml + parameters: + Module: ${{ module }} + GraphProfileFilter: ${{ parameters.GraphProfileFilter }} + BranchPrefix: ${{ parameters.BranchPrefix }} + BaseBranch: ${{ parameters.BaseBranch }} diff --git a/azure-pipelines/powershell-docs.yml b/azure-pipelines/powershell-docs.yml index 603004858ab45..b6dc5b22f8b9b 100644 --- a/azure-pipelines/powershell-docs.yml +++ b/azure-pipelines/powershell-docs.yml @@ -47,148 +47,106 @@ extends: customBuildTags: - ES365AIMigrationTooling stages: - - stage: stage - jobs: - - job: PowerShellDocsUpdate - timeoutInMinutes: ${{ parameters.PipelineTimeout }} - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Publish Examples to be reviewed as artifact' - targetPath: missingexternaldocsurl - artifactName: 'MissingExternalDocs' - publishLocation: 'Container' - steps: - - template: azure-pipelines/update-template.yml@self - - task : PowerShell@2 - name: "ComputeBranch" - displayName: 'Compute Branch Name' - inputs: - targetType: 'inline' - script: | - git submodule update --init --recursive - $branch = "{0}_{1}" -f "WeeklyReferenceDocsUpdate", (Get-Date -Format yyyyMMddHHmm) - Write-Host "Compute branch: $branch" - Write-Host "##vso[task.setvariable variable=WeeklyReferenceDocsBranch;isOutput=true]$branch" - - task: Bash@3 - displayName: "Create weekly refresh branch" - inputs: - targetType: inline - script: | - git status - git branch $(ComputeBranch.WeeklyReferenceDocsBranch) - git checkout $(ComputeBranch.WeeklyReferenceDocsBranch) - git status - - task: PowerShell@2 - displayName: 'Create missing external docs folder' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/CreateExternalDocsFolder.ps1 - - - task: PowerShell@2 - displayName: 'Generate markdown files from PowerShell help files' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/GenerateMarkDown.ps1 - - task: PowerShell@2 - displayName: 'Escape disallowed html tags' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/EscapeDisallowedHtmlTags.ps1 - - task: PowerShell@2 - displayName: 'Update metadata header' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/MsSubserviceUpdate.ps1 - - task: PowerShell@2 - displayName: 'Generate permissions table' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/GeneratePermissionsTable.ps1 - - task: PowerShell@2 - displayName: 'Update Links' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/UpdateLinks.ps1 - errorActionPreference: 'stop' - - task: PowerShell@2 - displayName: 'Repair Examples and Descriptions and Title' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/RepairExamplesDescriptionsAndTitle.ps1 - - task: PowerShell@2 - displayName: 'Remove boiler plate code' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/RemoveBoilerPlateCode.ps1 - - task: PowerShell@2 - displayName: 'Remove invalid fullstops' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/RemoveInvalidFullStops.ps1 - - task: PowerShell@2 - displayName: 'Generate missing docs for aliased cmdlets' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/GenerateAliasedDocs.ps1 - - task: PowerShell@2 - displayName: 'Correct related links section' - continueOnError: false - inputs: - targetType: 'filePath' - pwsh: true - filePath: scripts/CorrectRelatedLinks-AllFiles.ps1 - - task: AzureKeyVault@2 - displayName: "Azure Key Vault: Get GitHub App secrets" - inputs: - azureSubscription: "Federated AKV Managed Identity Connection" - KeyVaultName: akv-prod-eastus - SecretsFilter: "microsoft-graph-devx-bot-appid,microsoft-graph-devx-bot-privatekey" - - task: PowerShell@2 - displayName: Pushing to github - env: - GhAppId: $(microsoft-graph-devx-bot-appid) - GhAppKey: $(microsoft-graph-devx-bot-privatekey) - inputs: - targetType: inline - pwsh: true - script: | - git config --global user.email "GraphTooling@service.microsoft.com" - git config --global user.name "Microsoft Graph DevX Tooling" - git config --system core.longpaths true - # The microsoft-graph-devx-bot GitHub App must have contents:write on this repo. - $token = & "$(Build.SourcesDirectory)/scripts/Generate-Github-Token.ps1" -AppClientId $env:GhAppId -AppPrivateKeyContents $env:GhAppKey -Repository "MicrosoftDocs/microsoftgraph-docs-powershell" - if ([string]::IsNullOrWhiteSpace($token)) { throw "Failed to generate GitHub App installation token." } - # Mask the token so it is never surfaced in pipeline logs. - Write-Host "##vso[task.setsecret]$token" - git status - git add . - git commit -m "Updating reference docs" - git push --set-upstream "https://x-access-token:$token@github.com/MicrosoftDocs/microsoftgraph-docs-powershell.git" $(ComputeBranch.WeeklyReferenceDocsBranch) - git status - - template: azure-pipelines/common-templates/create-pr.yml@self - parameters: - BaseBranch: "main" - TargetBranch: $(ComputeBranch.WeeklyReferenceDocsBranch) - Title: "Weekly PowerShell Microsoft Graph Reference Docs Refresh" - Body: "This pull request was automatically created by Azure Pipelines. **Important** Check for unexpected deletions or changes in this PR." \ No newline at end of file + - template: azure-pipelines/common-templates/module-stages.yml@self + parameters: + GraphProfileFilter: "v1.0" + ProfileLabel: "v1" + BranchPrefix: "WeeklyDocs" + BaseBranch: "main" + PipelineTimeout: ${{ parameters.PipelineTimeout }} + Modules: + - Authentication + - Applications + - Bookings + - BusinessScenario + - BackupRestore + - Calendar + - ChangeNotifications + - CloudCommunications + - Compliance + - CrossDeviceExperiences + - Devices.CloudPrint + - Devices.CorporateManagement + - Devices.ServiceAnnouncement + - DeviceManagement + - DeviceManagement.Administration + - DeviceManagement.Enrollment + - DeviceManagement.Actions + - DeviceManagement.Functions + - DirectoryObjects + - Education + - Files + - Financials + - Groups + - Identity.DirectoryManagement + - Identity.Governance + - Identity.SignIns + - Identity.Partner + - Mail + - ManagedTenants + - NetworkAccess + - Notes + - People + - PersonalContacts + - Planner + - Reports + - SchemaExtensions + - Search + - Security + - Sites + - Teams + - Users + - Users.Actions + - Users.Functions + - WindowsUpdates + - template: azure-pipelines/common-templates/module-stages.yml@self + parameters: + GraphProfileFilter: "beta" + ProfileLabel: "beta" + BranchPrefix: "WeeklyDocs" + BaseBranch: "main" + PipelineTimeout: ${{ parameters.PipelineTimeout }} + Modules: + - Applications + - Bookings + - BusinessScenario + - BackupRestore + - Calendar + - ChangeNotifications + - CloudCommunications + - Compliance + - CrossDeviceExperiences + - Devices.CloudPrint + - Devices.CorporateManagement + - Devices.ServiceAnnouncement + - DeviceManagement + - DeviceManagement.Administration + - DeviceManagement.Enrollment + - DeviceManagement.Actions + - DeviceManagement.Functions + - DirectoryObjects + - Education + - Files + - Financials + - Groups + - Identity.DirectoryManagement + - Identity.Governance + - Identity.SignIns + - Identity.Partner + - Mail + - ManagedTenants + - NetworkAccess + - Notes + - People + - PersonalContacts + - Planner + - Reports + - SchemaExtensions + - Search + - Security + - Sites + - Teams + - Users + - Users.Actions + - Users.Functions + - WindowsUpdates diff --git a/azure-pipelines/update-template.yml b/azure-pipelines/update-template.yml index cd0ec7be60216..8884e887a9aae 100644 --- a/azure-pipelines/update-template.yml +++ b/azure-pipelines/update-template.yml @@ -1,3 +1,16 @@ +parameters: + # Which SDK profile to install for. "both" preserves the original full-run behaviour and + # installs the Microsoft.Graph and Microsoft.Graph.Beta meta modules. + - name: InstallProfile + type: string + default: both + # When set, only the single workload submodule for that module (plus Authentication) is + # installed instead of the whole SDK. This keeps each isolated per-module stage fast and + # guarantees only one workload module is ever present in the session. + - name: Module + type: string + default: "" + steps: - task: PowerShell@2 displayName: Install Required Modules @@ -12,25 +25,44 @@ steps: if (-not (Get-PSRepository -Name PowerShell_V2_Build -ErrorAction SilentlyContinue)) { Register-PSRepository -Name PowerShell_V2_Build -SourceLocation $feedUrl -InstallationPolicy Trusted -Credential $cred } - try{ - # Try to update the V1 module first. - Install-Module Microsoft.Graph.Beta -Repository PowerShell_V2_Build -Credential $cred -Scope AllUsers -AcceptLicense -SkipPublisherCheck -Force -AllowClobber - }catch{ - # If the module is not installed, install it. - echo "Error when installing Beta" + + $installProfile = "${{ parameters.InstallProfile }}" + $module = "${{ parameters.Module }}" + + # Build the list of modules to install. + $toInstall = @() + if ([string]::IsNullOrWhiteSpace($module)) { + # Full-run behaviour: install the meta module(s) for the requested profile(s). + if ($installProfile -ne 'v1.0') { $toInstall += 'Microsoft.Graph.Beta' } + if ($installProfile -ne 'beta') { $toInstall += 'Microsoft.Graph' } } - try{ - # Try to update the beta module first. - Install-Module Microsoft.Graph -Repository PowerShell_V2_Build -Credential $cred -Scope AllUsers -AcceptLicense -SkipPublisherCheck -Force -AllowClobber - }catch{ - # If the module is not installed, install it. - echo "Error when installing V1" + else { + # Isolated per-module run: install only Authentication + the single workload + # submodule for the target profile. + $toInstall += 'Microsoft.Graph.Authentication' + if ($installProfile -eq 'beta') { + $toInstall += "Microsoft.Graph.Beta.$module" + } + else { + $toInstall += "Microsoft.Graph.$module" + } + } + + foreach ($m in $toInstall) { + try { + Install-Module $m -Repository PowerShell_V2_Build -Credential $cred -Scope AllUsers -AcceptLicense -SkipPublisherCheck -Force -AllowClobber + } + catch { + # Do not fail the install step; a missing/failed module surfaces later as a + # fail-safe (no deletion, no PR) in the isolated generation stage. + Write-Host "##vso[task.logissue type=warning]Failed to install $m : $($_.Exception.Message)" + } } - $modules = Get-Module -Name Microsoft.Graph -ListAvailable + $modules = Get-Module -Name Microsoft.Graph* -ListAvailable foreach($r in $modules) { - echo $r.Version + echo "$($r.Name) $($r.Version)" } env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/scripts/CorrectRelatedLinks-AllFiles.ps1 b/scripts/CorrectRelatedLinks-AllFiles.ps1 index 3f6a71383b9e6..be414538528a7 100644 --- a/scripts/CorrectRelatedLinks-AllFiles.ps1 +++ b/scripts/CorrectRelatedLinks-AllFiles.ps1 @@ -19,7 +19,11 @@ param( [Parameter(Mandatory = $false)] [ValidateSet("v1.0", "beta", "both")] - [string]$GraphProfile = "both" + [Alias("GraphProfileFilter")] + [string]$GraphProfile = "both", + + [Parameter(Mandatory = $false)] + [string]$ModuleFilter = "" ) function Get-GraphMapping { @@ -217,6 +221,10 @@ if ($ModulesToGenerate.Count -eq 0) { } } +if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) +} + # Main execution Write-Host "=== Related Links Correction Script ===" -ForegroundColor Blue Write-Host "Working directory: $WorkLoadDocsPath" -ForegroundColor Blue diff --git a/scripts/EscapeDisallowedHtmlTags.ps1 b/scripts/EscapeDisallowedHtmlTags.ps1 index 597d7a5633f81..0039a05b33500 100644 --- a/scripts/EscapeDisallowedHtmlTags.ps1 +++ b/scripts/EscapeDisallowedHtmlTags.ps1 @@ -2,7 +2,10 @@ # Licensed under the MIT License. Param( $ModulesToGenerate = @(), - [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc") + [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Get-GraphMapping { $graphMapping = @{} @@ -20,13 +23,19 @@ function EscapeDisallowedHtmlTags { $GraphMapping = Get-GraphMapping $GraphMapping.Keys | ForEach-Object { $graphProfile = $_ + if ($GraphProfileFilter -ne "both" -and $graphProfile -ne $GraphProfileFilter) { return } Get-FilesByProfile -GraphProfile $graphProfile -GraphProfilePath $GraphMapping[$graphProfile] -ModulePrefix $ModulePrefix -ModulesToGenerate $ModulesToGenerate } git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Escaped disallowed html tags" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Escaped disallowed html tags" + } else { + $global:LASTEXITCODE = 0 + } } function Get-FilesByProfile { Param( @@ -322,5 +331,8 @@ function CleanupFile { } Write-Host -ForegroundColor Green "-------------finished checking out to today's branch-------------" +if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) +} EscapeDisallowedHtmlTags -ModulesToGenerate $ModulesToGenerate Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file diff --git a/scripts/GenerateAliasedDocs.ps1 b/scripts/GenerateAliasedDocs.ps1 index 97eba9685354d..c7ba9bec1d19c 100644 --- a/scripts/GenerateAliasedDocs.ps1 +++ b/scripts/GenerateAliasedDocs.ps1 @@ -6,12 +6,18 @@ See related PR for more information. #> +Param( + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" +) + function Start-GenerateAliasedDocs { $BreakingChangeReportV1Report = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/docs/PowerShellBreakingChanges-V1.0.csv") $BreakingChangeReportBetaReport = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/docs/PowerShellBreakingChanges-beta.csv") $Reports = @() - $Reports += $BreakingChangeReportV1Report - $Reports += $BreakingChangeReportBetaReport + if ($GraphProfileFilter -ne "beta") { $Reports += $BreakingChangeReportV1Report } + if ($GraphProfileFilter -ne "v1.0") { $Reports += $BreakingChangeReportBetaReport } foreach ($BreakingChangeReport in $Reports) { Import-Csv $BreakingChangeReport | ForEach-Object { $Command = $_."NewCmdlet" @@ -39,6 +45,7 @@ function Copy-Files { if($CmdDetails){ $Module = $CmdDetails.Module $Module = $Module.Replace("Beta.", "") + if (-not [string]::IsNullOrWhiteSpace($ModuleFilter) -and $Module -ne $ModuleFilter) { return } $Module = "$ModulePrefix.$Module" $NewDoc = (Join-Path $PSScriptRoot "../microsoftgraph" $GraphProfilePath $Module "$Command.md") if(Test-Path $NewDoc) { diff --git a/scripts/GenerateMarkDown.ps1 b/scripts/GenerateMarkDown.ps1 index 0d5a062bef429..ed6c85dcdb009 100644 --- a/scripts/GenerateMarkDown.ps1 +++ b/scripts/GenerateMarkDown.ps1 @@ -5,7 +5,16 @@ Param( $ModulesToGenerate = @(), [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config\ModulesMapping.jsonc"), [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph"), - [string] $CmdletMetadataPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src/Authentication/Authentication/custom/common/MgCommandMetadata.json") + [string] $CmdletMetadataPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src/Authentication/Authentication/custom/common/MgCommandMetadata.json"), + # Restrict generation to a single SDK profile ("v1.0" or "beta"); "both" keeps the + # original full-run behaviour. + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + # Restrict generation to a single module (e.g. "Applications" or "Authentication"). Empty + # means all modules. When set, the run is "scoped": the module's folder is completely + # cleared and regenerated, and an import failure aborts THIS run (fail-safe, no deletion) + # instead of silently skipping. + [string] $ModuleFilter = "" ) function Get-GraphMapping { $graphMapping = @{} @@ -51,36 +60,55 @@ function Start-GraphHelp { Param( $ModulesToGenerate = @() ) - - #Generate for auth module first + $ModulePrefix = "Microsoft.Graph" $AuthPath = "$ModulePrefix.Authentication" $AuthDestination = Join-Path $WorkLoadDocsPath "graph-powershell-1.0" $AuthPath - # Guard against catastrophic deletions: never remove existing docs unless the module - # is available to regenerate them. Authentication is required for every run, so abort - # if it failed to install rather than wiping committed documentation. - if (-not (Get-Module -Name Microsoft.Graph.Authentication -ListAvailable -ErrorAction SilentlyContinue)) { - throw "Microsoft.Graph.Authentication module is not available. Aborting generation to avoid deleting existing documentation." + # Determine which profiles this run covers. + $GraphMapping = Get-GraphMapping + $profilesToProcess = @($GraphMapping.Keys) + if ($GraphProfileFilter -ne "both") { + $profilesToProcess = @($GraphProfileFilter) } - Import-Module Microsoft.Graph.Authentication -Force -Global -ErrorAction Stop - Get-ChildItem -Path $AuthDestination * -File -Recurse | foreach { $_.Delete() } - $GraphMapping = Get-GraphMapping - $GraphMapping.Keys | ForEach-Object { - $graphProfile = $_ - $profilePath = "graph-powershell-1.0" - if ($graphProfile -eq "beta") { - $profilePath = "graph-powershell-beta" - } + # Authentication docs live under graph-powershell-1.0 and are only generated on a full run + # or when Authentication is the explicitly targeted module. + $generateAuth = ([string]::IsNullOrWhiteSpace($ModuleFilter) -or $ModuleFilter -eq "Authentication") + if ($generateAuth -and ($profilesToProcess -contains "v1.0")) { + # Guard against catastrophic deletions: never remove existing docs unless the module + # is available to regenerate them. Authentication is required for every run, so abort + # if it failed to install rather than wiping committed documentation. + if (-not (Get-Module -Name Microsoft.Graph.Authentication -ListAvailable -ErrorAction SilentlyContinue)) { + throw "Microsoft.Graph.Authentication module is not available. Aborting generation to avoid deleting existing documentation." + } + Import-Module Microsoft.Graph.Authentication -Force -Global -ErrorAction Stop + Get-ChildItem -Path $AuthDestination * -File -Recurse | foreach { $_.Delete() } $AuthenticationDocsPath = Join-Path $PSScriptRoot "..\microsoftgraph\graph-powershell-1.0" - Set-Help -ModuleDocsPath $AuthenticationDocsPath -Command "Connect-MgGraph" -Module "Microsoft.Graph.Authentication" - Get-FolderByProfile -GraphProfile $graphProfile -GraphProfilePath $profilePath -ModulePrefix $ModulePrefix -ModulesToGenerate $ModulesToGenerate + Set-Help -ModuleDocsPath $AuthenticationDocsPath -Command "Connect-MgGraph" -Module "Microsoft.Graph.Authentication" + } + + # Workload modules. Skip entirely when Authentication is the explicit single target. + if ($ModuleFilter -ne "Authentication") { + $profilesToProcess | ForEach-Object { + $graphProfile = $_ + $profilePath = "graph-powershell-1.0" + if ($graphProfile -eq "beta") { + $profilePath = "graph-powershell-beta" + } + Get-FolderByProfile -GraphProfile $graphProfile -GraphProfilePath $profilePath -ModulePrefix $ModulePrefix -ModulesToGenerate $ModulesToGenerate + } } + git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Updated markdown help" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Updated markdown help" + } else { + $global:LASTEXITCODE = 0 + } } function Get-FolderByProfile { @@ -95,6 +123,14 @@ function Get-FolderByProfile { $ModulesToGenerate = @() ) $CommandMetadataContent = Get-Content $CmdletMetadataPath | ConvertFrom-Json + + # In scoped (single-module) runs, restrict to the requested module and enable + # complete-delete + regenerate for it (safe because the module is verified to import + # below before anything is removed). + $Scoped = -not [string]::IsNullOrWhiteSpace($ModuleFilter) + if ($Scoped) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) + } $ModulesToGenerate | ForEach-Object { $ModuleName = $_ @@ -125,6 +161,12 @@ function Get-FolderByProfile { } } if (-not $ModuleUsable) { + if ($Scoped) { + # Isolated single-module run: fail fast WITHOUT deleting anything so the + # module's committed docs are preserved. The pipeline job contains this + # failure (continueOnError) so it never fails the whole run or other modules. + throw "Module $Path is not usable in isolated generation; aborting this module without deleting its documentation." + } Write-Warning "Module $Path is not usable; skipping to preserve existing documentation." return } @@ -132,6 +174,13 @@ function Get-FolderByProfile { if (-not(Test-Path $Destination)) { New-Item -Path $Destination -ItemType Directory } + elseif ($Scoped) { + # Complete deletion + regeneration for this single module. Safe here because the + # module imported successfully above, so regeneration repopulates the folder. + Get-ChildItem -Path $Destination -Filter "*.md" -File | ForEach-Object { + Remove-Item $_.FullName -Force + } + } # NOTE: docs are intentionally NOT bulk-deleted here. Each doc is overwritten in # place as it is regenerated, and only genuine orphans (commands no longer in the @@ -209,4 +258,8 @@ if ($ModulesToGenerate.Count -eq 0) { } Write-Host -ForegroundColor Green "-------------finished checking out to today's branch-------------" Start-GraphHelp -ModulesToGenerate $ModulesToGenerate +# Signal successful generation so downstream pipeline steps (post-processing, push, PR) only +# run when this module actually generated. On an import failure the script throws before this +# line, the variable is never set, and the isolated stage opens no PR and deletes nothing. +Write-Host "##vso[task.setvariable variable=ModuleGenerated]true" Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file diff --git a/scripts/GeneratePermissionsTable.ps1 b/scripts/GeneratePermissionsTable.ps1 index 47e90ae3698f8..d854d62458c62 100644 --- a/scripts/GeneratePermissionsTable.ps1 +++ b/scripts/GeneratePermissionsTable.ps1 @@ -1,6 +1,9 @@ param( [string]$MgCommandMetadatJsonFile = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src/Authentication/Authentication/custom/common/MgCommandMetadata.json"), - [string[]]$CmdList = @() + [string[]]$CmdList = @(), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Start-Generator { @@ -11,6 +14,9 @@ function Start-Generator { $CommandName = $_.Command; $ApiVersion = $_.ApiVersion $Module = $_.Module; + if ($GraphProfileFilter -ne "both" -and $ApiVersion -ne $GraphProfileFilter) { return } + $normalizedModule = $Module -replace "^Beta\.", "" + if (-not [string]::IsNullOrWhiteSpace($ModuleFilter) -and $normalizedModule -ne $ModuleFilter) { return } #Array for DelegatedWork Permissions $DelegatedWorkPermissions = @(); #Array for Application Permissions @@ -70,7 +76,12 @@ function Start-Generator { git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Inserted permissions Table" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Inserted permissions Table" + } else { + $global:LASTEXITCODE = 0 + } } catch { Write-Host "Error in $_"; diff --git a/scripts/MsSubserviceUpdate.ps1 b/scripts/MsSubserviceUpdate.ps1 index d9bbd826afe67..54ce3c003d8ca 100644 --- a/scripts/MsSubserviceUpdate.ps1 +++ b/scripts/MsSubserviceUpdate.ps1 @@ -5,7 +5,10 @@ Param( [string] $SDKDocsPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src"), [string] $SDKOpenApiPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell"), [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph"), - [string] $MissingMsSubserviceHeaderPath = (Join-Path $PSScriptRoot "../missingexternaldocsurl") + [string] $MissingMsSubserviceHeaderPath = (Join-Path $PSScriptRoot "../missingexternaldocsurl"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Start-Generator { if (Test-Path $CommandMetadataPath) { @@ -22,6 +25,8 @@ function Start-Generator { $ModulePrefix = "Microsoft.Graph.Beta" $ModuleName = $ModuleName.Replace("Beta.", "") } + if ($GraphProfileFilter -ne "both" -and $GraphProfile -ne $GraphProfileFilter) { return } + if (-not [string]::IsNullOrWhiteSpace($ModuleFilter) -and $ModuleName -ne $ModuleFilter) { return } $Path = "$ModulePrefix.$ModuleName" $DestinationFile = Join-Path $WorkLoadDocsPath $GraphProfilePath $Path "$Command.md" try { diff --git a/scripts/RemoveBoilerPlateCode.ps1 b/scripts/RemoveBoilerPlateCode.ps1 index 246e79ba3febe..8158654b106a1 100644 --- a/scripts/RemoveBoilerPlateCode.ps1 +++ b/scripts/RemoveBoilerPlateCode.ps1 @@ -4,7 +4,10 @@ Param( $ModulesToGenerate = @(), [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc"), [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph"), - [string] $AuthLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph/graph-powershell-1.0/Microsoft.Graph.Authentication") + [string] $AuthLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph/graph-powershell-1.0/Microsoft.Graph.Authentication"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Get-GraphMapping { $graphMapping = @{} @@ -19,19 +22,22 @@ function Start-Repair { $ModulesToGenerate = @() ) - #Cleanup Authentication Module first - $files = Get-ChildItem -Path $AuthLoadDocsPath -Filter *.md -Recurse - foreach ($file in $files) { - $content = Get-Content -Path $file.FullName - # Remove lines that contain '{{ Fill in the Description }}' or '### This' or '### *' or '### have' or '### certain' or '### the' - $cleanedContent = $content | Where-Object { $_ -notmatch '^\s*{{ Fill in the Description }}|^\s*### This|^\s*### \*|^\s*### have|^\s*### certain|^\s*### the' } - # Write the cleaned content back to the file - $cleanedContent | Set-Content -Path $file.FullName + #Cleanup Authentication Module first (only on a full run or the Authentication stage). + if ([string]::IsNullOrWhiteSpace($ModuleFilter) -or $ModuleFilter -eq "Authentication") { + $files = Get-ChildItem -Path $AuthLoadDocsPath -Filter *.md -Recurse + foreach ($file in $files) { + $content = Get-Content -Path $file.FullName + # Remove lines that contain '{{ Fill in the Description }}' or '### This' or '### *' or '### have' or '### certain' or '### the' + $cleanedContent = $content | Where-Object { $_ -notmatch '^\s*{{ Fill in the Description }}|^\s*### This|^\s*### \*|^\s*### have|^\s*### certain|^\s*### the' } + # Write the cleaned content back to the file + $cleanedContent | Set-Content -Path $file.FullName + } } $ModulePrefix = "Microsoft.Graph" $GraphMapping = Get-GraphMapping $GraphMapping.Keys | ForEach-Object { $graphProfile = $_ + if ($GraphProfileFilter -ne "both" -and $graphProfile -ne $GraphProfileFilter) { return } $profilePath = "graph-powershell-1.0" if ($graphProfile -eq "beta") { $profilePath = "graph-powershell-beta" @@ -41,7 +47,12 @@ function Start-Repair { git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Remove boiler plate code injected by Autorest" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Remove boiler plate code injected by Autorest" + } else { + $global:LASTEXITCODE = 0 + } } function Get-FilesByProfile { Param( @@ -118,5 +129,8 @@ if ($ModulesToGenerate.Count -eq 0) { } Write-Host -ForegroundColor Green "-------------finished checking out to today's branch-------------" +if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) +} Start-Repair -ModulesToGenerate $ModulesToGenerate Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file diff --git a/scripts/RemoveInvalidFullStops.ps1 b/scripts/RemoveInvalidFullStops.ps1 index db34789d58025..21cfad73afe51 100644 --- a/scripts/RemoveInvalidFullStops.ps1 +++ b/scripts/RemoveInvalidFullStops.ps1 @@ -2,7 +2,10 @@ # Licensed under the MIT License. Param( $ModulesToGenerate = @(), - [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc") + [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Get-GraphMapping { $graphMapping = @{} @@ -20,13 +23,19 @@ function Remove-InvalidFullStops { $GraphMapping = Get-GraphMapping $GraphMapping.Keys | ForEach-Object { $graphProfile = $_ + if ($GraphProfileFilter -ne "both" -and $graphProfile -ne $GraphProfileFilter) { return } Get-FilesByProfile -GraphProfile $graphProfile -GraphProfilePath $GraphMapping[$graphProfile] -ModulePrefix $ModulePrefix -ModulesToGenerate $ModulesToGenerate } git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Removed invalid full stops from the beginning of lines" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Removed invalid full stops from the beginning of lines" + } else { + $global:LASTEXITCODE = 0 + } } function Get-FilesByProfile { Param( @@ -88,6 +97,9 @@ if ($ModulesToGenerate.Count -eq 0) { [HashTable] $ModuleMapping = Get-Content $ModuleMappingConfigPath | ConvertFrom-Json -AsHashTable $ModulesToGenerate = $ModuleMapping.Keys } +if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) +} Remove-InvalidFullStops -ModulesToGenerate $ModulesToGenerate diff --git a/scripts/RepairExamplesDescriptionsAndTitle.ps1 b/scripts/RepairExamplesDescriptionsAndTitle.ps1 index c8e3c74074799..e1fd1d5a8f172 100644 --- a/scripts/RepairExamplesDescriptionsAndTitle.ps1 +++ b/scripts/RepairExamplesDescriptionsAndTitle.ps1 @@ -4,7 +4,10 @@ Param( $ModulesToGenerate = @(), [string] $SDKDocsPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src"), [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph"), - [string] $CommandMetadataPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src/Authentication/Authentication/custom/common/MgCommandMetadata.json") + [string] $CommandMetadataPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src/Authentication/Authentication/custom/common/MgCommandMetadata.json"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Start-Copy { @@ -22,6 +25,8 @@ function Start-Copy { $ModulePrefix = "Microsoft.Graph.Beta" $ModuleName = $ModuleName.Replace("Beta.", "") } + if ($GraphProfileFilter -ne "both" -and $GraphProfile -ne $GraphProfileFilter) { return } + if (-not [string]::IsNullOrWhiteSpace($ModuleFilter) -and $ModuleName -ne $ModuleFilter) { return } $DocPath = Join-Path $SDKDocsPath $ModuleName $GraphProfile "examples" "$Command.md" try { @@ -38,7 +43,12 @@ function Start-Copy { git config --global user.email "GraphTooling@service.microsoft.com" git config --global user.name "Microsoft Graph DevX Tooling" git add . - git commit -m "Repaired examples and descriptions" + $pending = git status --porcelain + if (-not [string]::IsNullOrWhiteSpace($pending)) { + git commit -m "Repaired examples and descriptions" + } else { + $global:LASTEXITCODE = 0 + } } diff --git a/scripts/UpdateLinks.ps1 b/scripts/UpdateLinks.ps1 index fcc8d8e2253b2..1ba011281aa6a 100644 --- a/scripts/UpdateLinks.ps1 +++ b/scripts/UpdateLinks.ps1 @@ -4,7 +4,10 @@ Param( $ModulesToGenerate = @(), [string] $ModuleMappingConfigPath = (Join-Path $PSScriptRoot "../microsoftgraph/config/ModulesMapping.jsonc"), [string] $SDKDocsPath = (Join-Path $PSScriptRoot "../msgraph-sdk-powershell/src"), - [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph") + [string] $WorkLoadDocsPath = (Join-Path $PSScriptRoot "../microsoftgraph"), + [ValidateSet("both", "v1.0", "beta")] + [string] $GraphProfileFilter = "both", + [string] $ModuleFilter = "" ) function Get-GraphMapping { $graphMapping = @{} @@ -22,6 +25,7 @@ function Start-Update { $GraphMapping = Get-GraphMapping $GraphMapping.Keys | ForEach-Object { $GraphProfile = $_ + if ($GraphProfileFilter -ne "both" -and $GraphProfile -ne $GraphProfileFilter) { return } $profilePath = "graph-powershell-1.0" if ($GraphProfile -eq "beta") { $ProfilePath = "graph-powershell-beta" @@ -205,5 +209,8 @@ if ($ModulesToGenerate.Count -eq 0) { Write-Host -ForegroundColor Green "-------------finished checking out to today's branch-------------" +if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { + $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) +} Start-Update -ModulesToGenerate $ModulesToGenerate Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file From 5202fe4d4c7f28cceeacc3451143fda9366e18b0 Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Fri, 31 Jul 2026 11:11:08 -0700 Subject: [PATCH 2/3] Fix template path resolution in powershell-docs.yml Azure DevOps resolves template references relative to the directory of the referencing file. powershell-docs.yml lives in azure-pipelines/, so the "azure-pipelines/common-templates/module-stages.yml@self" path resolved to azure-pipelines/azure-pipelines/common-templates/module-stages.yml and failed with "Not Found". Use a path relative to the file's own directory instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4d27d82f-3809-40d2-b4e7-6623436ff2ef --- azure-pipelines/powershell-docs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines/powershell-docs.yml b/azure-pipelines/powershell-docs.yml index b6dc5b22f8b9b..71679bf595944 100644 --- a/azure-pipelines/powershell-docs.yml +++ b/azure-pipelines/powershell-docs.yml @@ -47,7 +47,7 @@ extends: customBuildTags: - ES365AIMigrationTooling stages: - - template: azure-pipelines/common-templates/module-stages.yml@self + - template: common-templates/module-stages.yml@self parameters: GraphProfileFilter: "v1.0" ProfileLabel: "v1" @@ -99,7 +99,7 @@ extends: - Users.Actions - Users.Functions - WindowsUpdates - - template: azure-pipelines/common-templates/module-stages.yml@self + - template: common-templates/module-stages.yml@self parameters: GraphProfileFilter: "beta" ProfileLabel: "beta" From c49bf4e2aae84f7073a594457578adb2685eddc8 Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Fri, 31 Jul 2026 14:27:21 -0700 Subject: [PATCH 3/3] Fix Authentication stage failing at post-processing steps The Authentication module is not present in ModulesMapping.jsonc, so the scoped module filter ($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) produced an empty list. The folder-based post-processing scripts then passed that empty array into helper parameters marked [ValidateNotNullOrEmpty()] / [Parameter(Mandatory)], throwing "The argument is null or empty" and failing the Escape/UpdateLinks/RemoveInvalidFullStops/CorrectRelatedLinks steps. These scripts never post-processed Authentication in the original full run either (its docs are generated but excluded from the module list). Restore that behavior by skipping gracefully when the filtered module list is empty, so the Authentication stage succeeds and still opens its PR. RemoveBoilerPlateCode is unchanged: it intentionally runs its Authentication-specific cleanup and safely no-ops its (unvalidated) module loop. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4d27d82f-3809-40d2-b4e7-6623436ff2ef --- scripts/CorrectRelatedLinks-AllFiles.ps1 | 4 ++++ scripts/EscapeDisallowedHtmlTags.ps1 | 4 ++++ scripts/RemoveInvalidFullStops.ps1 | 4 ++++ scripts/UpdateLinks.ps1 | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/scripts/CorrectRelatedLinks-AllFiles.ps1 b/scripts/CorrectRelatedLinks-AllFiles.ps1 index be414538528a7..644b95df3cc31 100644 --- a/scripts/CorrectRelatedLinks-AllFiles.ps1 +++ b/scripts/CorrectRelatedLinks-AllFiles.ps1 @@ -224,6 +224,10 @@ if ($ModulesToGenerate.Count -eq 0) { if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) } +if ($ModulesToGenerate.Count -eq 0) { + Write-Host "No post-processing target for module '$ModuleFilter' (e.g. Authentication); skipping." + return +} # Main execution Write-Host "=== Related Links Correction Script ===" -ForegroundColor Blue diff --git a/scripts/EscapeDisallowedHtmlTags.ps1 b/scripts/EscapeDisallowedHtmlTags.ps1 index 0039a05b33500..eb960ab7610cc 100644 --- a/scripts/EscapeDisallowedHtmlTags.ps1 +++ b/scripts/EscapeDisallowedHtmlTags.ps1 @@ -334,5 +334,9 @@ Write-Host -ForegroundColor Green "-------------finished checking out to today's if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) } +if ($ModulesToGenerate.Count -eq 0) { + Write-Host "No post-processing target for module '$ModuleFilter' (e.g. Authentication); skipping." + return +} EscapeDisallowedHtmlTags -ModulesToGenerate $ModulesToGenerate Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file diff --git a/scripts/RemoveInvalidFullStops.ps1 b/scripts/RemoveInvalidFullStops.ps1 index 21cfad73afe51..c7bc5c13e0b02 100644 --- a/scripts/RemoveInvalidFullStops.ps1 +++ b/scripts/RemoveInvalidFullStops.ps1 @@ -100,6 +100,10 @@ if ($ModulesToGenerate.Count -eq 0) { if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) } +if ($ModulesToGenerate.Count -eq 0) { + Write-Host "No post-processing target for module '$ModuleFilter' (e.g. Authentication); skipping." + return +} Remove-InvalidFullStops -ModulesToGenerate $ModulesToGenerate diff --git a/scripts/UpdateLinks.ps1 b/scripts/UpdateLinks.ps1 index 1ba011281aa6a..7142f4e4ea8d6 100644 --- a/scripts/UpdateLinks.ps1 +++ b/scripts/UpdateLinks.ps1 @@ -212,5 +212,9 @@ Write-Host -ForegroundColor Green "-------------finished checking out to today's if (-not [string]::IsNullOrWhiteSpace($ModuleFilter)) { $ModulesToGenerate = @($ModulesToGenerate | Where-Object { $_ -eq $ModuleFilter }) } +if ($ModulesToGenerate.Count -eq 0) { + Write-Host "No post-processing target for module '$ModuleFilter' (e.g. Authentication); skipping." + return +} Start-Update -ModulesToGenerate $ModulesToGenerate Write-Host -ForegroundColor Green "-------------Done-------------" \ No newline at end of file