diff --git a/.github/workflows/majorrelease.yml b/.github/workflows/majorrelease.yml index ead2df8adf..bc753385dd 100644 --- a/.github/workflows/majorrelease.yml +++ b/.github/workflows/majorrelease.yml @@ -13,7 +13,9 @@ jobs: permissions: id-token: write contents: write - + + outputs: + version: ${{ steps.variables.outputs.BUILDVERSION }} steps: - name: Setup .NET uses: actions/setup-dotnet@v5 @@ -26,6 +28,20 @@ jobs: with: ref: dev token: ${{ secrets.PAT }} + # The provenance names the commit the workflow run is for, so stop before publishing anything when that is not the commit checked out + - name: Check the commit to build + shell: pwsh + run: | + $head = git rev-parse HEAD + if ($head -ne $env:GITHUB_SHA) { + throw "dev is at $head, but this workflow run is for $env:GITHUB_SHA. Start the workflow from dev again." + } + - name: Install SBOM tool + shell: pwsh + run: | + $sbomToolPath = Join-Path $env:RUNNER_TEMP "sbom-tool" + dotnet tool install Microsoft.Sbom.DotNetTool --tool-path $sbomToolPath --version 4.1.13 + "SBOM_TOOL_PATH=$sbomToolPath" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append - name: Install Sign CLI tool shell: pwsh run: | @@ -50,13 +66,73 @@ jobs: run: | ./build/Build-Release.ps1 - name: Set variables + id: variables shell: pwsh run: | $version = Get-Content version.txt -raw "BUILDVERSION=$version" | Out-File $env:GITHUB_ENV -Encoding utf8 -Append + "BUILDVERSION=$version" | Out-File $env:GITHUB_OUTPUT -Encoding utf8 -Append - name: Add & Commit uses: EndBug/add-and-commit@v10 with: message: 'Major release to PowerShell Gallery' tag: '${{env.BUILDVERSION}} --force' push: true + # The SBOM is generated here, as its packages are read from the restore this build used + - name: Package module and generate SBOM + shell: pwsh + run: | + $moduleFolder = Join-Path ([environment]::GetFolderPath("MyDocuments")) "PowerShell/Modules/PnP.PowerShell" + $releaseFolder = Join-Path $env:RUNNER_TEMP "release" + New-Item -Path $releaseFolder -ItemType Directory -Force | Out-Null + Compress-Archive -Path "$moduleFolder/*" -DestinationPath (Join-Path $releaseFolder "PnP.PowerShell-$env:BUILDVERSION.zip") -CompressionLevel Optimal + & (Join-Path $env:SBOM_TOOL_PATH "sbom-tool.exe") generate -b $moduleFolder -bc ./src/Commands -pn PnP.PowerShell -pv $env:BUILDVERSION -ps "Microsoft 365 Patterns and Practices" -nsb https://github.com/pnp/powershell -m $env:SBOM_TOOL_PATH + if ($LASTEXITCODE -ne 0) { + throw "Generating the SBOM failed" + } + Copy-Item -LiteralPath (Join-Path $env:SBOM_TOOL_PATH "_manifest/spdx_2.2/manifest.spdx.json") -Destination (Join-Path $releaseFolder "PnP.PowerShell-$env:BUILDVERSION.spdx.json") + - name: Upload release files + uses: actions/upload-artifact@v7 + with: + name: release + path: ${{ runner.temp }}/release + + # Attests and drafts the release apart from the signing environment, so that it can be rerun on its own when it fails + attest: + if: github.repository_owner == 'pnp' + needs: build + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + attestations: write + + steps: + - name: Download release files + uses: actions/download-artifact@v7 + with: + name: release + path: release + - name: Attest build provenance + id: provenance + uses: actions/attest@v4 + with: + subject-path: release/PnP.PowerShell-${{ needs.build.outputs.version }}.zip + - name: Attest SBOM + uses: actions/attest@v4 + with: + subject-path: release/PnP.PowerShell-${{ needs.build.outputs.version }}.zip + sbom-path: release/PnP.PowerShell-${{ needs.build.outputs.version }}.spdx.json + - name: Create draft GitHub release + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ needs.build.outputs.version }} + run: | + # Scorecard reads provenance from the release assets, not from the attestation store + cp "${{ steps.provenance.outputs.bundle-path }}" "release/PnP.PowerShell-$VERSION.intoto.jsonl" + gh release create "v$VERSION" release/* \ + --repo "$GITHUB_REPOSITORY" \ + --target "$GITHUB_SHA" \ + --title "Release $VERSION" \ + --notes "The zip holds the module as published to the PowerShell Gallery. Verify where it was built with \`gh attestation verify PnP.PowerShell-$VERSION.zip --repo pnp/powershell\`." \ + --draft diff --git a/.github/workflows/nightlydockerimages.yml b/.github/workflows/nightlydockerimages.yml index 788f9e373d..7c93634a17 100644 --- a/.github/workflows/nightlydockerimages.yml +++ b/.github/workflows/nightlydockerimages.yml @@ -125,9 +125,9 @@ jobs: platforms: linux/arm/v7 push: true tags: ${{ env.IMAGE_NAME }}:${{ needs.compute-version.outputs.VERSION_NIGHTLY }}-linux-arm32v7 - # Optional: pass your own build args - # build-args: | - # PNP_VERSION=${{ needs.compute-version.outputs.VERSION }} + # The image installs this version each time a container starts; without it, it falls back to the dockerfile's default + build-args: | + PNP_VERSION=${{ needs.compute-version.outputs.VERSION_NIGHTLY }} publish-docker-manifest: if: github.repository_owner == 'pnp' diff --git a/.github/workflows/stabledockerimages.yml b/.github/workflows/stabledockerimages.yml index a6b87ea9d5..697ba246c4 100644 --- a/.github/workflows/stabledockerimages.yml +++ b/.github/workflows/stabledockerimages.yml @@ -6,9 +6,23 @@ on: permissions: read-all jobs: + compute-version: + if: github.repository_owner == 'pnp' + runs-on: ubuntu-latest + outputs: + VERSION: ${{ steps.v.outputs.VERSION }} + steps: + # The images install the module from the PowerShell Gallery, so its latest stable release is the version to publish + - id: v + shell: pwsh + run: | + $version = (Find-Module -Name PnP.PowerShell -Repository PSGallery).Version + "VERSION=$version" | Out-File $env:GITHUB_OUTPUT -Encoding utf8 -Append + publish-docker-windows-amd64: if: github.repository_owner == 'pnp' runs-on: windows-2025 + needs: compute-version steps: - name: Checkout main branch uses: actions/checkout@v6 @@ -16,32 +30,47 @@ jobs: ref: main - name: Build an image run: | - $VERSION="$(cat ./version.txt)" + $VERSION="${{ needs.compute-version.outputs.VERSION }}" docker build --build-arg PNP_VERSION=$VERSION --platform windows/amd64 ./docker -f ./docker/windows-amd64.dockerfile --tag ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-windows-amd64 - name: Push the image run: | - $VERSION="$(cat ./version.txt)" + $VERSION="${{ needs.compute-version.outputs.VERSION }}" docker login -u ${{ secrets.DOCKER_USERNAME }} -p '${{ secrets.DOCKER_PASSWORD }}' docker push "${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-windows-amd64" - publish-docker-linux-arm32: - runs-on: ubuntu-22.04 - if: false + publish-docker-linux-arm32v7: + if: github.repository_owner == 'pnp' + runs-on: ubuntu-latest + needs: compute-version steps: - - uses: actions/checkout@v6 - - name: Build an image - run: | - VERSION="$(cat ./version.txt)" - docker build --build-arg PNP_VERSION=$VERSION ./docker -f ./docker/pnppowershell.dockerFile --tag ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-ubuntu-22.04-arm32; - - name: Push the image - run: | - VERSION="$(cat ./version.txt)" - docker login -u ${{ secrets.DOCKER_USERNAME }} -p '${{ secrets.DOCKER_PASSWORD }}' - docker push "${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-ubuntu-22.04-arm32" + - name: Checkout main branch + uses: actions/checkout@v6 + with: + ref: main + # Buildx emulates arm32 on the x64 runner; the image installs the module each time a container starts + - uses: docker/setup-buildx-action@v4 + - uses: docker/login-action@v4 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build & push the image + uses: docker/build-push-action@v7 + with: + context: ./docker + file: ./docker/linux-arm32.dockerfile + platforms: linux/arm/v7 + push: true + tags: ${{ secrets.DOCKER_ORG }}/powershell:${{ needs.compute-version.outputs.VERSION }}-stable-linux-arm32v7 + build-args: | + PNP_VERSION=${{ needs.compute-version.outputs.VERSION }} + # Without attestations the tag is a single image manifest, which docker manifest create can amend + provenance: false + sbom: false publish-docker-linux-arm64: if: github.repository_owner == 'pnp' runs-on: ubuntu-24.04-arm + needs: compute-version steps: - name: Checkout main branch uses: actions/checkout@v6 @@ -49,17 +78,18 @@ jobs: ref: main - name: Build an image run: | - VERSION="$(cat ./version.txt)" + VERSION="${{ needs.compute-version.outputs.VERSION }}" docker build --build-arg PNP_VERSION=$VERSION --platform linux/arm64/v8 ./docker -f ./docker/linux-arm64.dockerfile --tag ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-linux-arm64 - name: Push the image run: | - VERSION="$(cat ./version.txt)" + VERSION="${{ needs.compute-version.outputs.VERSION }}" docker login -u ${{ secrets.DOCKER_USERNAME }} -p '${{ secrets.DOCKER_PASSWORD }}' docker push "${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-linux-arm64" publish-docker-linux-amd64: if: github.repository_owner == 'pnp' runs-on: ubuntu-latest + needs: compute-version steps: - name: Checkout main branch uses: actions/checkout@v6 @@ -67,42 +97,38 @@ jobs: ref: main - name: Build an image run: | - VERSION="$(cat ./version.txt)" + VERSION="${{ needs.compute-version.outputs.VERSION }}" docker build --build-arg PNP_VERSION=$VERSION --platform linux/amd64 ./docker -f ./docker/linux-amd64.dockerfile --tag ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-linux-amd64 - name: Push the image run: | - VERSION="$(cat ./version.txt)" + VERSION="${{ needs.compute-version.outputs.VERSION }}" docker login -u ${{ secrets.DOCKER_USERNAME }} -p '${{ secrets.DOCKER_PASSWORD }}' docker push "${{ secrets.DOCKER_ORG }}/powershell:$VERSION-stable-linux-amd64" publish-docker-manifest: if: github.repository_owner == 'pnp' runs-on: ubuntu-latest - needs: [ publish-docker-linux-arm64, publish-docker-linux-amd64, publish-docker-windows-amd64 ] + needs: [ compute-version, publish-docker-linux-arm32v7, publish-docker-linux-arm64, publish-docker-linux-amd64, publish-docker-windows-amd64 ] steps: - - name: Checkout main branch - uses: actions/checkout@v6 - with: - ref: main - name: Publish manifest run: | - VERSION="$(cat ./version.txt)-stable" + VERSION="${{ needs.compute-version.outputs.VERSION }}-stable" docker login -u ${{ secrets.DOCKER_USERNAME }} -p '${{ secrets.DOCKER_PASSWORD }}' docker manifest create ${{ secrets.DOCKER_ORG }}/powershell:$VERSION \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-amd64 \ - --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32 \ + --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32v7 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm64 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-windows-amd64 docker manifest push ${{ secrets.DOCKER_ORG }}/powershell:$VERSION docker manifest create ${{ secrets.DOCKER_ORG }}/powershell:stable \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-amd64 \ - --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32 \ + --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32v7 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm64 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-windows-amd64 docker manifest push ${{ secrets.DOCKER_ORG }}/powershell:stable docker manifest create ${{ secrets.DOCKER_ORG }}/powershell:latest \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-amd64 \ - --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32 \ + --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm32v7 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-linux-arm64 \ --amend ${{ secrets.DOCKER_ORG }}/powershell:$VERSION-windows-amd64 docker manifest push ${{ secrets.DOCKER_ORG }}/powershell:latest diff --git a/CHANGELOG.md b/CHANGELOG.md index bd90775b8e..bd3992ef7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,22 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - Added `Get-PnPPersistedLogin` which lists the tenant url, client id and authentication type of every login registered to use the local token cache, so the cache can be inspected without reading it. [#5463](https://github.com/pnp/powershell/pull/5463) - Added `-PersistLogin` to the certificate based app only parameter sets of `Connect-PnPOnline`, so a connection made with `-CertificatePath`, `-CertificateBase64Encoded`, `-Thumbprint` or the environment variables can reuse its access token from the local cache. The certificate, tenant and client id are still required on each connection, as neither the certificate nor its password is stored. [#5463](https://github.com/pnp/powershell/pull/5463) - Added `-CopilotSearchOptOut` to `Set-PnPSearchSettings` and exposed the value through `Get-PnPSearchSettings`; added `-CopilotSearchOptIn` to `Set-PnPTenant` and exposed the value through `Get-PnPTenant`. [#5478](https://github.com/pnp/powershell/pull/5478) +- Added a zip of the module, its SPDX software bill of materials and its build provenance to each stable release on GitHub, so the zip can be verified with `gh attestation verify`. [#5483](https://github.com/pnp/powershell/pull/5483) +- Added `-AzureEnvironment` and `-MicrosoftGraphEndPoint` to `Connect-PnPOnline -AzureADWorkloadIdentity`, so a connection through a workload identity calls Microsoft Graph and the other APIs of a national cloud instead of the worldwide ones. [#5483](https://github.com/pnp/powershell/pull/5483) ### Changed - Changed `Connect-PnPOnline` to write verbose message on which stored credential it resolved for the url, as it previously picked one up from the credential manager without saying so. [#5463](https://github.com/pnp/powershell/pull/5463) - Changed `Disconnect-PnPOnline -ClearPersistedLogin` to write a warning when no persisted login exists for the current connection, instead of silently doing nothing. [#5463](https://github.com/pnp/powershell/pull/5463) - Telemetry in PnP PowerShell has been removed due to the costs of collecting the data didn't outweigh the benefits to the PnP PowerShell team to have insights into its usage. The involved cmdlets `Get-PnPPowerShellTelemetryEnabled`, `Enable-PnPPowerShellTelemetry` and `Disable-PnPPowerShellTelemetry` have been marked as deprecated and no longer function, but will stay in v3 for backwards compatibility with existing scripts. These cmdlets will be removed in the next v4 release. All versions of PnP PowerShell will no longer be able to submit telemetry. You might see background requests for this failing. This will not interfear with the normal execution of your PowerShell script. [#5460](https://github.com/pnp/powershell/pull/5460) +- Changed `Disable-PnPFeature` to mark `-Force` as obsolete, as it never had an effect. Using it now writes a warning. [#5483](https://github.com/pnp/powershell/pull/5483) +- Changed `Get-PnPUnifiedAuditLog` to call the Office 365 Management API of the cloud given with `Connect-PnPOnline -AzureEnvironment` for `USGovernment`, `USGovernmentHigh` and `USGovernmentDoD`, instead of always calling `manage.office.com`. [#5483](https://github.com/pnp/powershell/pull/5483) +- Changed `Connect-PnPOnline -ManagedIdentity` and `Connect-PnPOnline -AccessToken` to keep the cloud given with `-AzureEnvironment`, so the cmdlets that look it up, such as `Get-PnPEntraIDUser`, `Get-PnPUnifiedAuditLog` and those calling Power Platform or Azure Resource Manager APIs, call that cloud instead of the worldwide one. Microsoft Graph requests on such a connection now go to that cloud as well, also without `-Url`. [#5483](https://github.com/pnp/powershell/pull/5483) ### Fixed - Using UPNs with an apostrophe in it not working with `Remove-PnPUserProfile`, `Export-PnPUserProfile`, `Export-PnPUserInfo`, and `Remove-PnPUserInfo`. The apostrophe is now escaped in the API request. [#5459](https://github.com/pnp/powershell/pull/5459) - Fix PnP ALC initializer with loaded assemblies stackoverflow issue. [#5481](https://github.com/pnp/powershell/pull/5481) +- Fixed the stable Docker images stopping at 3.1.0: `m365pnp/powershell:latest` and `m365pnp/powershell:stable` are published again, for the latest stable release on the PowerShell Gallery, now also for 32 bit ARM (`linux/arm/v7`). [#5483](https://github.com/pnp/powershell/pull/5483) +- Fixed `Get-Help Move-PnPItemProxy` returning only the syntax of the cmdlet, as it had no documentation page. [#5483](https://github.com/pnp/powershell/pull/5483) ### Contributors diff --git a/README.md b/README.md index 819a7890d0..c585eaefbf 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ This module is a successor of the [PnP-PowerShell](https://github.com/pnp/pnp-po For more information about installing or upgrading to this module, please refer to [the documentation](https://pnp.github.io/powershell/articles/index.html). +To use PnP PowerShell from an AI assistant such as GitHub Copilot or Claude, see the [PnP PowerShell MCP server](https://pnp.github.io/powershell/articles/mcpserver.html). + ## IMPORTANT - New PnP PowerShell 3.x We released a new major version of PnP PowerShell, version 3 and upwards. This version of PnP PowerShell requires as of today PowerShell 7.4.0 or newer, and is based upon .NET 8.0. diff --git a/documentation/Connect-PnPOnline.md b/documentation/Connect-PnPOnline.md index fa4c7806b3..fe66afdbfc 100644 --- a/documentation/Connect-PnPOnline.md +++ b/documentation/Connect-PnPOnline.md @@ -74,22 +74,22 @@ Connect-PnPOnline -Url -AccessToken [-AzureEnvironment ] -ManagedIdentity [-ReturnConnection] +Connect-PnPOnline [-Url ] -ManagedIdentity [-AzureEnvironment ] [-ReturnConnection] ``` ### User Assigned Managed Identity by Client Id ```powershell -Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityClientId [-ReturnConnection] +Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityClientId [-AzureEnvironment ] [-ReturnConnection] ``` ### User Assigned Managed Identity by Principal Id ```powershell -Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityObjectId [-ReturnConnection] +Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityObjectId [-AzureEnvironment ] [-ReturnConnection] ``` ### User Assigned Managed Identity by Azure Resource Id ```powershell -Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityAzureResourceId [-ReturnConnection] +Connect-PnPOnline [-Url ] -ManagedIdentity -UserAssignedManagedIdentityAzureResourceId [-AzureEnvironment ] [-ReturnConnection] ``` ### Environment Variable @@ -103,7 +103,7 @@ Connect-PnPOnline [-ReturnConnection] [-Url] -EnvironmentVariable [-Per ### Azure AD Workload Identity ```powershell Connect-PnPOnline [-ReturnConnection] [-ValidateConnection] [-Url] - [-AzureADWorkloadIdentity] [-Connection ] + [-AzureADWorkloadIdentity] [-AzureEnvironment ] [-MicrosoftGraphEndPoint ] [-Connection ] ``` ### OS login @@ -349,7 +349,7 @@ The Azure environment to use for authentication, the defaults to 'Production' wh ```yaml Type: AzureEnvironment -Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, Managed Identity, Federated Identity +Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, OS login, System Assigned Managed Identity, User Assigned Managed Identity by Client Id, User Assigned Managed Identity by Principal Id, User Assigned Managed Identity by Azure Resource Id, Federated Identity, Azure AD Workload Identity Aliases: Accepted values: Production, PPE, China, Germany, USGovernment, USGovernmentHigh, USGovernmentDoD, BleuCloud, DelosCloud, GovSGCloud, Custom @@ -836,7 +836,7 @@ Custom Microsoft Graph endpoint to be used if we are using Azure Custom environm ```yaml Type: String -Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, Federated Identity, OS Login +Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, OS login, System Assigned Managed Identity, User Assigned Managed Identity by Client Id, User Assigned Managed Identity by Principal Id, User Assigned Managed Identity by Azure Resource Id, Federated Identity, Azure AD Workload Identity Aliases: Required: False @@ -851,7 +851,7 @@ Custom Azure AD login endpoint to be used if we are using Azure Custom environme ```yaml Type: String -Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, Federated Identity +Parameter Sets: Credentials, SharePoint ACS (Legacy) App Only, App-Only with Azure Active Directory, App-Only with Azure Active Directory using a certificate from the Windows Certificate Management Store by thumbprint, DeviceLogin, Interactive, Access Token, Environment Variable, OS login, System Assigned Managed Identity, User Assigned Managed Identity by Client Id, User Assigned Managed Identity by Principal Id, User Assigned Managed Identity by Azure Resource Id, Federated Identity Aliases: Required: False diff --git a/documentation/Disable-PnPFeature.md b/documentation/Disable-PnPFeature.md index 6635bbc287..31c851670b 100644 --- a/documentation/Disable-PnPFeature.md +++ b/documentation/Disable-PnPFeature.md @@ -33,13 +33,6 @@ This will disable the feature with the id "99a00f6e-fb81-4dc7-8eac-e09c6f9132fe" ### EXAMPLE 2 ```powershell -Disable-PnPFeature -Identity 99a00f6e-fb81-4dc7-8eac-e09c6f9132fe -Force -``` - -This will disable the feature with the id "99a00f6e-fb81-4dc7-8eac-e09c6f9132fe" with force. - -### EXAMPLE 3 -```powershell Disable-PnPFeature -Identity 99a00f6e-fb81-4dc7-8eac-e09c6f9132fe -Scope Web ``` @@ -62,7 +55,7 @@ Accept wildcard characters: False ``` ### -Force -Specifies whether to continue if an error occurs when deactivating the feature. +**This parameter is obsolete and has no effect. It will be removed in a future version.** ```yaml Type: SwitchParameter diff --git a/documentation/Get-PnPTraceLog.md b/documentation/Get-PnPTraceLog.md index e6a1cafd28..f8951ac9bc 100644 --- a/documentation/Get-PnPTraceLog.md +++ b/documentation/Get-PnPTraceLog.md @@ -26,7 +26,7 @@ Get-PnPTraceLog [-Verbose] ``` ## DESCRIPTION -This cmdlet returns the logged messages during the execution of PnP PowerShell cmdlets. It can return the messages from an in memory log stream or from a file. Note that you cannot read from a log file if it is currently in use to write to. In this case, you would first have to stop logging to it using [Stop-PnPTraceLog](Stop-PnPTraceLog.md) and then read the log file. The in memory log stream is always available. +This cmdlet returns the logged messages during the execution of PnP PowerShell cmdlets. It can return the messages from an in memory log stream or from a file. Note that you cannot read from a log file if it is currently in use to write to. In this case, you would first have to stop logging to it using [Stop-PnPTraceLog](Stop-PnPTraceLog.md) and then read the log file. The in memory log stream is only available after starting it with `Start-PnPTraceLog -WriteToLogStream`. You can use [Start-PnPTraceLog](Start-PnPTraceLog.md) to start logging to a file and/or to an in memory stream. diff --git a/documentation/Get-PnPUnifiedAuditLog.md b/documentation/Get-PnPUnifiedAuditLog.md index 27f4a35a03..3639f676f1 100644 --- a/documentation/Get-PnPUnifiedAuditLog.md +++ b/documentation/Get-PnPUnifiedAuditLog.md @@ -34,6 +34,8 @@ Get-PnPUnifiedAuditLog [-ContentType ] [-StartTime ] Allows to retrieve unified audit logs from the Office 365 Management API. +The cmdlet calls the Office 365 Management API of the cloud you connected to with `Connect-PnPOnline -AzureEnvironment`: `manage-gcc.office.com` for `USGovernment`, `manage.office365.us` for `USGovernmentHigh`, `manage.protection.apps.mil` for `USGovernmentDoD`, and `manage.office.com` for any other value. + ### Prerequisites Your Entra app registration must have one or more of the following delegated or application permissions from the Office 365 Management API. To add this permission using Azure CLI: diff --git a/documentation/Move-PnPItemProxy.md b/documentation/Move-PnPItemProxy.md new file mode 100644 index 0000000000..5a3c1bdda3 --- /dev/null +++ b/documentation/Move-PnPItemProxy.md @@ -0,0 +1,241 @@ +--- +Module Name: PnP.PowerShell +schema: 2.0.0 +applicable: SharePoint Online +online version: https://pnp.github.io/powershell/cmdlets/Move-PnPItemProxy.html +external help file: PnP.PowerShell.dll-Help.xml +title: Move-PnPItemProxy +--- + +# Move-PnPItemProxy + +## SYNOPSIS +Moves files and folders between the local file system and a SharePoint drive. It is used through the `Move-Item` alias. + +## SYNTAX + +### Path (Default) +```powershell +Move-PnPItemProxy [-Path] [[-Destination] ] [-Container] [-Force] [-Filter ] + [-Include ] [-Exclude ] [-PassThru] [-Credential ] [-WhatIf] [-Confirm] +``` + +### LiteralPath +```powershell +Move-PnPItemProxy [-LiteralPath] [[-Destination] ] [-Container] [-Force] [-Filter ] + [-Include ] [-Exclude ] [-PassThru] [-Credential ] [-WhatIf] [-Confirm] +``` + +## DESCRIPTION + +**This cmdlet stands in for the `Move-Item` cmdlet that is natively available with PowerShell.** + +When a SharePoint drive is created, for instance with `Connect-PnPOnline -CreateDrive`, PnP PowerShell points the `Move-Item` alias to `Move-PnPItemProxy` for the session. Removing the last SharePoint drive removes the alias again, and a drive created with `New-PSDrive -PSProvider SharePoint -NoProxyCmdLets` leaves it alone. Paths on the drive are server relative: on a drive named SPO, the library at https://contoso.sharepoint.com/sites/project/Shared%20Documents is `SPO:\sites\project\Shared Documents`. + +When items are moved from the file system to a SharePoint drive or from a SharePoint drive to the file system, folders are moved with all of their content, and the source is removed once everything has been copied. A file or folder removed from SharePoint this way is deleted permanently, not sent to the recycle bin, and without `-Force` that includes a file that was not downloaded because a file with the same name already existed locally. Any other move, including one between two locations in SharePoint, is passed on to `Move-Item`. + +> [!WARNING] +> A move between the file system and a SharePoint drive removes the whole source, also the parts it did not copy: +> +> - from a SharePoint folder holding more than 5,000 items, only the first 100 are copied +> - hidden files in a local folder are not copied +> - a local source given as a UNC path, or on a drive other than a drive letter such as `Temp:`, is not copied at all +> - without `-Force`, a file that already exists locally is not downloaded +> - `-LiteralPath` expands wildcard characters, so `-LiteralPath "file[1].txt"` moves `file1.txt` +> +> To keep the source until you have checked the result, copy with `Copy-PnPItemProxy` and remove the source afterwards. + +For more information on `Move-Item`, please refer to the official PowerShell documentation [here](https://learn.microsoft.com/powershell/module/microsoft.powershell.management/move-item). + +## EXAMPLES + +### EXAMPLE 1 +```powershell +Move-Item -Path "C:\Reports\*.xlsx" -Destination "SPO:\sites\project\Shared Documents\Reports" +``` + +Uploads all Excel files in the local `C:\Reports` folder to the Reports folder of the Shared Documents library of the site at /sites/project, then deletes them from `C:\Reports`. + +### EXAMPLE 2 +```powershell +Move-PnPItemProxy -Path "SPO:\sites\project\Shared Documents\Archive" -Destination "C:\Archive" -Force +``` + +Downloads the Archive folder to `C:\Archive`, overwriting local files with the same name, then permanently deletes the Archive folder from SharePoint. See the warning above for the content that is not downloaded. + +## PARAMETERS + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Container +Treats the last segment of `-Destination` as a folder, even when it contains a period. Without it, a destination whose last segment contains a period is taken to be a file name. Only used when moving between the file system and a SharePoint drive. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Credential +Passed on to `Move-Item`. Not used when moving between the file system and a SharePoint drive. + +```yaml +Type: PSCredential +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Destination +The path to move the items to. When it is a file name, only a single file can be moved to it. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: 1 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Exclude +Passed on to `Move-Item`. Not used when moving between the file system and a SharePoint drive. + +```yaml +Type: String[] +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Filter +Passed on to `Move-Item`. Not used when moving between the file system and a SharePoint drive. + +```yaml +Type: String +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +Overwrites files that already exist at the destination. Without it, a move to a SharePoint drive stops at the first file that already exists there. A move from a SharePoint drive to the file system, however, does not download a file that already exists locally, and still deletes it from SharePoint. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Include +Passed on to `Move-Item`. Not used when moving between the file system and a SharePoint drive. + +```yaml +Type: String[] +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -LiteralPath +The path of the items to move. + +```yaml +Type: String[] +Parameter Sets: LiteralPath +Aliases: PSPath + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PassThru +Returns objects for the files and folders created at the destination. By default, this cmdlet does not generate any output. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Path +The path of the items to move. Wildcard characters are permitted. + +```yaml +Type: String[] +Parameter Sets: Path + +Required: True +Position: 0 +Default value: None +Accept pipeline input: True (ByValue, ByPropertyName) +Accept wildcard characters: True +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. The cmdlet is not run. + +```yaml +Type: SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +## RELATED LINKS + +[Microsoft 365 Patterns and Practices](https://aka.ms/m365pnp) diff --git a/documentation/Stop-PnPTraceLog.md b/documentation/Stop-PnPTraceLog.md index 53e42c10e3..cc5880035f 100644 --- a/documentation/Stop-PnPTraceLog.md +++ b/documentation/Stop-PnPTraceLog.md @@ -19,7 +19,7 @@ Stop-PnPTraceLog [-StopFileLogging ] [-StopConsoleLogging [-Verbose] -``` -### Log from log stream - -```powershell -Write-PnPTraceLog [-Verbose] +Write-PnPTraceLog [-Message] [-CorrelationId ] [-Source ] [-EllapsedMilliseconds ] [-Level ] [-Verbose] ``` ## DESCRIPTION @@ -92,7 +85,7 @@ Accept wildcard characters: False ``` ### -Level -The level to log your message under. Options are: Verbose, Information, Warning, Error and Debug. It will log it both to the default PowerShell its logging equivallent such as Write-Warning, Write-Error, Write-Verbose, and to the PnPTraceLog logging. If not provided, it will default to Information. +The level to log your message under. Options are: Debug, Information, Warning and Error. It will log it both to the default PowerShell its logging equivalent such as Write-Warning, Write-Error, Write-Verbose, and to the PnPTraceLog logging. If not provided, it will default to Information. ```yaml Type: Framework.Diagnostics.LogLevel @@ -113,9 +106,9 @@ Type: String Parameter Sets: (All) Required: True -Position: Named +Position: 0 Default value: None -Accept pipeline input: True +Accept pipeline input: True (ByValue) Accept wildcard characters: False ``` diff --git a/pages/articles/authentication.md b/pages/articles/authentication.md index ab4037c75d..d5b2dc6cbc 100644 --- a/pages/articles/authentication.md +++ b/pages/articles/authentication.md @@ -172,15 +172,15 @@ Connect-PnPOnline [yourtenant].sharepoint.com -ClientId [!Important] - > Currently the only stable PnP PowerShell version that works with Azure Automation 7.2 Runbooks is **2.12.0**. Later versions are currently not supported. - > If you would like to use a [latest nightly build](#latest-prerelease-version) instead, use the below instructions - - Select **Browse from gallery**, Runtime version **7.2 (recommended)** and click on the **Click here to browse from gallery** link - - ![Add a module](../images/azureautomation/addmodulefromgallery.png) - - Search for PnP PowerShell and select the first result. - - ![Add the PnP PowerShell module](../images/azureautomation/automationaddmodulepnpposh.png) - - Click on **Select** to confirm. +PnP PowerShell 3.x requires PowerShell 7.4 or later, so it runs on the PowerShell 7.4 and 7.6 runtime versions of Azure Automation. These are only available through a [Runtime environment](https://learn.microsoft.com/azure/automation/runtime-environment-overview). The PowerShell 7.1 and 7.2 runtime versions, which only run PnP PowerShell 2.12.0 or older, are no longer supported by Azure Automation since 30 September 2026. PowerShell 7.4 itself reaches end of support on 10 November 2026, so use 7.6 for new runbooks. - ![Confirm adding the PnP PowerShell module](../images/azureautomation/automationaddmodulepnpposhconfirm.png) - - Click on **Import** to start the download and importing process. +To add PnP PowerShell to the Azure Automation Account, follow these steps: - ![Start importing the PnP PowerShell module](../images/azureautomation/automationaddmodulepnpposhimport.png) +1. In your Azure Automation Account, select **Runtime Environments** under **Process Automation**. If it is not there, first select **Try Runtime environment experience** on the **Overview** page. - It will take up to 10 minutes for the import to complete. You can check the import status by changing the **Module type** filter to **Custom**. +1. Select **Create**, enter a name for the Runtime environment, select **PowerShell** as the **Language** and **7.6** as the **Runtime version**, and select **Next**. - ![Check the import status](../images/azureautomation/automationaddmodulepnpposhstatus.png) +1. On the **Packages** tab, add PnP PowerShell using one of the following options, then select **Next** and **Create**. Importing the module can take several minutes. - Once it's done, it will show the status **Available** +#### Stable version - ![Import done](../images/azureautomation/automationaddmodulepnpposhdone.png) + Select **Add from gallery**, search for **PnP.PowerShell** and select it. #### Latest prerelease version @@ -78,25 +50,7 @@ To add PnP PowerShell to the Azure Automation Account, follow these steps: Save-Module PnP.PowerShell -AllowPrerelease -Path c:\temp ``` - ![Download the PnP PowerShell package](../images/azureautomation/pwshdownloadcustombuild.png) - - Using Windows File Explorer, go to the folder where you downloaded the PnP PowerShell package. You should see a folder called `PnP.PowerShell` in there. Right click on it and choose the option **Compress to ZIP file**. - - ![Compress the PnP PowerShell package](../images/azureautomation/explorerzipcustombuild.png) - - Select **Browse for file**, Runtime version **7.2 (recommended)** and click on the folder icon next to **Powershell module file** and select the zipped up PnP.PowerShell.zip file generated in the previous step. - - ![Upload module file](../images/azureautomation/addmodulefromgallerycustombuild.png) - - Click on **Import** to start the download and importing process. - - It will take up to 10 minutes for the import to complete. You can check the import status by changing the **Module type** filter to **Custom**. - - ![Check the import status](../images/azureautomation/automationaddmodulepnpposhcustombuildstatus.png) - - Once it's done, it will show the status **Available** - - ![Import done](../images/azureautomation/automationaddmodulepnpposhcustomdone.png) + This creates a folder `c:\temp\PnP.PowerShell` holding a folder named after the version number. Rename that version folder to `PnP.PowerShell`, as Azure Automation only imports a module from a folder that has the name of the module, and compress it into a ZIP file. Select **Add a file** and select the ZIP file. ## Decide how you want to authenticate in your Azure Automation Runbooks @@ -144,9 +98,7 @@ We're now ready to create a Runbook in which your PnP PowerShell script will run ![Create a Runbook](../images/azureautomation/azureportaladdrunbookoption.png) -1. Give the Runbook a name, select the Runbook type **PowerShell** and for the Runtime version choose **7.2 (recommended)** and click on **Create** at the bottom left. - - ![Provide Runbook creation paramters](../images/azureautomation/azureportalcreaterunbook.png) +1. Give the Runbook a name, select the Runbook type **PowerShell**, select the Runtime environment you created in which PnP PowerShell has been added and click on **Create** at the bottom left. 1. On the Edit PowerShell Runbook page, enter your PnP PowerShell code in the large white area, i.e.: diff --git a/pages/articles/azurefunctions.md b/pages/articles/azurefunctions.md index 9e5d31ba4d..e4b45e4c04 100644 --- a/pages/articles/azurefunctions.md +++ b/pages/articles/azurefunctions.md @@ -17,7 +17,7 @@ As the UI in [the Azure Portal](https://portal.azure.com) changes every now and ![Creating a function app resource](./../images/azurefunctions/createfunctionappresource.png) -1. Choose runtime stack `PowerShell Core` and version `7.4` (7.0 is not longer an option as of December 3rd, 2022) +1. Choose runtime stack `PowerShell Core` and version `7.6`. PnP PowerShell 3.x requires PowerShell 7.4 or later, and PowerShell 7.4 reaches end of support on 10 November 2026. The Linux Consumption plan does not offer 7.6; use the Flex Consumption plan instead. ![Create function app basics](./../images/azurefunctions/createfunctionappbasics2.png) @@ -68,14 +68,17 @@ The Azure Function comes with the Azure cmdlets pre-installed. If you don't need 1. Add a new entry or replace the whole contents of the file with one of the following and remember to save the `requirements.psd1` file: + > [!Note] + > The Flex Consumption plan does not install the modules listed in `requirements.psd1`. On that plan, include PnP PowerShell in your app content instead, as described in [Including modules in app content](https://learn.microsoft.com/azure/azure-functions/functions-reference-powershell#including-modules-in-app-content). + #### Specific stable version > [!Important] - > PnP PowerShell version 2 or later is required for this to work + > Use PnP PowerShell 3.4.0 or later. Earlier versions can fail in `Connect-PnPOnline` with `Method not found: 'Microsoft.Extensions.DependencyInjection.IServiceCollection Microsoft.Extensions.DependencyInjection.OptionsServiceCollectionExtensions.AddOptions(...)'` on current Azure Functions hosts, see [#5350](https://github.com/pnp/powershell/issues/5350). ```powershell @{ - 'PnP.PowerShell' = '2.12.0' + 'PnP.PowerShell' = '3.4.1' } ``` @@ -83,18 +86,15 @@ The Azure Function comes with the Azure cmdlets pre-installed. If you don't need #### Latest stable version - > [!Important] - > PnP PowerShell version 2 or later is required for this to work - If, for some reason, you would like to ensure it is always using the latest available PnP PowerShell version, you can also specify a wildcard in the version (not recommended): ```powershell @{ - 'PnP.PowerShell' = '2.*' + 'PnP.PowerShell' = '3.*' } ``` - This will then automatically download any minor version of the major 1 release when available. Note that wildcards will always take the latest stable version and not the nightly build/prerelease versions. + This will then automatically download any minor version of the major 3 release when available. Note that wildcards will always take the latest stable version and not the nightly build/prerelease versions. #### Specific prerelease version @@ -102,7 +102,7 @@ The Azure Function comes with the Azure cmdlets pre-installed. If you don't need ```powershell @{ - 'PnP.PowerShell' = '2.99.50-nightly' + 'PnP.PowerShell' = '3.4.46-nightly' } ``` diff --git a/pages/articles/determinepermissions.md b/pages/articles/determinepermissions.md index 9028f345a9..5aac306062 100644 --- a/pages/articles/determinepermissions.md +++ b/pages/articles/determinepermissions.md @@ -187,11 +187,15 @@ For an app only scenario, you will have to follow a different approach, as there The quickest way to find out what to add is to run [Get-PnPCommandPermission](../cmdlets/Get-PnPCommandPermission.md) against the cmdlet that failed, as described [earlier in this article](#asking-pnp-powershell-which-permissions-a-cmdlet-needs). It requires no connection, so you can run it before ever hitting the access denied. -Alternatively you can add `-Verbose` to your cmdlet. For many, but unfortunately not all, cmdlets, this will reveal which permissions it receives through the application registration and which permissions it actually needs to be able to execute properly. See the following example: +Alternatively you can start the [trace log](logging.md) before running the cmdlet. For many, but unfortunately not all, cmdlets, PnP PowerShell compares the permissions in the access token with the permissions the cmdlet needs, and writes the ones that are missing to the trace log as an error. `-Verbose` does not show them. See the following example: -![image](../images/determinepermissions/entraid_permissions_accessdenied_verbose.png) +```powershell +Start-PnPTraceLog -WriteToLogStream +Get-PnPEntraIDApp +Get-PnPTraceLog | Where-Object Level -eq Error +``` -In this scenario, you now know you need to add `Application.Read.All` on the applications scope of Microsoft Graph in your application registration in order to give it sufficient rights to execute this cmdlet. +If the application registration lacks the permission, this returns an entry with the message `Current access token lacks the following required application permission scope on the resource Microsoft Graph: Application.Read.All`. In this scenario, you now know you need to add `Application.Read.All` on the applications scope of Microsoft Graph in your application registration in order to give it sufficient rights to execute this cmdlet. ## Help, I can't figure out which permissions I need diff --git a/pages/articles/docker.md b/pages/articles/docker.md index 3b818b9d5b..8c94dd4b13 100644 --- a/pages/articles/docker.md +++ b/pages/articles/docker.md @@ -100,13 +100,13 @@ After that you can start running commands like `Connect-PnPOnline`. If you want to run PnP.PowerShell commands interactively: -- Latest stable version (i.e. 3.1.0) +- Latest stable version ```bash docker run --rm -it m365pnp/powershell:latest ``` -- Latest nightly version (i.e. 3.1.127-nightly) +- Latest nightly version ```bash docker run --rm -it m365pnp/powershell:nightly @@ -140,13 +140,13 @@ Please see [Docker documentation](https://docs.docker.com/engine/reference/run/) ### Latest -* latest: The latest stable image (i.e. 3.1.0) +* latest: The latest stable image * `docker pull m365pnp/powershell:stable` or `docker pull m365pnp/powershell:latest` or even more simple just `docker pull m365pnp/powershell` ### Nightly -* nightly: The latest nightly image (i.e. 3.1.127-nightly) +* nightly: The latest nightly image * `docker pull m365pnp/powershell:nightly` @@ -158,10 +158,10 @@ Tags names mean the following: Currently supported architectures: -* [windows-amd64](/pnp/powershell/blob/dev/docker/windows-amd64.dockerfile): Windows NanoServer LTSC 2025 64 bits -* [linux-arm32](/pnp/powershell/blob/dev/docker/linux-arm32.dockerfile): .NET 9 SDK 32 bit (i.e. Raspberry Pi 2 v1.1 or older running 32 bits Linux) -* [linux-arm64](/pnp/powershell/blob/dev/docker/linux-arm64.dockerfile): Linux Debian Bullseye Slim 64 bits for ARM devices (i.e. Raspberry Pi 2 v1.2 or later running 64 bits Linux) -* [linux-amd64](/pnp/powershell/blob/dev/docker/linux-amd64.dockerfile): Alpine 64 bits +* [windows-amd64](https://github.com/pnp/powershell/blob/dev/docker/windows-amd64.dockerfile): Windows NanoServer LTSC 2025 64 bits +* [linux-arm32v7](https://github.com/pnp/powershell/blob/dev/docker/linux-arm32.dockerfile): Linux Debian Bookworm Slim 32 bits for ARM devices (i.e. Raspberry Pi 2 v1.1 or older running 32 bits Linux). This image installs PnP PowerShell from the PowerShell Gallery each time a container starts +* [linux-arm64](https://github.com/pnp/powershell/blob/dev/docker/linux-arm64.dockerfile): Linux Debian Bookworm Slim 64 bits for ARM devices (i.e. Raspberry Pi 2 v1.2 or later running 64 bits Linux) +* [linux-amd64](https://github.com/pnp/powershell/blob/dev/docker/linux-amd64.dockerfile): Alpine 64 bits Tag name examples: diff --git a/pages/articles/environmentvariables.md b/pages/articles/environmentvariables.md index 68553faf1e..dcd8a068e3 100644 --- a/pages/articles/environmentvariables.md +++ b/pages/articles/environmentvariables.md @@ -6,7 +6,11 @@ PnP PowerShell supports a few environment variables you can set to control some | Environment variable | Description| | ---------------------------|--------------------------| -| MicrosoftGraphEndPoint | Overrides the default Microsoft Graph endpoint (https://graph.microsoft.com) to use | +| MicrosoftGraphEndPoint | The Microsoft Graph endpoint to use with `Connect-PnPOnline -AzureEnvironment Custom`, as a host name such as `custom.graph.microsoft.com`. It is ignored for the other values of `-AzureEnvironment`. See [National and sovereign clouds](nationalclouds.md#custom-endpoints) | +| AzureADLoginEndPoint | The Microsoft Entra ID sign in endpoint to use with `Connect-PnPOnline -AzureEnvironment Custom`, as a URL such as `https://custom.login.microsoftonline.com`. It is ignored for the other values of `-AzureEnvironment` | +| PNPPOWERSHELL_FEDERATEDIDENTITY_AUDIENCE | The audience `Connect-PnPOnline -FederatedIdentity` requests its GitHub Actions token for, instead of the one that follows from `-AzureEnvironment`. See [National and sovereign clouds](nationalclouds.md#sign-in-methods) | +| SharePointPnPHttpTimeout | The timeout in seconds for requests to SharePoint Online and Microsoft Graph, retries included, or `-1` for no timeout. Defaults to 100 seconds. Set it before connecting. It does not apply to cmdlets built on the PnP Core SDK. See [Throttling and retries](throttling.md#the-request-timeout) | +| SharePointPnPUserAgent | The `User-Agent` sent with the SharePoint REST API and Microsoft Graph requests PnP PowerShell makes through PnP Framework. Set it before the first connection in the PowerShell session. See [Throttling and retries](throttling.md#identifying-your-traffic) for the format | | ENTRAID_APP_ID | When set [`Connect-PnPOnline`](../cmdlets/connect-pnponline.md) will use this value for authentication. See more info at [Set a default Client ID](defaultclientid.md) | | ENTRAID_CLIENT_ID | See ENTRAID_APP_ID | | AZURE_USERNAME | A way to set the username to use when authenticating with `Connect-PnPOnline -EnvironmentVariable` | diff --git a/pages/articles/logging.md b/pages/articles/logging.md new file mode 100644 index 0000000000..f4ae407cce --- /dev/null +++ b/pages/articles/logging.md @@ -0,0 +1,67 @@ +# Logging and tracing + +PnP PowerShell can keep a trace log of what it does: the start and end of every cmdlet, the Microsoft Graph and REST requests it sends, the [retries after throttling](throttling.md), and the warnings and errors it runs into. The trace log also holds messages that `-Verbose` does not show, such as a permission scope missing from the access token. It is the first thing to turn on when a cmdlet does not behave as expected. + +## Starting the trace log + +[Start-PnPTraceLog](../cmdlets/Start-PnPTraceLog.md) writes the log to one or more targets: + +```powershell +# Keep the entries in memory, to read them as objects with Get-PnPTraceLog +Start-PnPTraceLog -WriteToLogStream -Level Debug + +# Append the entries to a file +Start-PnPTraceLog -Path ./pnp.log -Level Debug + +# Write the entries to the console +Start-PnPTraceLog -WriteToConsole +``` + +The targets can be combined. Starting a target that is already running replaces it, which for `-WriteToLogStream` discards the entries kept so far. + +`-Level` sets which entries are written, for all targets at once: + +| Level | Writes | +|---|---| +| `Debug` | everything, including each cmdlet as it was called and each Microsoft Graph and REST request sent | +| `Information` | retries and other progress messages, plus warnings and errors (the default) | +| `Warning` | warnings and errors | +| `Error` | errors only | + +The trace log is kept for the whole PowerShell process. It is not affected by `Connect-PnPOnline` or `Disconnect-PnPOnline`, and entries from `ForEach-Object -Parallel` script blocks end up in the same log. + +## Reading the trace log + +[Get-PnPTraceLog](../cmdlets/Get-PnPTraceLog.md) returns the entries kept in memory as objects with, among others, a `TimeStamp`, `Source`, `Level` and `Message`: + +```powershell +Get-PnPTraceLog | Where-Object Level -eq Error +``` + +A log file is plain text with the fields of each entry separated by tabs, and can be read with `Get-Content` while it is still being written. [Clear-PnPTraceLog](../cmdlets/Clear-PnPTraceLog.md) empties the entries kept in memory. + +## Adding your own entries + +[Write-PnPTraceLog](../cmdlets/Write-PnPTraceLog.md) adds entries from your script, so they appear in order with those of PnP PowerShell: + +```powershell +Write-PnPTraceLog -Message "Processing site $siteUrl" -Source "SiteInventory" -Level Information +``` + +Besides the trace log, an entry is also written to the matching PowerShell stream: a `Warning` entry shows as a warning, and an `Error` entry is written as an error, which stops a script running with `$ErrorActionPreference = 'Stop'`. + +## Stopping the trace log + +[Stop-PnPTraceLog](../cmdlets/Stop-PnPTraceLog.md) stops every target unless told otherwise. To stop writing to a file and the console, but keep the entries in memory available to `Get-PnPTraceLog`: + +```powershell +Stop-PnPTraceLog -StopLogStreamLogging:$false +``` + +## The trace log and -Verbose + +Entries written at the `Debug` level by the cmdlets themselves also appear when a cmdlet runs with `-Verbose`, whether the trace log is started or not. The other entries, which include the requests sent, the retries and the permission checks, appear only in the trace log. + +## Secrets in the trace log + +At the `Debug` level, each cmdlet is logged as it was typed. A client secret or password typed into the command appears in the log as is, while one passed in a variable appears as the variable name. The access tokens PnP PowerShell acquires are not logged. Treat a debug log as sensitive, and pass secrets in variables. diff --git a/pages/articles/mcpserver.md b/pages/articles/mcpserver.md new file mode 100644 index 0000000000..76bf3ea165 --- /dev/null +++ b/pages/articles/mcpserver.md @@ -0,0 +1,48 @@ +# PnP PowerShell MCP server + +The [PnP PowerShell MCP server](https://github.com/pnp/pnp-powershell-mcp-server) lets AI assistants work with PnP PowerShell. MCP, the [Model Context Protocol](https://modelcontextprotocol.io), is the standard way for assistants such as GitHub Copilot in Visual Studio Code, GitHub Copilot CLI, Claude Code, Claude Desktop and Cursor to use tools on your machine. With the server added to your assistant, you can ask in plain language for things like "list the sites created this month" or "write a script that reports on sharing links", and the assistant finds the right cmdlets, reads their documentation, runs them against your tenant, and drafts scripts from the [PnP Script Samples](https://pnp.github.io/script-samples/). + +The server is a separate community project in its own repository, and is published to the [MCP Registry](https://registry.modelcontextprotocol.io) as `io.github.pnp/pnp-powershell-mcp-server`. It is in preview: its releases are versioned `0.x` and marked as prereleases. + +## How it works + +The server runs locally on your machine and runs the PnP PowerShell module installed there. It does not sign in by itself: it connects with `Connect-PnPOnline` and your own [application registration](registerapplication.md), like any other PnP PowerShell script, so in Microsoft 365 it can only do what that account or application is permitted to do. On your machine, it runs PowerShell with your own permissions. + +## Installing it + +You need [PowerShell 7.4 or later](https://aka.ms/powershell) and the PnP PowerShell module, see [Installation](installation.md). The server itself is installed as a .NET tool, which takes the [.NET SDK](https://dotnet.microsoft.com/download): + +```powershell +dotnet tool install --global PnP.PowerShell.MCPServer --prerelease +``` + +Then add the `pnp-powershell-mcp-server` command to your assistant. In Claude Code, for instance: + +```powershell +claude mcp add pnp-powershell --scope user -- pnp-powershell-mcp-server +``` + +Or in the `.vscode/mcp.json` file of Visual Studio Code: + +```json +{ + "servers": { + "PnP PowerShell MCP Server": { + "type": "stdio", + "command": "pnp-powershell-mcp-server" + } + } +} +``` + +The [README of the server](https://github.com/pnp/pnp-powershell-mcp-server#readme) describes the setup for each assistant, the tools the server offers and all of its settings. + +## Staying in control + +An assistant that can run PnP PowerShell can change your tenant. The server asks you to confirm commands whose verb deletes or revokes, such as the `Remove-*`, `Clear-*`, `Reset-*` and `Revoke-*` cmdlets, before it runs them; with an assistant that cannot show that question, it blocks them instead. All other commands run without asking, including the `Set-*`, `Add-*` and `New-*` cmdlets, and `Add-PnPFile`, which overwrites a file that already exists. + +To let an assistant look but not touch, set the `PNP_MCP_READONLY` environment variable to `true` in the server configuration of your assistant. The server then refuses the commands whose verb changes things. As it goes by the verb, it is not a sandbox: a few cmdlets with a reading verb still change your tenant, such as `Resolve-PnPFolder`, which creates the folders it does not find. Connecting with an account or application that only has read permissions is what keeps your tenant unchanged. + +## Feedback + +Report issues with the server and ideas for it in [its own repository](https://github.com/pnp/pnp-powershell-mcp-server/issues), and issues with the cmdlets it runs in the [PnP PowerShell repository](https://github.com/pnp/powershell/issues). diff --git a/pages/articles/nationalclouds.md b/pages/articles/nationalclouds.md new file mode 100644 index 0000000000..1c0aa66157 --- /dev/null +++ b/pages/articles/nationalclouds.md @@ -0,0 +1,62 @@ +# National and sovereign clouds + +By default, PnP PowerShell signs in to and calls the worldwide Microsoft cloud. To work with a tenant in another cloud, connect to a site in that tenant and pass the cloud to [Connect-PnPOnline](../cmdlets/Connect-PnPOnline.md#-azureenvironment) with `-AzureEnvironment`: + +```powershell +Connect-PnPOnline -Url "https://contoso.sharepoint.us" -ClientId "" -Interactive -AzureEnvironment USGovernmentHigh +``` + +The application registration you connect with has to be registered in that same cloud. See [Register your application](registerapplication.md#special-instructions-for-gcc-or-national-cloud-environments) for how to create one with `Register-PnPEntraIDApp -AzureEnvironment`. + +## The values of -AzureEnvironment + +The value decides which Microsoft Entra ID endpoint PnP PowerShell signs in with and which Microsoft Graph endpoint it calls. The SharePoint endpoint follows from the URL you connect to. + +| Value | Cloud | Sign in | Microsoft Graph | +|---|---|---|---| +| `Production` | Worldwide, the default | login.microsoftonline.com | graph.microsoft.com | +| `USGovernment` | US Government Community Cloud (GCC) | login.microsoftonline.com | graph.microsoft.com | +| `USGovernmentHigh` | US Government GCC High | login.microsoftonline.us | graph.microsoft.us | +| `USGovernmentDoD` | US Government DoD | login.microsoftonline.us | dod-graph.microsoft.us | +| `China` | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn | +| `BleuCloud` | Bleu, France | login.sovcloud-identity.fr | graph.svc.sovcloud.fr | +| `DelosCloud` | Delos, Germany | login.sovcloud-identity.de | graph.svc.sovcloud.de | +| `GovSGCloud` | GovSG | login.sovcloud-identity.sg | graph.svc.sovcloud.sg | +| `Germany` | Microsoft Cloud Deutschland, closed on 29 October 2021 | login.microsoftonline.com | graph.microsoft.com | +| `PPE` | Microsoft's internal preproduction environment | login.windows-ppe.net, except for `-Interactive` and `-OSLogin` | graph.microsoft.com | +| `Custom` | Endpoints you provide | see [Custom endpoints](#custom-endpoints) | see [Custom endpoints](#custom-endpoints) | + +A tenant in GCC uses the worldwide endpoints for signing in and for Microsoft Graph, but `USGovernment` also points the cmdlets that call Power Platform, Azure Resource Manager and Office 365 Management APIs to their US Government endpoints. `Get-PnPUnifiedAuditLog` calls the Office 365 Management API for GCC, GCC High and DoD at its endpoint in that cloud, and at the worldwide endpoint for the other values. With `BleuCloud`, `DelosCloud` and `GovSGCloud`, the cmdlets that call Power Platform APIs are not supported. + +`Germany` is still accepted, but uses the worldwide endpoints, as the cloud it stood for has closed. + +## Custom endpoints + +For a cloud not in the list, use `-AzureEnvironment Custom` and provide the endpoints yourself. Provide the Microsoft Graph endpoint as a host name and the sign in endpoint as a URL: + +```powershell +Connect-PnPOnline -Url "https://contoso.sharepoint.com" -ClientId "" -DeviceLogin -AzureEnvironment Custom -MicrosoftGraphEndPoint "custom.graph.microsoft.com" -AzureADLoginEndPoint "https://custom.login.microsoftonline.com" +``` + +Instead of the parameters, you can set the `MicrosoftGraphEndPoint` and `AzureADLoginEndPoint` environment variables. Like the parameters, they are only used with `-AzureEnvironment Custom`, and an endpoint you do not provide falls back to the worldwide one. The custom sign in endpoint is used when signing in with a certificate, with credentials, with `-DeviceLogin` or with `-FederatedIdentity`; `-Interactive` and `-OSLogin` sign in through the worldwide endpoint, and `-AzureADWorkloadIdentity` through the endpoint in `AZURE_AUTHORITY_HOST`. + +## Sign in methods + +`-AzureEnvironment` applies to every way of signing in with `Connect-PnPOnline`, with these differences: + +- **`-AccessToken`** sends the token as it is given to every API, so the token has to be issued in the cloud given with `-AzureEnvironment`. +- **`-AzureADWorkloadIdentity`** signs in through the endpoint in the `AZURE_AUTHORITY_HOST` environment variable, which the workload identity webhook sets for the cloud of the cluster. `-AzureEnvironment` decides the endpoints of Microsoft Graph and the other APIs. +- **`-FederatedIdentity`** on GitHub Actions requests its token for the audience `api://AzureADTokenExchangeUSGov` with `USGovernmentHigh` and `USGovernmentDoD`, `api://AzureADTokenExchangeChina` with `China`, and `api://AzureADTokenExchange` otherwise. The federated credential on your application registration has to use the same audience. To use another one, set it in the `PNPPOWERSHELL_FEDERATEDIDENTITY_AUDIENCE` environment variable. + +## Cmdlets that only work in the worldwide cloud + +The following cmdlets call APIs through their worldwide endpoints, whatever the value of `-AzureEnvironment`: + +- `Get-PnPPlannerConfiguration`, `Set-PnPPlannerConfiguration`, `Get-PnPPlannerUserPolicy` and `Set-PnPPlannerUserPolicy` +- `Get-PnPSearchVertical`, `New-PnPSearchVertical`, `Set-PnPSearchVertical`, `Remove-PnPSearchVertical`, `Set-PnPSearchVerticalOrder`, `Get-PnPSearchResultType`, `New-PnPSearchResultType`, `Set-PnPSearchResultType`, `Remove-PnPSearchResultType` and `Get-PnPSearchSiteConnection` + +## Other cmdlets that take -AzureEnvironment + +[Register-PnPEntraIDApp](../cmdlets/Register-PnPEntraIDApp.md), [Register-PnPEntraIDAppForInteractiveLogin](../cmdlets/Register-PnPEntraIDAppForInteractiveLogin.md) and [Get-PnPTenantId](../cmdlets/Get-PnPTenantId.md) take the same values, to work with a tenant in another cloud without connecting first. + +For the endpoints of each cloud, see [Microsoft Graph national cloud deployments](https://learn.microsoft.com/graph/deployments) and [National clouds](https://learn.microsoft.com/entra/identity-platform/authentication-national-cloud). diff --git a/pages/articles/registerapplication.md b/pages/articles/registerapplication.md index cf7d106a75..daa53b365a 100644 --- a/pages/articles/registerapplication.md +++ b/pages/articles/registerapplication.md @@ -149,18 +149,18 @@ Connect-PnPOnline [yourtenant].sharepoint.com -ClientId [clientid] -Tenant [your ## Special instructions for GCC or National Cloud environments -In order to set up your application registration on a GCC or a national cloud environment, you will have to take a few extra steps. In the two methods described above for [interactive login](#automatically-create-an-app-registration-for-interactive-login) and [App Only access](#setting-up-access-to-your-own-entra-id-app-for-app-only-access), you will have to add `-AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|Germany|China|BleuCloud|DelosCloud|GovSGCloud]` to the cmdlet picking the one that applies to your environment to register your application in Entra ID. +In order to set up your application registration on a GCC or a national cloud environment, you will have to take a few extra steps. In the two methods described above for [interactive login](#automatically-create-an-app-registration-for-interactive-login) and [App Only access](#setting-up-access-to-your-own-entra-id-app-for-app-only-access), you will have to add `-AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|China|BleuCloud|DelosCloud|GovSGCloud]` to the cmdlet picking the one that applies to your environment to register your application in Entra ID. See [National and sovereign clouds](nationalclouds.md) for what each value changes. For an application registration meant for interactive login, use: ```PowerShell -Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "PnP.PowerShell" -Tenant [yourtenant].onmicrosoft.com -AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|Germany|China|BleuCloud|DelosCloud|GovSGCloud] +Register-PnPEntraIDAppForInteractiveLogin -ApplicationName "PnP.PowerShell" -Tenant [yourtenant].onmicrosoft.com -AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|China|BleuCloud|DelosCloud|GovSGCloud] ``` And for an App Only application registration, use: ```PowerShell -$result = Register-PnPEntraIDApp -ApplicationName "PnP.PowerShell" -Tenant [yourtenant].onmicrosoft.com -OutPath c:\mycertificates -DeviceLogin -AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|Germany|China|BleuCloud|DelosCloud|GovSGCloud] +$result = Register-PnPEntraIDApp -ApplicationName "PnP.PowerShell" -Tenant [yourtenant].onmicrosoft.com -OutPath c:\mycertificates -DeviceLogin -AzureEnvironment [USGovernment|USGovernmentHigh|USGovernmentDoD|China|BleuCloud|DelosCloud|GovSGCloud] $result ``` diff --git a/pages/articles/throttling.md b/pages/articles/throttling.md new file mode 100644 index 0000000000..ae2a3bf9a9 --- /dev/null +++ b/pages/articles/throttling.md @@ -0,0 +1,54 @@ +# Throttling and retries + +SharePoint Online and Microsoft Graph throttle clients that send too many requests: they answer with `429 Too Many Requests` or `503 Service Unavailable`, usually with a `Retry-After` header saying how many seconds to wait. PnP PowerShell retries throttled requests for you, so a script does not need its own retry loop for a short throttle. A long one can still make a cmdlet fail, see [The request timeout](#the-request-timeout). + +## How PnP PowerShell retries + +Requests to SharePoint Online, through CSOM as well as through the REST API, and requests to Microsoft Graph all pass through the same retry handler in PnP Framework. When a response has status `429`, `503` or `504`, or the connection drops, the handler: + +- waits the number of seconds given in the `Retry-After` header, or 1, 2, 4, 8 and so on seconds when there is none, with no single wait longer than 300 seconds +- retries the request up to 10 times, as long as the request timeout allows, after which the cmdlet fails with `Too many http request retries: 10` + +Requests that change data, such as `POST` requests, are retried as well. + +Cmdlets built on the PnP Core SDK retry in the same way, starting with a 3 second wait. In a [batch](batching.md), throttled Microsoft Graph requests are retried with that same 3 second starting wait, but without looking at `Retry-After`. A SharePoint batch is retried as a whole. + +## The request timeout + +A request and all of its retries have to finish within the HTTP timeout of 100 seconds. When throttling lasts longer, the cmdlet fails with `The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.` instead of retrying further. Without `Retry-After`, the waits of 1, 2, 4, 8, 16 and 32 seconds already add up to 63 seconds, so about six retries fit in that time, and a `Retry-After` of 100 seconds or more leaves no room for a retry at all. + +For long running jobs, set the `SharePointPnPHttpTimeout` environment variable to the timeout in seconds, or to `-1` for no timeout, before connecting. It does not apply to cmdlets built on the PnP Core SDK, which keep a timeout of 100 seconds. + +```powershell +$env:SharePointPnPHttpTimeout = 600 +Connect-PnPOnline -Url "https://contoso.sharepoint.com" -ClientId "" -Interactive +``` + +## Identifying your traffic + +Microsoft gives precedence to traffic that identifies the application sending it through its `User-Agent` header. PnP PowerShell identifies most of its CSOM requests as `NONISV|SharePointPnP|PnPPS/`, its REST API and Microsoft Graph requests as `NONISV|SharePointPnP|PnPCore/`, and the requests of cmdlets built on the PnP Core SDK as `NONISV|SharePointPnP|PnPCoreSDK/`. + +To identify a script as your own, set the `SharePointPnPUserAgent` environment variable in the format `NONISV|CompanyName|AppName/Version` before the first connection in the PowerShell session. It replaces the `PnPCore` value only. + +```powershell +$env:SharePointPnPUserAgent = "NONISV|Contoso|SiteInventory/1.0" +``` + +## Seeing retries happen + +Retries do not show with `-Verbose`. The retries described above are written to the [trace log](logging.md) at the default `Information` level, apart from those of cmdlets built on the PnP Core SDK: + +```powershell +Start-PnPTraceLog -WriteToLogStream +# Run the cmdlets of your script +Get-PnPTraceLog | Where-Object Message -like "*retry*" +``` + +Each retry adds an entry like `Retrying request https://contoso.sharepoint.com/... due to status code TooManyRequests`, followed by `Waiting 4 seconds before retrying`. For a wait of a minute or more, that number shows only the seconds beyond the whole minutes. + +## Avoiding throttling + +Fewer requests mean less throttling. Use [batching](batching.md) where a cmdlet supports it, ask only for the properties and items you need, and spread large jobs over time. Microsoft's guidance covers the limits and patterns in detail: + +- [Avoid getting throttled or blocked in SharePoint Online](https://learn.microsoft.com/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online) +- [Microsoft Graph throttling guidance](https://learn.microsoft.com/graph/throttling) diff --git a/pages/articles/toc.yml b/pages/articles/toc.yml index 6651fcb3e1..8dcfed6f65 100644 --- a/pages/articles/toc.yml +++ b/pages/articles/toc.yml @@ -50,6 +50,12 @@ href: microsoftsearch.md - name: Batching in PnP PowerShell href: batching.md + - name: Throttling and retries + href: throttling.md + - name: Logging and tracing + href: logging.md + - name: National and sovereign clouds + href: nationalclouds.md - name: The extract configuration (using PnP Provisioning Engine) href: extract-configuration.md - name: The apply configuration (using PnP Provisioning Engine) @@ -78,3 +84,5 @@ items: - name: Visual Studio Code extension href: vscodeextension.md + - name: MCP server for AI assistants + href: mcpserver.md diff --git a/pages/images/azureautomation/addmodulefromgallery.png b/pages/images/azureautomation/addmodulefromgallery.png deleted file mode 100644 index 74971273d8..0000000000 Binary files a/pages/images/azureautomation/addmodulefromgallery.png and /dev/null differ diff --git a/pages/images/azureautomation/addmodulefromgallerycustombuild.png b/pages/images/azureautomation/addmodulefromgallerycustombuild.png deleted file mode 100644 index 1373428bed..0000000000 Binary files a/pages/images/azureautomation/addmodulefromgallerycustombuild.png and /dev/null differ diff --git a/pages/images/azureautomation/addmodulefromgallerycustombuildimport.png b/pages/images/azureautomation/addmodulefromgallerycustombuildimport.png deleted file mode 100644 index a9c5b27b9a..0000000000 Binary files a/pages/images/azureautomation/addmodulefromgallerycustombuildimport.png and /dev/null differ diff --git a/pages/images/azureautomation/addmodulefromgallerycustombuildnupkg.png b/pages/images/azureautomation/addmodulefromgallerycustombuildnupkg.png deleted file mode 100644 index ca97eec61d..0000000000 Binary files a/pages/images/azureautomation/addmodulefromgallerycustombuildnupkg.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaccountmodulesmenu.png b/pages/images/azureautomation/automationaccountmodulesmenu.png deleted file mode 100644 index 2705d7e28f..0000000000 Binary files a/pages/images/azureautomation/automationaccountmodulesmenu.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodule.png b/pages/images/azureautomation/automationaddmodule.png deleted file mode 100644 index 20b4e9d90b..0000000000 Binary files a/pages/images/azureautomation/automationaddmodule.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposh.png b/pages/images/azureautomation/automationaddmodulepnpposh.png deleted file mode 100644 index cb7eea36df..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposh.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhconfirm.png b/pages/images/azureautomation/automationaddmodulepnpposhconfirm.png deleted file mode 100644 index 197c9de877..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhconfirm.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhcustombuildstatus.png b/pages/images/azureautomation/automationaddmodulepnpposhcustombuildstatus.png deleted file mode 100644 index 88e9dc676d..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhcustombuildstatus.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhcustomdone.png b/pages/images/azureautomation/automationaddmodulepnpposhcustomdone.png deleted file mode 100644 index 569b35e21c..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhcustomdone.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhdone.png b/pages/images/azureautomation/automationaddmodulepnpposhdone.png deleted file mode 100644 index 38a38113ea..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhdone.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhimport.png b/pages/images/azureautomation/automationaddmodulepnpposhimport.png deleted file mode 100644 index d167ab504c..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhimport.png and /dev/null differ diff --git a/pages/images/azureautomation/automationaddmodulepnpposhstatus.png b/pages/images/azureautomation/automationaddmodulepnpposhstatus.png deleted file mode 100644 index 116831d6b9..0000000000 Binary files a/pages/images/azureautomation/automationaddmodulepnpposhstatus.png and /dev/null differ diff --git a/pages/images/azureautomation/azureportalcreaterunbook.png b/pages/images/azureautomation/azureportalcreaterunbook.png deleted file mode 100644 index ad0bf92316..0000000000 Binary files a/pages/images/azureautomation/azureportalcreaterunbook.png and /dev/null differ diff --git a/pages/images/azureautomation/explorerzipcustombuild.png b/pages/images/azureautomation/explorerzipcustombuild.png deleted file mode 100644 index e5caeba9c4..0000000000 Binary files a/pages/images/azureautomation/explorerzipcustombuild.png and /dev/null differ diff --git a/pages/images/azureautomation/pwshdownloadcustombuild.png b/pages/images/azureautomation/pwshdownloadcustombuild.png deleted file mode 100644 index b7611e4d20..0000000000 Binary files a/pages/images/azureautomation/pwshdownloadcustombuild.png and /dev/null differ diff --git a/pages/images/determinepermissions/entraid_permissions_accessdenied_verbose.png b/pages/images/determinepermissions/entraid_permissions_accessdenied_verbose.png deleted file mode 100644 index beb1333d28..0000000000 Binary files a/pages/images/determinepermissions/entraid_permissions_accessdenied_verbose.png and /dev/null differ diff --git a/src/Commands/Base/ConnectOnline.cs b/src/Commands/Base/ConnectOnline.cs index 0f16666c03..ac60cf8f78 100644 --- a/src/Commands/Base/ConnectOnline.cs +++ b/src/Commands/Base/ConnectOnline.cs @@ -191,6 +191,7 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYPRINCIPALID)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYAZURERESOURCEID)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_FEDERATEDIDENTITY)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_AZUREAD_WORKLOAD_IDENTITY)] public Framework.AzureEnvironment AzureEnvironment = Framework.AzureEnvironment.Production; // [Parameter(Mandatory = true, ParameterSetName = ParameterSet_APPONLYCLIENTIDCLIENTSECRETAADDOMAIN)] @@ -255,6 +256,7 @@ public class ConnectOnline : BasePSCmdlet [Parameter(Mandatory = false, ParameterSetName = ParameterSet_USERASSIGNEDMANAGEDIDENTITYBYAZURERESOURCEID)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_OSLOGIN)] [Parameter(Mandatory = false, ParameterSetName = ParameterSet_FEDERATEDIDENTITY)] + [Parameter(Mandatory = false, ParameterSetName = ParameterSet_AZUREAD_WORKLOAD_IDENTITY)] public string MicrosoftGraphEndPoint; [Parameter(Mandatory = false, ParameterSetName = ParameterSet_CREDENTIALS)] @@ -695,7 +697,7 @@ private PnPConnection ConnectAccessToken() { LogDebug("Connecting using a provided Access Token"); - return PnPConnection.CreateWithAccessToken(!string.IsNullOrEmpty(Url) ? new Uri(Url) : null, AccessToken, TenantAdminUrl); + return PnPConnection.CreateWithAccessToken(!string.IsNullOrEmpty(Url) ? new Uri(Url) : null, AccessToken, TenantAdminUrl, AzureEnvironment); } /// @@ -914,7 +916,7 @@ private PnPConnection ConnectAzureADWorkloadIdentity() { LogDebug("Connecting using Entra ID Workload Identity"); - return PnPConnection.CreateWithAzureADWorkloadIdentity(Url, TenantAdminUrl); + return PnPConnection.CreateWithAzureADWorkloadIdentity(Url, TenantAdminUrl, AzureEnvironment); } private PnPConnection ConnectWithOSLogin() diff --git a/src/Commands/Base/PnPConnection.cs b/src/Commands/Base/PnPConnection.cs index 375a6c1309..f29bdc45ca 100644 --- a/src/Commands/Base/PnPConnection.cs +++ b/src/Commands/Base/PnPConnection.cs @@ -161,7 +161,7 @@ internal PnPContext PnPContext #endregion #region Creators - internal static PnPConnection CreateWithAccessToken(Uri url, string accessToken, string tenantAdminUrl) + internal static PnPConnection CreateWithAccessToken(Uri url, string accessToken, string tenantAdminUrl, AzureEnvironment azureEnvironment) { using (var authManager = new PnP.Framework.AuthenticationManager(new System.Net.NetworkCredential("", accessToken).SecurePassword)) { @@ -183,6 +183,9 @@ internal static PnPConnection CreateWithAccessToken(Uri url, string accessToken, } var connection = new PnPConnection(context, connectionType, null, url != null ? url.ToString() : null, tenantAdminUrl, PnPPSVersionTag, InitializationType.Token); + connection.AzureEnvironment = azureEnvironment; + // The token only AuthenticationManager backing this context carries no cloud, so its Graph endpoint would otherwise resolve to the commercial one. + connection._graphEndPoint = GetGraphEndPoint(azureEnvironment); return connection; } } @@ -513,6 +516,9 @@ internal static PnPConnection CreateWithManagedIdentity(string url, string tenan UserAssignedManagedIdentityClientId = userAssignedManagedIdentityClientId, UserAssignedManagedIdentityAzureResourceId = userAssignedManagedIdentityAzureResourceId, ConnectionMethod = ConnectionMethod.ManagedIdentity, + AzureEnvironment = azureEnvironment, + // Without a url there is no context to derive the Graph endpoint from, so it would otherwise resolve to the commercial one. + _graphEndPoint = GetGraphEndPoint(azureEnvironment), }; return connection; } @@ -709,10 +715,12 @@ internal static PnPConnection CreateWithInteractiveLogin(Cmdlet cmdlet, Uri uri, /// PowerShell instance hosting this execution /// Url to the SharePoint Online site to connect to /// Url to the SharePoint Online Admin Center site to connect to + /// The cloud to connect to, which selects the Microsoft Graph endpoint /// Instantiated PnPConnection - internal static PnPConnection CreateWithAzureADWorkloadIdentity(string url, string tenantAdminUrl) + internal static PnPConnection CreateWithAzureADWorkloadIdentity(string url, string tenantAdminUrl, AzureEnvironment azureEnvironment = AzureEnvironment.Production) { - string defaultResource = "https://graph.microsoft.com/.default"; + var graphEndPoint = GetGraphEndPoint(azureEnvironment); + string defaultResource = $"https://{graphEndPoint}/.default"; if (url != null) { var resourceUri = new Uri(url); @@ -755,6 +763,9 @@ internal static PnPConnection CreateWithAzureADWorkloadIdentity(string url, stri } var connection = new PnPConnection(context, connectionType, null, url != null ? url.ToString() : null, tenantAdminUrl, PnPPSVersionTag, InitializationType.AzureADWorkloadIdentity); + connection.AzureEnvironment = azureEnvironment; + // The token only AuthenticationManager backing this context carries no cloud, so its Graph endpoint would otherwise resolve to the commercial one. + connection._graphEndPoint = graphEndPoint; return connection; } } diff --git a/src/Commands/Base/PnPOfficeManagementApiCmdlet.cs b/src/Commands/Base/PnPOfficeManagementApiCmdlet.cs index d14ae5be6e..46fc3061af 100644 --- a/src/Commands/Base/PnPOfficeManagementApiCmdlet.cs +++ b/src/Commands/Base/PnPOfficeManagementApiCmdlet.cs @@ -2,6 +2,7 @@ using System.Management.Automation; using Microsoft.SharePoint.Client; using System.Linq; +using PnP.PowerShell.Commands.Utilities.Auth; using PnP.PowerShell.Commands.Utilities.REST; namespace PnP.PowerShell.Commands.Base @@ -14,7 +15,7 @@ public abstract class PnPOfficeManagementApiCmdlet : PnPConnectedCmdlet /// /// Returns an Access Token for the Microsoft Office Management API, if available, otherwise NULL /// - public string AccessToken => TokenHandler.GetAccessToken("https://manage.office.com/.default", Connection); + public string AccessToken => TokenHandler.GetAccessToken($"{Endpoints.GetOfficeManagementApiEndpoint(Connection)}/.default", Connection); public ApiRequestHelper RequestHelper { get; set; } protected override void BeginProcessing() { @@ -26,7 +27,7 @@ protected override void BeginProcessing() throw new PSInvalidOperationException("This cmdlet not work with a WebLogin/Cookie based connection towards SharePoint."); } } - RequestHelper = new ApiRequestHelper(GetType(), Connection, "https://manage.office.com/.default"); + RequestHelper = new ApiRequestHelper(GetType(), Connection, $"{Endpoints.GetOfficeManagementApiEndpoint(Connection)}/.default"); } protected Guid? TenantId @@ -40,6 +41,6 @@ protected Guid? TenantId /// /// Root URL to the Office 365 Management API /// - protected string ApiRootUrl => $"https://manage.office.com/api/v1.0/{TenantId}/"; + protected string ApiRootUrl => $"{Endpoints.GetOfficeManagementApiEndpoint(Connection)}/api/v1.0/{TenantId}/"; } } \ No newline at end of file diff --git a/src/Commands/Features/DisableFeature.cs b/src/Commands/Features/DisableFeature.cs index 61d40bac84..bfdf420906 100644 --- a/src/Commands/Features/DisableFeature.cs +++ b/src/Commands/Features/DisableFeature.cs @@ -11,6 +11,7 @@ public class DisableFeature : PnPWebCmdlet [Parameter(Mandatory = true, Position = 0, ParameterSetName = ParameterAttribute.AllParameterSets)] public Guid Identity; + [Obsolete("The Force parameter is obsolete and will be removed in future versions. Please update your scripts accordingly.")] [Parameter(Mandatory = false, ParameterSetName = ParameterAttribute.AllParameterSets)] public SwitchParameter Force; diff --git a/src/Commands/Utilities/Auth/Endpoints.cs b/src/Commands/Utilities/Auth/Endpoints.cs index 3cad1cfea8..1f20af2ea0 100644 --- a/src/Commands/Utilities/Auth/Endpoints.cs +++ b/src/Commands/Utilities/Auth/Endpoints.cs @@ -25,6 +25,22 @@ public static string GetArmEndpoint(PnPConnection connection) }; } + /// + /// Returns the endpoint for the Office 365 Management API based on the current connection + /// + /// Connection to base the proper API endpoint on + /// The API endpoint + public static string GetOfficeManagementApiEndpoint(PnPConnection connection) + { + return connection.AzureEnvironment switch + { + Framework.AzureEnvironment.USGovernment => "https://manage-gcc.office.com", + Framework.AzureEnvironment.USGovernmentHigh => "https://manage.office365.us", + Framework.AzureEnvironment.USGovernmentDoD => "https://manage.protection.apps.mil", + _ => "https://manage.office.com", + }; + } + /// /// Returns the endpoint for the Microsoft Graph API based on the current connection ///