├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ └── bug_report.md ├── pull_request_template.md └── workflows │ ├── build-docs.yaml │ ├── deploy-site.yaml │ └── wiki.yml ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── assets └── aadconsentgrantpermissiontable.csv ├── build ├── Add-PSModuleHeader.ps1 ├── Build-PSModule.ps1 ├── Build-Wiki.ps1 ├── CommonFunctions.psm1 ├── Get-PSModuleInfo.ps1 ├── Launch-PSModule.ps1 ├── Merge-PSModuleNestedModuleScripts.ps1 ├── PesterConfiguration.Baseline.psd1 ├── PesterConfiguration.CD.psd1 ├── PesterConfiguration.CI.psd1 ├── PesterConfiguration.Debug.psd1 ├── PesterConfiguration.psd1 ├── PesterCustomAssertions.psm1 ├── Publish-PSModule.ps1 ├── Restore-NugetPackages.ps1 ├── Restore-PSModuleDependencies.ps1 ├── Sign-PSModule.ps1 ├── Test-PSModule.ps1 ├── Update-CommandReference.ps1 ├── Update-PSModuleManifest.ps1 └── azure-pipelines │ ├── azure-pipelines-cd.yml │ ├── azure-pipelines-ci.yml │ ├── template-psmodule-build.yml │ ├── template-psmodule-package.yml │ ├── template-psmodule-publish.yml │ ├── template-psmodule-sign.yml │ └── template-psmodule-test.yml ├── src ├── Add-MsIdServicePrincipal.ps1 ├── Confirm-MsIdJwtTokenSignature.ps1 ├── ConvertFrom-MsIdAadcAadConnectorSpaceDn.ps1 ├── ConvertFrom-MsIdAadcSourceAnchor.ps1 ├── ConvertFrom-MsIdJwtToken.ps1 ├── ConvertFrom-MsIdSamlMessage.ps1 ├── ConvertFrom-MsIdUniqueTokenIdentifier.ps1 ├── Expand-MsIdJwtTokenPayload.ps1 ├── Export-MsIdAppConsentGrantReport.ps1 ├── Export-MsIdAzureMfaReport.ps1 ├── Find-MsIdUnprotectedUsersWithAdminRoles.ps1 ├── Get-MsIdAdfsSamlToken.ps1 ├── Get-MsIdAdfsSampleApp.ps1 ├── Get-MsIdAdfsWsFedToken.ps1 ├── Get-MsIdAdfsWsTrustToken.ps1 ├── Get-MsIdApplicationIdByAppId.ps1 ├── Get-MsIdAuthorityUri.ps1 ├── Get-MsIdAzureIpRange.ps1 ├── Get-MsIdAzureUsers.ps1 ├── Get-MsIdCrossTenantAccessActivity.ps1 ├── Get-MsIdGroupWithExpiration.ps1 ├── Get-MsIdGroupWritebackConfiguration.ps1 ├── Get-MsIdHasMicrosoftAccount.ps1 ├── Get-MsIdInactiveSignInUser.ps1 ├── Get-MsIdIsViralUser.ps1 ├── Get-MsIdMsftIdentityAssociation.ps1 ├── Get-MsIdO365Endpoints.ps1 ├── Get-MsIdOpenIdProviderConfiguration.ps1 ├── Get-MsIdProvisioningLogStatistics.ps1 ├── Get-MsIdSamlFederationMetadata.ps1 ├── Get-MsIdServicePrincipalIdByAppId.ps1 ├── Get-MsIdSigningKeyThumbprint.ps1 ├── Get-MsIdUnmanagedExternalUser.ps1 ├── Get-MsIdUnredeemedInvitedUser.ps1 ├── Import-MsIdAdfsSampleApp.ps1 ├── Import-MsIdAdfsSamplePolicy.ps1 ├── Invoke-MsIdAzureAdSamlRequest.ps1 ├── MSIdentityTools.psd1 ├── MSIdentityTools.psm1 ├── New-MsIdClientSecret.ps1 ├── New-MsIdSamlRequest.ps1 ├── New-MsIdTemporaryUserPassword.ps1 ├── New-MsIdWsTrustRequest.ps1 ├── Remove-MsIdUserAuthenticationMethod.ps1 ├── Reset-MsIdExternalUser.ps1 ├── Resolve-MsIdAzureIpAddress.ps1 ├── Resolve-MsIdTenant.ps1 ├── Revoke-MsIdServicePrincipalConsent.ps1 ├── Set-MsIdServicePrincipalVisibleInMyApps.ps1 ├── Set-MsIdWindowsTlsSettings.ps1 ├── Show-MsIdJwtToken.ps1 ├── Show-MsIdSamlToken.ps1 ├── Split-MsIdEntitlementManagementConnectedOrganization.ps1 ├── Test-MsIdAzureAdDeviceRegConnectivity.ps1 ├── Test-MsIdCBATrustStoreConfiguration.ps1 ├── Update-MsIdApplicationSigningKeyThumbprint.ps1 ├── Update-MsIdGroupWritebackConfiguration.ps1 ├── Update-MsIdInvitedUserSponsorsFromInvitedBy.ps1 └── internal │ ├── AdfsSamples │ ├── AdfsAccessControlPolicy.xml │ ├── Amazon Web Services.json │ ├── BOX.json │ ├── Blackboard.json │ ├── Concur.json │ ├── CornerStone OnDemand.json │ ├── Facebook for Work.json │ ├── Google Cloud Console.json │ ├── SAP Cloud Identity Platform.json │ ├── Salesforce.json │ ├── Service Now.json │ ├── Slack.json │ ├── SuccessFactors.json │ ├── Templafy.json │ ├── Workday.json │ ├── Zoom.json │ └── Zscaler.json │ ├── Compress-Data.ps1 │ ├── Confirm-JsonWebSignature.ps1 │ ├── ConvertFrom-Base64String.ps1 │ ├── ConvertFrom-HexString.ps1 │ ├── ConvertFrom-JsonWebSignature.ps1 │ ├── ConvertFrom-QueryString.ps1 │ ├── ConvertFrom-SamlMessage.ps1 │ ├── ConvertFrom-SecureStringAsPlainText.ps1 │ ├── ConvertTo-Base64String.ps1 │ ├── ConvertTo-HexString.ps1 │ ├── ConvertTo-PsParameterString.ps1 │ ├── ConvertTo-PsString.ps1 │ ├── ConvertTo-QueryString.ps1 │ ├── Expand-Data.ps1 │ ├── Get-GraphBaseUri.ps1 │ ├── Get-MsftUserRealm.ps1 │ ├── Get-ObjectPropertyValue.ps1 │ ├── Get-OpenIdProviderConfiguration.ps1 │ ├── Get-ParsedTokenFromResponse.ps1 │ ├── Get-SamlFederationMetadata.ps1 │ ├── Get-X509Certificate.ps1 │ ├── Import-AdfsModule.ps1 │ ├── Invoke-CommandAsSystem.ps1 │ ├── New-AdfsLoginFormFields.ps1 │ ├── Resolve-XmlAttribute.ps1 │ ├── Resolve-XmlElement.ps1 │ ├── SamlMessages.format.ps1xml │ ├── SamlRedirect.html │ ├── SamlRequestTemplate.xml │ ├── Test-IpAddressInSubnet.ps1 │ ├── Test-MgCommandPrerequisites.ps1 │ ├── Test-MgModulePrerequisites.ps1 │ ├── Test-PsElevation.ps1 │ └── Write-HostPrompt.ps1 ├── tests ├── Get-MsIdApplicationIdByAppId.tests.ps1.disable ├── Get-MsIdAuthorityUri.tests.ps1.disable ├── Get-MsIdOpenIdProviderConfiguration.tests.ps1.disable ├── Get-MsIdSamlFederationMetadata.tests.ps1.disable ├── Get-MsIdServicePrincipalIdByAppId.tests.ps1.disable ├── Integration.tests.ps1.disable ├── MSIdentityTools.tests.ps1 ├── Revoke-MsIdServicePrincipalConsent.tests.ps1.disable └── Test-MgCommandPrerequisites.tests.ps1 └── website ├── .gitignore ├── README.md ├── babel.config.js ├── docs ├── assets │ └── export-msidazuremfareport-sample.png ├── commands │ ├── Add-MsIdServicePrincipal.mdx │ ├── Confirm-MsIdJwtTokenSignature.mdx │ ├── ConvertFrom-MsIdAadcAadConnectorSpaceDn.mdx │ ├── ConvertFrom-MsIdAadcSourceAnchor.mdx │ ├── ConvertFrom-MsIdJwtToken.mdx │ ├── ConvertFrom-MsIdSamlMessage.mdx │ ├── ConvertFrom-MsIdUniqueTokenIdentifier.mdx │ ├── Expand-MsIdJwtTokenPayload.mdx │ ├── Export-MsIdAppConsentGrantReport.mdx │ ├── Export-MsIdAzureMfaReport.mdx │ ├── Find-MsIdUnprotectedUsersWithAdminRoles.mdx │ ├── Get-MsIdAdfsSamlToken.mdx │ ├── Get-MsIdAdfsSampleApp.mdx │ ├── Get-MsIdAdfsWsFedToken.mdx │ ├── Get-MsIdAdfsWsTrustToken.mdx │ ├── Get-MsIdApplicationIdByAppId.mdx │ ├── Get-MsIdAuthorityUri.mdx │ ├── Get-MsIdAzureIpRange.mdx │ ├── Get-MsIdAzureUsers.mdx │ ├── Get-MsIdCrossTenantAccessActivity.mdx │ ├── Get-MsIdGroupWithExpiration.mdx │ ├── Get-MsIdGroupWritebackConfiguration.mdx │ ├── Get-MsIdHasMicrosoftAccount.mdx │ ├── Get-MsIdInactiveSignInUser.mdx │ ├── Get-MsIdIsViralUser.mdx │ ├── Get-MsIdMsftIdentityAssociation.mdx │ ├── Get-MsIdO365Endpoints.mdx │ ├── Get-MsIdOpenIdProviderConfiguration.mdx │ ├── Get-MsIdProvisioningLogStatistics.mdx │ ├── Get-MsIdSamlFederationMetadata.mdx │ ├── Get-MsIdServicePrincipalIdByAppId.mdx │ ├── Get-MsIdSigningKeyThumbprint.mdx │ ├── Get-MsIdUnmanagedExternalUser.mdx │ ├── Get-MsIdUnredeemedInvitedUser.mdx │ ├── Import-MsIdAdfsSampleApp.mdx │ ├── Import-MsIdAdfsSamplePolicy.mdx │ ├── Invoke-MsIdAzureAdSamlRequest.mdx │ ├── New-MsIdClientSecret.mdx │ ├── New-MsIdSamlRequest.mdx │ ├── New-MsIdTemporaryUserPassword.mdx │ ├── New-MsIdWsTrustRequest.mdx │ ├── Reset-MsIdExternalUser.mdx │ ├── Resolve-MsIdAzureIpAddress.mdx │ ├── Resolve-MsIdTenant.mdx │ ├── Revoke-MsIdServicePrincipalConsent.mdx │ ├── Set-MsIdServicePrincipalVisibleInMyApps.mdx │ ├── Set-MsIdWindowsTlsSettings.mdx │ ├── Show-MsIdJwtToken.mdx │ ├── Show-MsIdSamlToken.mdx │ ├── Split-MsIdEntitlementManagementConnectedOrganization.mdx │ ├── Test-MsIdAzureAdDeviceRegConnectivity.mdx │ ├── Test-MsIdCBATrustStoreConfiguration.mdx │ ├── Update-MsIdApplicationSigningKeyThumbprint.mdx │ ├── Update-MsIdGroupWritebackConfiguration.mdx │ ├── docusaurus.sidebar.js │ └── readme.md └── intro.md ├── docusaurus.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src ├── components │ └── DocLinkList │ │ ├── DocLinkList.tsx │ │ └── styles.module.css └── css │ └── custom.css ├── static ├── .nojekyll └── img │ ├── favicon.ico │ └── logo.svg └── tsconfig.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | ## GitHub Code Owners 2 | ## https://github.blog/2017-07-06-introducing-code-owners/ 3 | ## https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 4 | 5 | # Default 6 | * @AzureAD/identity-cxp-code 7 | 8 | # Tooling Configuration 9 | .*/* @AzureAD/identity-cxp-code-admins 10 | .github/* @AzureAD/identity-cxp-code-admins 11 | .vscode/* @AzureAD/identity-cxp-code-admins 12 | 13 | # Automation 14 | build/* @AzureAD/identity-cxp-code-admins 15 | 16 | # Documentation 17 | *.md @AzureAD/identity-cxp-code 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug, ToTriage 6 | assignees: '' 7 | --- 8 | 9 | ### Describe the bug 10 | 11 | A clear and concise description of what the bug is. 12 | 13 | ### To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 1. Go to '...' 17 | 2. Click on '....' 18 | 3. Scroll down to '....' 19 | 4. See error 20 | 21 | ### Expected behavior 22 | 23 | A clear and concise description of what you expected to happen. 24 | 25 | ### Screenshots 26 | 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | ### Environment (please complete the following information) 30 | 33 | 34 | - Operating System: [e.g. Windows, MacOS, Linux] 35 | - PowerShell Version: [e.g. Windows PowerShell 5.1, PowerShell 7.2 ] 36 | - MS Graph PowerShell SDK Module Version: [e.g. 1.6.2, 1.9.3, 2.0.0] 37 | 38 | ### Additional context 39 | 40 | Add any other context about the problem here. 41 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | Fixes # 3 | 4 | ### Changes proposed in this pull request 5 | 6 | - 7 | - 8 | - 9 | 10 | ### Testing 11 | 12 | 13 | ### Documentation 14 | 15 | - [ ] All exported commands have Synopsis, Parameter Descriptions, and at least one Example. 16 | 17 | ### Other links 18 | 19 | - 20 | - 21 | - 22 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yaml: -------------------------------------------------------------------------------- 1 | # Regenerates the documentation for the "Command Reference" section of the site https://maester.dev 2 | name: build-docs 3 | 4 | on: 5 | workflow_dispatch: 6 | # - "!src/MSIdentityTools.psd1" 7 | # push: 8 | # branches: 9 | # - main 10 | # paths: 11 | # - "src/**" 12 | 13 | # Sets permissions of the GITHUB_TOKEN to allow updates to the repository 14 | permissions: 15 | contents: write 16 | pull-requests: write 17 | 18 | jobs: 19 | build: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Update docs 24 | run: ./build/Update-CommandReference.ps1 25 | shell: pwsh 26 | 27 | - name: Create PR to update repo 28 | uses: peter-evans/create-pull-request@v6 29 | with: 30 | title: Update command reference 31 | -------------------------------------------------------------------------------- /.github/workflows/deploy-site.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | # Review gh actions docs if you want to further define triggers, paths, etc 9 | # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#on 10 | 11 | jobs: 12 | 13 | build: 14 | name: Build website 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 0 20 | - name: Update command reference 21 | run: ./build/Update-CommandReference.ps1 22 | shell: pwsh 23 | 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: 18 27 | cache: npm 28 | cache-dependency-path: './website/package-lock.json' 29 | 30 | - name: Install dependencies 31 | run: npm ci 32 | working-directory: ./website/ 33 | 34 | - name: Build website 35 | run: npm run build 36 | working-directory: ./website/ 37 | 38 | - name: Upload build artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: ./website/build 42 | 43 | deploy: 44 | name: Deploy to GitHub Pages 45 | needs: build 46 | runs-on: ubuntu-latest 47 | 48 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 49 | permissions: 50 | pages: write # to deploy to Pages 51 | id-token: write # to verify the deployment originates from an appropriate source 52 | 53 | # Deploy to the github-pages environment 54 | environment: 55 | name: github-pages 56 | url: ${{ steps.deployment.outputs.page_url }} 57 | 58 | steps: 59 | - name: Deploy to GitHub Pages 60 | id: deployment 61 | uses: actions/deploy-pages@v4 62 | -------------------------------------------------------------------------------- /.github/workflows/wiki.yml: -------------------------------------------------------------------------------- 1 | name: Update Wiki 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | # branches: 7 | # - main 8 | # paths: 9 | # - '.github/workflows/wiki.yml' 10 | # - 'src/**' 11 | # - 'build/*Wiki.ps1' 12 | 13 | jobs: 14 | update: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: "Checkout Code" 18 | uses: actions/Checkout@v2 19 | - name: "Generate Pages" 20 | run: | 21 | Install-Module -Name "Microsoft.Graph.Authentication" -RequiredVersion "2.8.0" -Force 22 | ./Build-Wiki.ps1 23 | shell: pwsh 24 | working-directory: ./build 25 | - name: "Upload Wiki" 26 | run: | 27 | echo "set git user" 28 | git config --global user.email "action@github.com" 29 | git config --global user.name "Github Action" 30 | echo "clone the remote wiki" 31 | git clone https://githubaction:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY.wiki.git tmp.wiki 32 | cd tmp.wiki 33 | echo "copy generated files" 34 | cp -f ../.wiki/*.md ./ 35 | echo "add generated files" 36 | git add -A 37 | echo "commit new wiki" 38 | git commit --allow-empty -m "update wiki $GITHUB_SHA" 39 | echo "push to wiki" 40 | git push 41 | env: 42 | GITHUB_TOKEN: ${{ github.token }} 43 | 44 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "ms-vscode.powershell" 8 | ] 9 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // File Formatting 3 | "files.insertFinalNewline": true, 4 | "files.trimFinalNewlines": true, 5 | "files.associations": { 6 | "CODEOWNERS": "properties" 7 | }, 8 | 9 | // PowerShell Code Formatting 10 | "powershell.codeFormatting.addWhitespaceAroundPipe": true, 11 | "powershell.codeFormatting.alignPropertyValuePairs": true, 12 | "powershell.codeFormatting.autoCorrectAliases": true, 13 | "powershell.codeFormatting.avoidSemicolonsAsLineTerminators": true, 14 | "powershell.codeFormatting.newLineAfterCloseBrace": true, 15 | "powershell.codeFormatting.newLineAfterOpenBrace": true, 16 | "powershell.codeFormatting.openBraceOnSameLine": true, 17 | "powershell.codeFormatting.pipelineIndentationStyle": "NoIndentation", 18 | "powershell.codeFormatting.preset": "Stroustrup", 19 | "powershell.codeFormatting.trimWhitespaceAroundPipe": true, 20 | "powershell.codeFormatting.useConstantStrings": false, 21 | "powershell.codeFormatting.useCorrectCasing": true, 22 | "powershell.codeFormatting.whitespaceAfterSeparator": true, 23 | "powershell.codeFormatting.whitespaceAroundOperator": true, 24 | "powershell.codeFormatting.whitespaceBeforeOpenBrace": true, 25 | "powershell.codeFormatting.whitespaceBeforeOpenParen": true, 26 | "powershell.codeFormatting.whitespaceBetweenParameters": true, 27 | "powershell.codeFormatting.whitespaceInsideBrace": true 28 | 29 | // Editor Settings 30 | //"editor.formatOnSave": true, 31 | //"editor.formatOnSaveMode": "modificationsIfAvailable" 32 | } 33 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | ## How to file issues and get help 2 | 3 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 4 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 5 | feature request as a new Issue. 6 | 7 | For help and questions about using this project, please raise Issues. 8 | 9 | ## Filing Issues 10 | Please file a new issue with the appropriate label for the type if item on the [issues](https://github.com/AzureAD/MSIdentityTools/issues) page 11 | 12 | ## Bugs or Functional Issues 13 | For bugs please create an issue with the appropriate label on the repo page 14 | 15 | - **bug** - Issues with the functionality of the cmdlet or script in the module where something isn't working as expected 16 | 17 | ## Documentation Enhancements 18 | 19 | - **documentation** - Issues or suggestions on the documenation for the module 20 | 21 | ## Feature Requests 22 | 23 | - **enhacement** - Idea for improvement or enhancement of the module 24 | 25 | ## Issue Lifecycle 26 | Issues will be triaged as appropriate. 27 | 28 | ## Microsoft Support Policy 29 | 30 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 31 | -------------------------------------------------------------------------------- /assets/aadconsentgrantpermissiontable.csv: -------------------------------------------------------------------------------- 1 | Type,Permission,Privilege,Reason 2 | Delegated,Mail.ReadBasic,Medium,DataExfiltration 3 | Delegated,Mail,High,Phishing 4 | Delegated,Contacts,High,Phishing 5 | Delegated,MailboxSettings,High,Phishing 6 | Delegated,People,High,Phishing 7 | Delegated,Files,High,Phishing 8 | Delegated,AllSites,High,Phishing 9 | Delegated,Notes,High,Phishing 10 | Delegated,Policy,High,Phishing 11 | Delegated,AppRoleAssignment.ReadWrite.All,High,Phishing 12 | Delegated,Directory.AccessAsUser.All,High,Phishing 13 | Delegated,user_impersonation,High,Phishing 14 | Delegated,Application.ReadWrite.All,High,BroadImpact 15 | Delegated,Directory.ReadWrite.All,High,BroadImpact 16 | Delegated,Domain.ReadWrite.All,High,BroadImpact 17 | Delegated,EduRoster.ReadWrite.All,High,BroadImpact 18 | Delegated,Group.ReadWrite.All,High,BroadImpact 19 | Delegated,Member.Read.Hidden,High,BroadImpact 20 | Delegated,RoleManagement.ReadWrite.Directory,High,BroadImpact 21 | Delegated,RoleAssignmentSchedule.ReadWrite.Directory,High,BroadImpact 22 | Delegated,RoleEligibilitySchedule.ReadWrite.Directory,High,BroadImpact 23 | Delegated,User.ReadWrite.All,High,BroadImpact 24 | Delegated,User.ManageCreds.All,High,BroadImpact 25 | Delegated,User.Export.All,High,BroadImpact 26 | Application,Mail,High,Phishing 27 | Application,Contacts,High,Phishing 28 | Application,MailboxSettings,High,Phishing 29 | Application,People,High,Phishing 30 | Application,Files,High,Phishing 31 | Application,Sites,High,Phishing 32 | Application,AllSites,High,Phishing 33 | Application,Notes,High,Phishing 34 | Application,Policy,High,BroadImpact 35 | Application,PrivilegedAccess,High,BroadImpact 36 | Application,PrivilegedAssignmentSchedule,High,BroadImpact 37 | Application,PrivilegedEligibilitySchedule,High,BroadImpact 38 | Application,AppRoleAssignment.ReadWrite.All,High,Phishing 39 | Application,Directory.AccessAsUser.All,High,Phishing 40 | Application,user_impersonation,High,Phishing 41 | Application,Application.ReadWrite.All,High,BroadImpact 42 | Application,Directory.ReadWrite.All,High,BroadImpact 43 | Application,Domain.ReadWrite.All,High,BroadImpact 44 | Application,EduRoster.ReadWrite.All,High,BroadImpact 45 | Application,Group.ReadWrite.All,High,BroadImpact 46 | Application,Member.Read.Hidden,High,BroadImpact 47 | Application,UserAuthenticationMethod.ReadWrite.All,High,BroadImpact 48 | Application,RoleManagement.ReadWrite.Directory,High,BroadImpact 49 | Application,User.ReadWrite.All,High,BroadImpact 50 | Application,User.ManageCreds.All,High,BroadImpact 51 | Application,CallRecords.Read.All,High,SensitiveData 52 | Delegated,User.Read,Low,Common pattern 53 | Delegated,User.ReadBasic.All,Low,Common pattern 54 | Delegated,openid,Low,Common pattern 55 | Delegated,email,Low,Common pattern 56 | Delegated,profile,Low,Common pattern 57 | Delegated,offline_access,Low,Common pattern when used with other low permissions 58 | -------------------------------------------------------------------------------- /build/Get-PSModuleInfo.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | # Path to Module Manifest 4 | [parameter(Mandatory = $false)] 5 | [string] $ModuleManifestPath = "..\src", 6 | # Path to packages.config file 7 | [parameter(Mandatory = $false)] 8 | [string] $PackagesConfigPath = "..\", 9 | # Return trimmed version to the depth specified 10 | [parameter(Mandatory = $false)] 11 | [int] $TrimVersionDepth 12 | ) 13 | 14 | ## Initialize 15 | Import-Module "$PSScriptRoot\CommonFunctions.psm1" -Force -WarningAction SilentlyContinue -ErrorAction Stop 16 | 17 | [System.IO.FileInfo] $ModuleManifestFileInfo = Get-PathInfo $ModuleManifestPath -DefaultFilename "*.psd1" -ErrorAction Stop 18 | [System.IO.FileInfo] $PackagesConfigFileInfo = Get-PathInfo $PackagesConfigPath -DefaultFilename "packages.config" -ErrorAction SilentlyContinue 19 | 20 | ## Read Module Manifest 21 | $ModuleManifest = Import-PowerShellDataFile $ModuleManifestFileInfo.FullName -ErrorAction Stop 22 | 23 | ## Output moduleName Azure Pipelines 24 | $env:moduleName = $ModuleManifestFileInfo.BaseName 25 | Write-Host ('##vso[task.setvariable variable=moduleName;isOutput=true]{0}' -f $env:moduleName) 26 | Write-Host ('##[debug] {0} = {1}' -f 'moduleName', $env:moduleName) 27 | 28 | ## Output moduleVersion Azure Pipelines 29 | $env:moduleVersion = $ModuleManifest['ModuleVersion'] 30 | Write-Host ('##vso[task.setvariable variable=moduleVersion;isOutput=true]{0}' -f $env:moduleVersion) 31 | Write-Host ('##[debug] {0} = {1}' -f 'moduleVersion', $env:moduleVersion) 32 | 33 | if ($TrimVersionDepth) { 34 | $env:moduleVersionTrimmed = $env:moduleVersion -replace ('(?<=^(.?[0-9]+){{{0},}})(.[0-9]+)+$' -f $TrimVersionDepth), '' 35 | Write-Host ('##vso[task.setvariable variable=moduleVersionTrimmed;isOutput=true]{0}' -f $env:moduleVersionTrimmed) 36 | Write-Host ('##[debug] {0} = {1}' -f 'moduleVersionTrimmed', $env:moduleVersionTrimmed) 37 | } 38 | 39 | ## Read Packages Configuration 40 | if ($PackagesConfigFileInfo.Exists) { 41 | $xmlPackagesConfig = New-Object xml 42 | $xmlPackagesConfig.Load($PackagesConfigFileInfo.FullName) 43 | 44 | foreach ($package in $xmlPackagesConfig.packages.package) { 45 | ## Output packageVersion Azure Pipelines 46 | Set-Variable ('env:{0}' -f $package.id) -Value $package.version 47 | Write-Host ('##vso[task.setvariable variable=version.{0};isOutput=true]{1}' -f $package.id, $package.version) 48 | Write-Host ('##[debug] version.{0} = {1}' -f $package.id, $package.version) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /build/Launch-PSModule.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | # Module to Launch 3 | [Parameter(Mandatory = $false)] 4 | [string] $ModuleManifestPath = '.\src\*.psd1', 5 | # ScriptBlock to Execute After Module Import 6 | [Parameter(Mandatory = $false)] 7 | [scriptblock] $PostImportScriptBlock, 8 | # Paths to PowerShell Executables 9 | [Parameter(Mandatory = $false)] 10 | [string[]] $PowerShellPaths = @( 11 | 'pwsh' 12 | #'powershell' 13 | #'D:\Software\PowerShell-6.2.4-win-x64\pwsh.exe' 14 | ), 15 | # Import Module into the same session 16 | [Parameter(Mandatory = $false)] 17 | [switch] $NoNewWindow #= $true 18 | ) 19 | 20 | ## Restore Module Dependencies 21 | $PSModuleCacheDirectory = &$PSScriptRoot\Restore-PSModuleDependencies.ps1 -ModuleManifestPath $ModuleManifestPath #-OutputDirectory $OutputDirectory.FullName 22 | 23 | ## Launch PSModule 24 | if ($NoNewWindow) { 25 | Import-Module $ModuleManifestPath -PassThru -Force 26 | if ($PostImportScriptBlock) { Invoke-Command -ScriptBlock $PostImportScriptBlock -NoNewScope } 27 | } 28 | else { 29 | [scriptblock] $ScriptBlock = { 30 | param ([string]$ModulePath, [string]$PSModuleCacheDirectory, [scriptblock]$PostImportScriptBlock) 31 | ## Reset PSModulePath environment variable to default value because starting powershell.exe from pwsh.exe (or vice versa) will inherit environment variables for the wrong version of PowerShell. 32 | $PSModulePathDefault = [System.Management.Automation.ModuleIntrinsics]::GetModulePath($null, [System.Environment]::GetEnvironmentVariable('PSMODULEPATH', [EnvironmentVariableTarget]::Machine), [System.Environment]::GetEnvironmentVariable('PSMODULEPATH', [EnvironmentVariableTarget]::User)) 33 | [Environment]::SetEnvironmentVariable("PSMODULEPATH", $PSModulePathDefault) 34 | ## Add PSModuleCacheDirectory to PSModulePath environment variable 35 | if (!$env:PSModulePath.Contains($PSModuleCacheDirectory)) { $env:PSModulePath += '{0}{1}' -f [IO.Path]::PathSeparator, $PSModuleCacheDirectory } 36 | ## Import Module and Execute Post-Import ScriptBlock 37 | Import-Module $ModulePath -PassThru 38 | Invoke-Command -ScriptBlock $PostImportScriptBlock -NoNewScope 39 | } 40 | $strScriptBlock = 'Invoke-Command -ScriptBlock {{ {0} }} -ArgumentList {1}, {2}, {{ {3} }}' -f $ScriptBlock, $ModuleManifestPath, $PSModuleCacheDirectory, $PostImportScriptBlock 41 | #$strScriptBlock = 'Import-Module {0} -PassThru' -f $ModuleManifestPath 42 | 43 | foreach ($Path in $PowerShellPaths) { 44 | if ($Path -eq 'wsl') { 45 | Start-Process $Path -ArgumentList ('pwsh' , '-NoExit', '-NoProfile', '-EncodedCommand', [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($strScriptBlock))) 46 | } 47 | else { 48 | Start-Process $Path -ArgumentList ('-NoExit', '-NoProfile', '-EncodedCommand', [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($strScriptBlock))) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /build/PesterConfiguration.Baseline.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Run = @{ 3 | #PassThru = $true 4 | } 5 | Filter = @{ 6 | Tag = 'Common' 7 | ExcludeTag = 'IntegrationTest' 8 | } 9 | CodeCoverage = @{ 10 | Enabled = $true 11 | OutputFormat = 'JaCoCo' 12 | OutputPath = '.\build\TestResults\CodeCoverage.xml' 13 | RecursePaths = $false 14 | } 15 | TestResult = @{ 16 | Enabled = $true 17 | OutputFormat = 'NUnitXML' 18 | OutputPath = '.\build\TestResults\TestResult.xml' 19 | } 20 | Output = @{ 21 | #Verbosity = 'Detailed' 22 | } 23 | } -------------------------------------------------------------------------------- /build/PesterConfiguration.CD.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Run = @{ 3 | PassThru = $true 4 | } 5 | Filter = @{ 6 | #Tag = '' 7 | #ExcludeTag = 'IntegrationTest' 8 | } 9 | CodeCoverage = @{ 10 | Enabled = $true 11 | OutputFormat = 'JaCoCo' 12 | OutputPath = '.\build\TestResults\CodeCoverage.xml' 13 | RecursePaths = $false 14 | } 15 | TestResult = @{ 16 | Enabled = $true 17 | OutputFormat = 'NUnitXML' 18 | OutputPath = '.\build\TestResults\TestResult.xml' 19 | } 20 | Output = @{ 21 | #Verbosity = 'Detailed' 22 | } 23 | } -------------------------------------------------------------------------------- /build/PesterConfiguration.CI.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Run = @{ 3 | PassThru = $true 4 | } 5 | Filter = @{ 6 | #Tag = '' 7 | ExcludeTag = 'Deferrable', 'IntegrationTest', 'Slow' 8 | } 9 | CodeCoverage = @{ 10 | Enabled = $true 11 | OutputFormat = 'JaCoCo' 12 | OutputPath = '.\build\TestResults\CodeCoverage.xml' 13 | RecursePaths = $false 14 | } 15 | TestResult = @{ 16 | Enabled = $true 17 | OutputFormat = 'NUnitXML' 18 | OutputPath = '.\build\TestResults\TestResult.xml' 19 | } 20 | Output = @{ 21 | #Verbosity = 'Detailed' 22 | } 23 | } -------------------------------------------------------------------------------- /build/PesterConfiguration.Debug.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Run = @{ 3 | PassThru = $true 4 | } 5 | Filter = @{ 6 | #Tag = 'Common' 7 | ExcludeTag = 'IntegrationTest' 8 | } 9 | Debug = @{ 10 | ShowFullErrors = $false 11 | ShowNavigationMarkers = $false 12 | WriteDebugMessages = $false 13 | } 14 | Output = @{ 15 | Verbosity = 'Detailed' 16 | } 17 | } -------------------------------------------------------------------------------- /build/PesterConfiguration.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Run = @{ 3 | PassThru = $true 4 | } 5 | Filter = @{ 6 | #Tag = 'Debug' 7 | #ExcludeTag = 'IntegrationTest' 8 | } 9 | CodeCoverage = @{ 10 | Enabled = $true 11 | OutputFormat = 'JaCoCo' 12 | OutputPath = '.\build\TestResults\CodeCoverage.xml' 13 | RecursePaths = $false 14 | } 15 | TestResult = @{ 16 | Enabled = $false 17 | OutputFormat = 'NUnitXML' 18 | OutputPath = '.\build\TestResults\TestResult.xml' 19 | } 20 | Output = @{ 21 | Verbosity = 'Detailed' 22 | } 23 | } -------------------------------------------------------------------------------- /build/Publish-PSModule.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 7.0 2 | param 3 | ( 4 | # Path to Module Manifest 5 | [Parameter(Mandatory = $false)] 6 | [string] $ModuleManifestPath = ".\release\*\*.*.*\*.psd1", 7 | # Repository for PowerShell Gallery 8 | [Parameter(Mandatory = $false)] 9 | [string] $RepositorySourceLocation = 'https://www.powershellgallery.com/api/v2', 10 | # API Key for PowerShell Gallery 11 | [Parameter(Mandatory = $true)] 12 | [securestring] $NuGetApiKey, 13 | # Unlist from PowerShell Gallery 14 | [Parameter(Mandatory = $false)] 15 | [switch] $Unlist 16 | ) 17 | 18 | ## Initialize 19 | Import-Module "$PSScriptRoot\CommonFunctions.psm1" -Force -WarningAction SilentlyContinue -ErrorAction Stop 20 | 21 | [System.IO.FileInfo] $ModuleManifestFileInfo = Get-PathInfo $ModuleManifestPath -DefaultFilename "*.psd1" -ErrorAction Stop | Select-Object -Last 1 22 | 23 | ## Read Module Manifest 24 | $ModuleManifest = Import-PowerShellDataFile $ModuleManifestFileInfo.FullName 25 | 26 | ## Install Module Dependencies 27 | foreach ($Module in $ModuleManifest['RequiredModules']) { 28 | if ($Module -is [hashtable]) { $ModuleName = $Module.ModuleName } 29 | else { $ModuleName = $Module } 30 | if ($ModuleName -notin $ModuleManifest.PrivateData.PSData['ExternalModuleDependencies'] -and !(Get-Module $ModuleName -ListAvailable)) { 31 | Install-Module $ModuleName -Force -SkipPublisherCheck -Repository PSGallery -AcceptLicense 32 | } 33 | } 34 | 35 | ## Publish 36 | $PSRepositoryAll = Get-PSRepository 37 | $PSRepository = $PSRepositoryAll | Where-Object SourceLocation -Like "$RepositorySourceLocation*" 38 | if (!$PSRepository) { 39 | try { 40 | [string] $RepositoryName = New-Guid 41 | Register-PSRepository $RepositoryName -SourceLocation $RepositorySourceLocation 42 | $PSRepository = Get-PSRepository $RepositoryName 43 | Publish-Module -Path $ModuleManifestFileInfo.DirectoryName -NuGetApiKey (ConvertFrom-SecureString $NuGetApiKey -AsPlainText) -Repository $PSRepository.Name 44 | } 45 | finally { 46 | Unregister-PSRepository $RepositoryName 47 | } 48 | } 49 | else { 50 | Write-Verbose ('Publishing Module Path [{0}]' -f $ModuleManifestFileInfo.DirectoryName) 51 | Publish-Module -Path $ModuleManifestFileInfo.DirectoryName -NuGetApiKey (ConvertFrom-SecureString $NuGetApiKey -AsPlainText) -Repository $PSRepository.Name 52 | } 53 | 54 | ## Unlist the Package 55 | if ($Unlist) { 56 | if ($ModuleManifest.PrivateData.PSData['Prerelease']) { 57 | Invoke-RestMethod -Method Delete -Uri ("{0}/{1}/{2}-{3}" -f $PSRepository.PublishLocation, $ModuleManifestFileInfo.BaseName, $ModuleManifest['ModuleVersion'], $ModuleManifest.PrivateData.PSData['Prerelease']) -Headers @{ 'X-NuGet-ApiKey' = ConvertFrom-SecureString $NuGetApiKey -AsPlainText } 58 | } 59 | else { 60 | Invoke-RestMethod -Method Delete -Uri ("{0}/{1}/{2}" -f $PSRepository.PublishLocation, $ModuleManifestFileInfo.BaseName, $ModuleManifest['ModuleVersion']) -Headers @{ 'X-NuGet-ApiKey' = ConvertFrom-SecureString $NuGetApiKey -AsPlainText } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /build/Restore-NugetPackages.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | # Directory used to base all relative paths 4 | [Parameter(Mandatory = $false)] 5 | [string] $BaseDirectory = "..\", 6 | # 7 | [Parameter(Mandatory = $false)] 8 | [string] $PackagesConfigPath = ".\packages.config", 9 | # 10 | [Parameter(Mandatory = $false)] 11 | [string] $NuGetConfigPath, 12 | # 13 | [Parameter(Mandatory = $false)] 14 | [string] $OutputDirectory, 15 | # 16 | [Parameter(Mandatory = $false)] 17 | [string] $NuGetPath = ".\build", 18 | # 19 | [Parameter(Mandatory = $false)] 20 | [uri] $NuGetUri = 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' 21 | ) 22 | 23 | ## Initialize 24 | Remove-Module CommonFunctions -ErrorAction SilentlyContinue 25 | Import-Module $PSScriptRoot\CommonFunctions.psm1 -DisableNameChecking 26 | 27 | [System.IO.DirectoryInfo] $BaseDirectoryInfo = Get-PathInfo $BaseDirectory -InputPathType Directory -ErrorAction Stop 28 | [System.IO.FileInfo] $PackagesConfigFileInfo = Get-PathInfo $PackagesConfigPath -DefaultDirectory $BaseDirectoryInfo.FullName -DefaultFilename "packages.config" -ErrorAction Stop 29 | [System.IO.FileInfo] $NuGetConfigFileInfo = Get-PathInfo $NuGetConfigPath -DefaultDirectory $BaseDirectoryInfo.FullName -DefaultFilename "NuGet.config" -SkipEmptyPaths 30 | [System.IO.DirectoryInfo] $OutputDirectoryInfo = Get-PathInfo $OutputDirectory -InputPathType Directory -DefaultDirectory $BaseDirectoryInfo.FullName -SkipEmptyPaths -ErrorAction SilentlyContinue 31 | [System.IO.FileInfo] $NuGetFileInfo = Get-PathInfo $NuGetPath -DefaultDirectory $BaseDirectoryInfo.FullName -DefaultFilename "nuget.exe" -ErrorAction SilentlyContinue 32 | #Set-Alias nuget -Value $itemNuGetPath.FullName 33 | 34 | ## Download NuGet 35 | if (!$NuGetFileInfo.Exists) { 36 | Invoke-WebRequest $NuGetUri.AbsoluteUri -UseBasicParsing -OutFile $NuGetFileInfo.FullName 37 | } 38 | 39 | ## Run NuGet 40 | $argsNuget = New-Object System.Collections.Generic.List[string] 41 | $argsNuget.Add('restore') 42 | $argsNuget.Add($PackagesConfigFileInfo.FullName) 43 | if ($VerbosePreference -eq 'Continue') { 44 | $argsNuget.Add('-Verbosity') 45 | $argsNuget.Add('Detailed') 46 | } 47 | if ($NuGetConfigFileInfo) { 48 | $argsNuget.Add('-ConfigFile') 49 | $argsNuget.Add($NuGetConfigFileInfo.FullName) 50 | } 51 | if ($OutputDirectoryInfo) { 52 | $argsNuget.Add('-OutputDirectory') 53 | $argsNuget.Add($OutputDirectoryInfo.FullName) 54 | } 55 | 56 | Use-StartProcess $NuGetFileInfo.FullName -ArgumentList $argsNuget 57 | -------------------------------------------------------------------------------- /build/Restore-PSModuleDependencies.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | # Path to Module Manifest 4 | [Parameter(Mandatory = $false)] 5 | [string] $ModuleManifestPath = ".\src\*.psd1", 6 | # 7 | [Parameter(Mandatory = $false)] 8 | [string] $PSModuleCacheDirectory = ".\build\TestResults\PSModuleCache", 9 | # 10 | [Parameter(Mandatory = $false)] 11 | [string[]] $Repository = "PSGallery", 12 | # 13 | [Parameter(Mandatory = $false)] 14 | [switch] $SkipExternalModuleDependencies 15 | ) 16 | 17 | ## Initialize 18 | Import-Module "$PSScriptRoot\CommonFunctions.psm1" -Force -WarningAction SilentlyContinue -ErrorAction Stop 19 | #$PSModulePathBackup = $env:PSModulePath 20 | 21 | [System.IO.FileInfo] $ModuleManifestFileInfo = Get-PathInfo $ModuleManifestPath -DefaultFilename "*.psd1" -ErrorAction Stop | Select-Object -Last 1 22 | [System.IO.DirectoryInfo] $PSModuleCacheDirectoryInfo = Get-PathInfo $PSModuleCacheDirectory -InputPathType Directory -SkipEmptyPaths -ErrorAction SilentlyContinue 23 | 24 | ## Read Module Manifest 25 | $ModuleManifest = Import-PowerShellDataFile $ModuleManifestFileInfo.FullName 26 | 27 | ## Restore Nuget Packages 28 | #.\build\Restore-NugetPackages.ps1 -BaseDirectory ".\" -Verbose:$false 29 | 30 | ## Create directory 31 | if ($ModuleManifest['RequiredModules']) { 32 | Assert-DirectoryExists $PSModuleCacheDirectoryInfo.FullName -ErrorAction Stop | Out-Null 33 | if (!$env:PSModulePath.Contains($PSModuleCacheDirectoryInfo.FullName)) { $env:PSModulePath += '{0}{1}' -f [IO.Path]::PathSeparator, $PSModuleCacheDirectoryInfo.FullName } 34 | } 35 | 36 | ## Save Module Dependencies 37 | foreach ($Module in $ModuleManifest['RequiredModules']) { 38 | if (!(Get-Module -FullyQualifiedName $Module -ListAvailable -ErrorAction SilentlyContinue)) { 39 | $paramSaveModule = @{} 40 | if ($Module -is [hashtable]) { 41 | $paramSaveModule['Name'] = $Module.ModuleName 42 | if ($Module.ContainsKey('ModuleVersion')) { $paramSaveModule['MinimumVersion'] = $Module.ModuleVersion } 43 | elseif ($Module.ContainsKey('RequiredVersion')) { $paramSaveModule['RequiredVersion'] = $Module.RequiredVersion } 44 | } 45 | else { $paramSaveModule['Name'] = $Module } 46 | 47 | if (!$SkipExternalModuleDependencies -or $paramSaveModule['Name'] -notin $ModuleManifest.PrivateData.PSData['ExternalModuleDependencies']) { 48 | Save-Module -Repository $Repository -Path $PSModuleCacheDirectoryInfo.FullName @paramSaveModule 49 | } 50 | } 51 | } 52 | 53 | #$env:PSModulePath = $PSModulePathBackup 54 | 55 | return $PSModuleCacheDirectoryInfo.FullName 56 | -------------------------------------------------------------------------------- /build/Sign-PSModule.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | # Path to Module Manifest 4 | [Parameter(Mandatory = $false)] 5 | [string] $ModuleManifestPath = ".\release\*\*.*.*", 6 | # Specifies the certificate that will be used to sign the script or file. 7 | [Parameter(Mandatory = $false)] 8 | [object] $SigningCertificate = (Get-ChildItem Cert:\CurrentUser\My\E7413D745138A6DC584530AECE27CEFDDA9D9CD6 -CodeSigningCert), 9 | # Uses the specified time stamp server to add a time stamp to the signature. 10 | [Parameter(Mandatory = $false)] 11 | [string] $TimestampServer = 'http://timestamp.digicert.com', 12 | # Generate and sign catalog file 13 | [Parameter(Mandatory = $false)] 14 | [switch] $AddFileCatalog 15 | ) 16 | 17 | ## Initialize 18 | Import-Module "$PSScriptRoot\CommonFunctions.psm1" -Force -WarningAction SilentlyContinue -ErrorAction Stop 19 | 20 | [System.IO.FileInfo] $ModuleManifestFileInfo = Get-PathInfo $ModuleManifestPath -DefaultFilename "*.psd1" | Select-Object -Last 1 21 | 22 | ## Parse Signing Certificate 23 | if ($SigningCertificate -is [System.Security.Cryptography.X509Certificates.X509Certificate2]) { } 24 | elseif ($SigningCertificate -is [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]) { $SigningCertificate = $SigningCertificate[-1] } 25 | else { $SigningCertificate = Get-X509Certificate $SigningCertificate -EndEntityCertificateOnly } 26 | 27 | ## Read Module Manifest 28 | $ModuleManifest = Import-PowerShellDataFile $ModuleManifestFileInfo.FullName 29 | 30 | $FileList = $ModuleManifest['FileList'] -like "*.ps*1*" 31 | for ($i = 0; $i -lt $FileList.Count; $i++) { 32 | $FileList[$i] = Join-Path $ModuleManifestFileInfo.DirectoryName $FileList[$i] -Resolve 33 | } 34 | 35 | #$FileList = Get-ChildItem $ModuleManifestFileInfo.DirectoryName -Filter "*.ps*1" -Recurse 36 | 37 | ## Sign PowerShell Files 38 | Set-AuthenticodeSignature $FileList -Certificate $SigningCertificate -HashAlgorithm SHA256 -IncludeChain NotRoot -TimestampServer $TimestampServer 39 | 40 | ## Generate and Sign File Catalog 41 | if ($AddFileCatalog) { 42 | $FileCatalogPath = Join-Path $ModuleManifestFileInfo.Directory ('{0}.cat' -f $ModuleManifestFileInfo.Name) 43 | $FileCatalogPath = New-FileCatalog $FileCatalogPath -Path $ModuleManifestFileInfo.Directory -CatalogVersion 2.0 44 | Set-AuthenticodeSignature $FileCatalog.FullName -Certificate $SigningCertificate -HashAlgorithm SHA256 -IncludeChain NotRoot -TimestampServer $TimestampServer 45 | } 46 | -------------------------------------------------------------------------------- /build/Test-PSModule.ps1: -------------------------------------------------------------------------------- 1 | param 2 | ( 3 | # 4 | [Parameter(Mandatory = $false)] 5 | [string] $ModuleManifestPath = ".\src\*.psd1", 6 | # 7 | [Parameter(Mandatory = $false)] 8 | [string] $PSModuleCacheDirectory = ".\build\TestResults\PSModuleCache", 9 | # 10 | [Parameter(Mandatory = $false)] 11 | [string] $PesterConfigurationPath = ".\build\PesterConfiguration.psd1", 12 | # 13 | [Parameter(Mandatory = $false)] 14 | [string] $TestResultPath, 15 | # 16 | [Parameter(Mandatory = $false)] 17 | [string] $CodeCoveragePath, 18 | # 19 | [Parameter(Mandatory = $false)] 20 | [string] $ModuleTestsDirectory = ".\tests" 21 | ) 22 | 23 | ## Initialize 24 | Import-Module "$PSScriptRoot\CommonFunctions.psm1" -Force -WarningAction SilentlyContinue -ErrorAction Stop 25 | 26 | [System.IO.FileInfo] $ModuleManifestFileInfo = Get-PathInfo $ModuleManifestPath -DefaultFilename "*.psd1" -ErrorAction Stop | Select-Object -Last 1 27 | [System.IO.FileInfo] $TestResultFileInfo = Get-PathInfo $TestResultPath -DefaultFilename 'TestResult.xml' -ErrorAction Ignore 28 | [System.IO.FileInfo] $CodeCoverageFileInfo = Get-PathInfo $CodeCoveragePath -DefaultFilename 'CodeCoverage.xml' -ErrorAction Ignore 29 | [System.IO.DirectoryInfo] $PSModuleCacheDirectoryInfo = Get-PathInfo $PSModuleCacheDirectory -InputPathType Directory -SkipEmptyPaths -ErrorAction SilentlyContinue 30 | [System.IO.FileInfo] $PesterConfigurationFileInfo = Get-PathInfo $PesterConfigurationPath -DefaultFilename 'PesterConfiguration.psd1' -ErrorAction SilentlyContinue 31 | [System.IO.DirectoryInfo] $ModuleTestsDirectoryInfo = Get-PathInfo $ModuleTestsDirectory -InputPathType Directory -ErrorAction SilentlyContinue 32 | 33 | ## Restore Module Dependencies 34 | &$PSScriptRoot\Restore-PSModuleDependencies.ps1 -ModuleManifestPath $ModuleManifestPath -PSModuleCacheDirectory $PSModuleCacheDirectoryInfo.FullName | Out-Null 35 | 36 | Import-Module Pester -MinimumVersion 5.0.0 37 | #$PSModule = Import-Module $ModulePath -PassThru -Force 38 | 39 | $PesterConfiguration = New-PesterConfiguration (Import-PowerShellDataFile $PesterConfigurationFileInfo.FullName) 40 | $PesterConfiguration.Run.Container = New-PesterContainer -Path $ModuleTestsDirectoryInfo.FullName -Data @{ ModulePath = $ModuleManifestFileInfo.FullName } 41 | $PesterConfiguration.CodeCoverage.Path = Split-Path $ModuleManifestFileInfo.FullName -Parent 42 | if ($TestResultPath) { $PesterConfiguration.TestResult.OutputPath = $TestResultFileInfo.FullName } 43 | if ($CodeCoveragePath) { $PesterConfiguration.CodeCoverage.OutputPath = $CodeCoverageFileInfo.FullName } 44 | #$PesterConfiguration.CodeCoverage.OutputPath = [IO.Path]::ChangeExtension($PesterConfiguration.CodeCoverage.OutputPath.Value, "$($PSVersionTable.PSVersion).xml") 45 | #$PesterConfiguration.TestResult.OutputPath = [IO.Path]::ChangeExtension($PesterConfiguration.TestResult.OutputPath.Value, "$($PSVersionTable.PSVersion).xml") 46 | $PesterRun = Invoke-Pester -Configuration $PesterConfiguration 47 | $PesterRun 48 | 49 | ## Return SucceededWithIssues when running in ADO Pipeline and a test fails. 50 | if ($env:AGENT_ID -and $PesterRun -and $PesterRun.Result -ne 'Passed') { Write-Host '##vso[task.complete result=SucceededWithIssues;]FailedTest' } 51 | -------------------------------------------------------------------------------- /build/Update-CommandReference.ps1: -------------------------------------------------------------------------------- 1 | # Description: This script is used to generate the 'Command Reference' section of the Maester docusaurus site 2 | # * This command needs to be run from the root of the project. e.g. ./build/Build-CommandReference.ps1 3 | # * If running the docusaurus site locally you will need to stop and start Docusaurus to clear the 'Module not found' errors after running this command 4 | 5 | if (-not (Get-Module Alt3.Docusaurus.Powershell -ListAvailable)) { Install-Module Alt3.Docusaurus.Powershell -Scope CurrentUser -Force -SkipPublisherCheck } 6 | if (-not (Get-Module PlatyPS -ListAvailable)) { Install-Module PlatyPS -Scope CurrentUser -Force -SkipPublisherCheck } 7 | if (-not (Get-Module ImportExcel -ListAvailable)) { Install-Module ImportExcel -Scope CurrentUser -Force -SkipPublisherCheck } 8 | 9 | Import-Module Alt3.Docusaurus.Powershell 10 | Import-Module PlatyPS 11 | Import-Module ImportExcel 12 | Import-Module ./src/MSIdentityTools.psd1 -Force 13 | 14 | # Generate the command reference markdown 15 | $commandsIndexFile = "./website/docs/commands/readme.md" 16 | $readmeContent = Get-Content $commandsIndexFile # Backup the readme.md since it will be deleted by New-DocusaurusHelp 17 | 18 | # Get all the filenames in the ./powershell/internal folder wihtout the extension 19 | $internalCommands = Get-ChildItem ./src/internal -Filter *.ps1 | ForEach-Object { $_.BaseName } 20 | 21 | New-DocusaurusHelp -Module ./src/MSIdentityTools.psd1 -DocsFolder ./website/docs -NoPlaceHolderExamples -EditUrl https://github.com/azuread/msidentitytools/blob/main/src/ -Exclude $internalCommands 22 | 23 | # Update the markdown to include the synopsis as description so it can be displayed correctly in the doc links. 24 | $cmdMarkdownFiles = Get-ChildItem ./website/docs/commands 25 | foreach ($file in $cmdMarkdownFiles) { 26 | $content = Get-Content $file 27 | $synopsis = $content[($content.IndexOf("## SYNOPSIS") + 2)] # Get the synopsis 28 | if (![string]::IsNullOrWhiteSpace($synopsis)) { 29 | $updatedContent = $content.Replace("id:", "sidebar_class_name: hidden`ndescription: $($synopsis)`nid:") 30 | Set-Content $file $updatedContent 31 | } 32 | } 33 | 34 | Set-Content $commandsIndexFile $readmeContent # Restore the readme content 35 | -------------------------------------------------------------------------------- /build/azure-pipelines/template-psmodule-package.yml: -------------------------------------------------------------------------------- 1 | # PowerShell Module Package Pipeline Template 2 | # https://aka.ms/yaml 3 | 4 | parameters: 5 | - name: moduleName 6 | type: string 7 | default: 8 | - name: moduleVersion 9 | type: string 10 | default: 11 | - name: pipelineId 12 | type: string 13 | default: 14 | - name: artifactInput 15 | type: string 16 | default: 'PSModuleSigned' 17 | - name: artifactOutput 18 | type: string 19 | default: 'PSModulePackage' 20 | 21 | steps: 22 | #- download: current 23 | # artifact: '${{ parameters.artifactName }}' 24 | 25 | - task: ArchiveFiles@2 26 | displayName: 'Package PowerShell Module' 27 | inputs: 28 | rootFolderOrFile: '$(Pipeline.Workspace)/${{ parameters.pipelineId }}/${{ parameters.artifactInput }}/${{ parameters.moduleName }}' 29 | includeRootFolder: true 30 | archiveType: 'zip' 31 | archiveFile: '$(Pipeline.Workspace)/${{ parameters.artifactOutput }}/${{ parameters.moduleName }}_${{ parameters.moduleVersion }}.zip' 32 | replaceExistingArchive: true 33 | 34 | - task: PublishPipelineArtifact@1 35 | displayName: 'Publish PowerShell Module Package Artifact' 36 | inputs: 37 | targetPath: '$(Pipeline.Workspace)/${{ parameters.artifactOutput }}/${{ parameters.moduleName }}_${{ parameters.moduleVersion }}.zip' 38 | artifact: '${{ parameters.artifactOutput }}' 39 | publishLocation: 'pipeline' 40 | -------------------------------------------------------------------------------- /build/azure-pipelines/template-psmodule-publish.yml: -------------------------------------------------------------------------------- 1 | # PowerShell Module Publish Pipeline Template 2 | # https://aka.ms/yaml 3 | 4 | parameters: 5 | - name: moduleName 6 | type: string 7 | - name: pipelineId 8 | type: string 9 | default: 10 | - name: artifactInput 11 | type: string 12 | default: 'PSModuleSigned' 13 | - name: RepositorySourceLocation 14 | type: string 15 | default: 'https://www.powershellgallery.com/api/v2' 16 | - name: NuGetApiKeyAzureConnection 17 | type: string 18 | - name: NuGetApiKeyVaultName 19 | type: string 20 | - name: NuGetApiKeySecretName 21 | type: string 22 | - name: Unlist 23 | type: boolean 24 | default: false 25 | 26 | steps: 27 | - checkout: self 28 | 29 | - task: AzureKeyVault@1 30 | displayName: 'Download NuGet API Key' 31 | inputs: 32 | azureSubscription: '${{ parameters.NuGetApiKeyAzureConnection }}' 33 | KeyVaultName: '${{ parameters.NuGetApiKeyVaultName }}' 34 | SecretsFilter: '${{ parameters.NuGetApiKeySecretName }}' 35 | RunAsPreJob: false 36 | 37 | - task: PowerShell@2 38 | displayName: 'Publish PowerShell Module' 39 | inputs: 40 | filePath: '$(System.DefaultWorkingDirectory)/build/Publish-PSModule.ps1' 41 | arguments: > 42 | -ModuleManifestPath "$(Pipeline.Workspace)/${{ parameters.pipelineId }}/${{ parameters.artifactInput }}/${{ parameters.moduleName }}" 43 | -RepositorySourceLocation ${{ parameters.RepositorySourceLocation }} 44 | -NuGetApiKey (ConvertTo-SecureString "$(${{ parameters.NuGetApiKeySecretName }})" -AsPlainText) 45 | -Unlist:$${{ parameters.Unlist }} 46 | pwsh: true 47 | -------------------------------------------------------------------------------- /src/Add-MsIdServicePrincipal.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Create service principal for existing application registration 4 | 5 | .EXAMPLE 6 | PS > Add-MsIdServicePrincipal 10000000-0000-0000-0000-000000000001 7 | 8 | Create service principal for existing appId, 10000000-0000-0000-0000-000000000001. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | #> 14 | function Add-MsIdServicePrincipal { 15 | [CmdletBinding()] 16 | [OutputType([object])] 17 | param ( 18 | # AppID of Application 19 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 20 | [string[]] $AppId 21 | ) 22 | 23 | begin { 24 | ## Initialize Critical Dependencies 25 | $CriticalError = $null 26 | if (!(Test-MgCommandPrerequisites 'New-MgServicePrincipal' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } 27 | } 28 | 29 | process { 30 | if ($CriticalError) { return } 31 | 32 | foreach ($_AppId in $AppId) { 33 | ## Create Service Principal from Application Registration 34 | New-MgServicePrincipal -AppId $_AppId 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Confirm-MsIdJwtTokenSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Validate the digital signature for JSON Web Token. 4 | 5 | .EXAMPLE 6 | PS > Confirm-MsIdJwtTokenSignature $OpenIdConnectToken 7 | 8 | Validate the OpenId token was signed by token issuer based on the OIDC Provider Configuration for token issuer. 9 | 10 | .EXAMPLE 11 | PS > Confirm-MsIdJwtTokenSignature $AccessToken 12 | 13 | Validate the access token was signed by token issuer based on the OIDC Provider Configuration for token issuer. 14 | 15 | .INPUTS 16 | System.String 17 | 18 | #> 19 | function Confirm-MsIdJwtTokenSignature { 20 | [CmdletBinding()] 21 | [Alias('Confirm-JwtSignature')] 22 | [OutputType([bool])] 23 | param ( 24 | # JSON Web Token (JWT) 25 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 26 | [string[]] $Tokens 27 | ) 28 | 29 | process { 30 | foreach ($Token in $Tokens) { 31 | $Jws = ConvertFrom-JsonWebSignature $Token 32 | $SigningKeys = $Jws.Payload.iss | Get-OpenIdProviderConfiguration -Keys | Where-Object use -EQ 'sig' 33 | $SigningKey = $SigningKeys | Where-Object kid -EQ $Jws.Header.kid 34 | $SigningCertificate = Get-X509Certificate $SigningKey.x5c 35 | 36 | Confirm-JsonWebSignature $Token -SigningCertificate $SigningCertificate 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ConvertFrom-MsIdAadcAadConnectorSpaceDn.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Azure AD connector space object Distinguished Name (DN) in AAD Connect 4 | 5 | .EXAMPLE 6 | PS > ConvertFrom-MsIdAadcAadConnectorSpaceDn 'CN={414141414141414141414141414141414141414141413D3D}' 7 | 8 | Convert Azure AD connector space object DN in AAD Connect to sourceAnchor and sourceGuid. 9 | 10 | .EXAMPLE 11 | PS > 'CN={4F626A656374547970655F30303030303030302D303030302D303030302D303030302D303030303030303030303030}' | ConvertFrom-MsIdAadcAadConnectorSpaceDn 12 | 13 | Convert Azure AD connector space object DN in AAD Connect to cloudAnchor and cloudGuid. 14 | 15 | .INPUTS 16 | System.String 17 | 18 | #> 19 | function ConvertFrom-MsIdAadcAadConnectorSpaceDn { 20 | [CmdletBinding()] 21 | [OutputType([PSCustomObject])] 22 | param ( 23 | # Azure AD Connector Space DN from AAD Connect 24 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] 25 | [string] $InputObject 26 | ) 27 | 28 | process { 29 | ## Extract Hex String 30 | if ($InputObject -imatch '(?:CN=)?\{?([0-9a-f]+)\}?') { 31 | [string] $HexString = $Matches[1] 32 | } 33 | else { 34 | [string] $HexString = $InputObject 35 | } 36 | 37 | ## Decode Hex String 38 | [string] $DecodedString = ConvertFrom-HexString $HexString 39 | if ($DecodedString -imatch '([a-z]+)_([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})') { 40 | [guid] $CloudGuid = $Matches[2] 41 | $Result = [PSCustomObject]@{ 42 | cloudAnchor = $DecodedString 43 | cloudGuid = $CloudGuid 44 | } 45 | } 46 | else { 47 | [guid] $SourceGuid = ConvertFrom-Base64String $DecodedString -RawBytes 48 | $Result = [PSCustomObject]@{ 49 | sourceAnchor = $DecodedString 50 | sourceGuid = $SourceGuid 51 | } 52 | } 53 | 54 | Write-Output $Result 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ConvertFrom-MsIdAadcSourceAnchor.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Azure AD Connect metaverse object sourceAnchor or Azure AD ImmutableId to sourceGuid. 4 | 5 | .EXAMPLE 6 | PS > ConvertFrom-MsIdAadcSourceAnchor 'AAAAAAAAAAAAAAAAAAAAAA==' 7 | 8 | Convert Azure AD Connect metaverse object sourceAnchor base64 format to sourceGuid. 9 | 10 | .EXAMPLE 11 | PS > ConvertFrom-MsIdAadcSourceAnchor '00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' 12 | 13 | Convert Azure AD Connect metaverse object sourceAnchor hex format to sourceGuid. 14 | 15 | .INPUTS 16 | System.String 17 | 18 | #> 19 | function ConvertFrom-MsIdAadcSourceAnchor { 20 | [CmdletBinding()] 21 | [Alias('ConvertFrom-MsIdAzureAdImmutableId')] 22 | [OutputType([guid], [string])] 23 | param ( 24 | # Azure AD Connect metaverse object sourceAnchor. 25 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)] 26 | [string] $InputObject 27 | ) 28 | 29 | process { 30 | if ($InputObject -imatch '(?:^|,)((?:[0-9a-f]{2} ?)+)(?:$|,)') { 31 | [guid] $SourceGuid = ConvertFrom-HexString $Matches[1].Trim() -RawBytes 32 | } 33 | elseif ($InputObject -imatch '(?:^|,)([0-9a-z+/=]+=+)(?:$|,)') { 34 | [guid] $SourceGuid = ConvertFrom-Base64String $Matches[1] -RawBytes 35 | } 36 | else { 37 | [guid] $SourceGuid = ConvertFrom-Base64String $InputObject -RawBytes 38 | } 39 | 40 | Write-Output $SourceGuid 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ConvertFrom-MsIdJwtToken.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Msft Identity token structure to PowerShell object. 4 | 5 | .EXAMPLE 6 | PS > ConvertFrom-MsIdJwtToken $OpenIdConnectToken 7 | 8 | Convert OAuth Id Token JWS to PowerShell object. 9 | 10 | .EXAMPLE 11 | PS > ConvertFrom-MsIdJwtToken $AccessToken 12 | 13 | Convert OAuth Access Token JWS to PowerShell object. 14 | 15 | .INPUTS 16 | System.String 17 | 18 | #> 19 | function ConvertFrom-MsIdJwtToken { 20 | [CmdletBinding()] 21 | [OutputType([PSCustomObject])] 22 | param ( 23 | # JSON Web Token (JWT) 24 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 25 | [string[]] $Tokens 26 | ) 27 | 28 | process { 29 | foreach ($Token in $Tokens) { 30 | ConvertFrom-JsonWebSignature $Token 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ConvertFrom-MsIdSamlMessage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert SAML Message structure to PowerShell object. 4 | 5 | .EXAMPLE 6 | PS > ConvertFrom-MsIdSamlMessage 'Base64String' 7 | 8 | Convert Saml Message to XML object. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | .OUTPUTS 14 | SamlMessage : System.Xml.XmlDocument 15 | 16 | #> 17 | function ConvertFrom-MsIdSamlMessage { 18 | [CmdletBinding()] 19 | [Alias('ConvertFrom-MsIdSamlRequest')] 20 | [Alias('ConvertFrom-MsIdSamlResponse')] 21 | #[OutputType([xml])] 22 | param ( 23 | # SAML Message 24 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 25 | [string[]] $InputObject 26 | ) 27 | 28 | process { 29 | foreach ($_InputObject in $InputObject) { 30 | ConvertFrom-SamlMessage $_InputObject 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ConvertFrom-MsIdUniqueTokenIdentifier.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Azure AD Unique Token Identifier to Request Id. 4 | 5 | .EXAMPLE 6 | PS > ConvertFrom-MsIdUniqueTokenIdentifier 'AAAAAAAAAAAAAAAAAAAAAA' 7 | 8 | Convert Azure AD Unique Token Identifier to Request Id. 9 | 10 | .EXAMPLE 11 | PS > Get-MgBetaAuditLogSignIn -Top 1 | ConvertFrom-MsIdUniqueTokenIdentifier 12 | 13 | Get a Sign-in Log Entry and Convert Azure AD Unique Token Identifier to Request Id. 14 | 15 | .INPUTS 16 | System.String 17 | 18 | #> 19 | function ConvertFrom-MsIdUniqueTokenIdentifier { 20 | [CmdletBinding()] 21 | [OutputType([guid])] 22 | param ( 23 | # Azure AD Unique Token Identifier 24 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] 25 | [ValidateLength(22,22)] 26 | [Alias("UniqueTokenIdentifier")] 27 | [string] $InputObject 28 | ) 29 | 30 | process { 31 | [guid] $SourceGuid = ConvertFrom-Base64String $InputObject -Base64Url -RawBytes 32 | return $SourceGuid 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Expand-MsIdJwtTokenPayload.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Extract Json Web Token (JWT) payload from JWS structure to PowerShell object. 4 | 5 | .EXAMPLE 6 | PS > $MsalToken.IdToken | Expand-MsIdJwtTokenPayload 7 | 8 | Extract Json Web Token (JWT) payload from JWS structure to PowerShell object. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | #> 14 | function Expand-MsIdJwtTokenPayload { 15 | [CmdletBinding()] 16 | [OutputType([PSCustomObject])] 17 | param ( 18 | # JSON Web Token (JWT) 19 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 20 | [string[]] $Tokens 21 | ) 22 | 23 | process { 24 | foreach ($Token in $Tokens) { 25 | $Jwt = ConvertFrom-JsonWebSignature $Token 26 | Write-Output $Jwt.Payload 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Get-MsIdAdfsSampleApp.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the list of availabe sample AD FS relyng party trust applications available in this module. These applications do NOT use real endpoints and are meant to be used as test applications. 4 | .EXAMPLE 5 | PS > Get-MsIdAdfsSampleApps 6 | 7 | Get the full list of sample AD FS apps. 8 | 9 | .EXAMPLE 10 | PS > Get-MsIdAdfsSampleApps SampleAppName 11 | 12 | Get only SampleAppName sample AD FS app (replace SampleAppName by one of the available apps). 13 | 14 | #> 15 | function Get-MsIdAdfsSampleApp { 16 | [CmdletBinding()] 17 | [OutputType([object[]])] 18 | param ( 19 | # Sample applications name 20 | [Parameter(Mandatory = $false)] 21 | [string] $Name 22 | ) 23 | 24 | $result = [System.Collections.ArrayList]@() 25 | 26 | if (Import-AdfsModule) { 27 | $apps = Get-ChildItem -Path "$($PSScriptRoot)\internal\AdfsSamples\" 28 | 29 | if ($Name -ne '') { 30 | $apps = $apps | Where-Object { $_.Name -eq $Name + '.json' } 31 | } 32 | 33 | ForEach ($app in $apps) { 34 | Try { 35 | Write-Verbose "Loading app: $($app.Name)" 36 | if ($app.Name -notlike '*.xml') { 37 | $rp = Get-Content $app.FullName | ConvertFrom-json 38 | $null = $result.Add($rp) 39 | } 40 | } 41 | catch { 42 | Write-Warning "Error while loading app '$($app.Name)': ($_)" 43 | } 44 | } 45 | 46 | return ,$result 47 | } 48 | } -------------------------------------------------------------------------------- /src/Get-MsIdApplicationIdByAppId.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Lookup Application Registration by AppId 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdApplicationIdByAppId 10000000-0000-0000-0000-000000000001 7 | 8 | Return the application registration id matching appId, 10000000-0000-0000-0000-000000000001. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | #> 14 | function Get-MsIdApplicationIdByAppId { 15 | [CmdletBinding()] 16 | [OutputType([string])] 17 | param ( 18 | # AppID of the Application Registration 19 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 20 | [string[]] $AppId 21 | ) 22 | 23 | begin { 24 | ## Initialize Critical Dependencies 25 | $CriticalError = $null 26 | if (!(Test-MgCommandPrerequisites 'Get-MgApplication' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } 27 | } 28 | 29 | process { 30 | if ($CriticalError) { return } 31 | 32 | foreach ($_AppId in $AppId) { 33 | ## Filter application registration by appId and return id 34 | Get-MgApplication -Filter "appId eq '$_AppId'" -Select id | Select-Object -ExpandProperty id 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Get-MsIdGroupWithExpiration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Return groups with an expiration date via lifecycle policy. 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdGroupWithExpiration | ft Id,DisplayName,ExpirationDateTime,RenewedDateTime 7 | 8 | Return all groups with an expiration date. 9 | 10 | .EXAMPLE 11 | PS > Get-MsIdGroupWithExpiration -After (Get-Date).AddDays(-30) -Before (Get-Date).AddDays(30) | ft Id,DisplayName,ExpirationDateTime,RenewedDateTime 12 | 13 | Return all groups with an expiration date between 30 days before today and 30 days after today. 14 | 15 | .EXAMPLE 16 | PS > Get-MsIdGroupWithExpiration -Days 30 | ft Id,DisplayName,ExpirationDateTime,RenewedDateTime 17 | 18 | Return all groups with an expiration date between now and 30 days from now. 19 | 20 | .INPUTS 21 | None 22 | 23 | #> 24 | function Get-MsIdGroupWithExpiration { 25 | [CmdletBinding(DefaultParameterSetName = 'DateTimeSpan')] 26 | param ( 27 | # Numbers of days 28 | [Parameter(Mandatory = $false, Position = 0, ParameterSetName = 'Days')] 29 | [int] $Days, 30 | # Start of DateTime range 31 | [Parameter(Mandatory = $false, Position = 0, ParameterSetName = 'DateTimeSpan')] 32 | [datetime] $After = [datetime]::MinValue, 33 | # End of DateTime range 34 | [Parameter(Mandatory = $false, Position = 1, ParameterSetName = 'DateTimeSpan')] 35 | [datetime] $Before = [datetime]::MaxValue 36 | ) 37 | 38 | ## Initialize Critical Dependencies 39 | if (!(Test-MgCommandPrerequisites 'Get-MgGroup' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } 40 | 41 | if ($PSCmdlet.ParameterSetName -eq 'Days') { 42 | [datetime] $After = Get-Date 43 | [datetime] $Before = $After.AddDays($Days) 44 | } 45 | 46 | ## Filter for groups with an expiration date 47 | Get-MgGroup -Filter "expirationDateTime ge $($After.ToUniversalTime().ToString('o')) and expirationDateTime le $($Before.ToUniversalTime().ToString('o'))" -All -CountVariable MgCount -ConsistencyLevel eventual | Sort-Object expirationDateTime 48 | } 49 | -------------------------------------------------------------------------------- /src/Get-MsIdHasMicrosoftAccount.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns true if the user's mail is a Microsoft Account 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdHasMicrosoftAccount -Mail john@yopmail.net 7 | 8 | Check if the mail address has a Microsoft account 9 | 10 | #> 11 | function Get-MsIdHasMicrosoftAccount { 12 | [CmdletBinding()] 13 | 14 | param ( 15 | # The email address of the external user. 16 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] 17 | [string] $Mail 18 | ) 19 | 20 | $userRealm = Get-MsftUserRealm $Mail -CheckForMicrosoftAccount 21 | 22 | $isMSA = (Get-ObjectPropertyValue $userRealm 'MicrosoftAccount') -eq "0" 23 | 24 | return $isMSA 25 | } 26 | -------------------------------------------------------------------------------- /src/Get-MsIdIsViralUser.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns true if the user's mail domain is a viral (unmanaged) Azure AD tenant. 4 | 5 | .DESCRIPTION 6 | To learn more about viral tenants see [Take over an unmanaged directory as administrator in Azure Active Directory](https://docs.microsoft.com/azure/active-directory/enterprise-users/domains-admin-takeover) 7 | 8 | .EXAMPLE 9 | PS > Get-MsIdIsViralUser -Mail john@yopmail.net 10 | 11 | Check if the mail address is from a viral tenant. 12 | 13 | #> 14 | function Get-MsIdIsViralUser { 15 | [CmdletBinding()] 16 | 17 | param ( 18 | # The email address of the external user. 19 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, ValueFromRemainingArguments = $false)] 20 | [string] $Mail 21 | ) 22 | 23 | $userRealm = Get-MsftUserRealm $Mail 24 | 25 | $isExternalAzureADViral = (Get-ObjectPropertyValue $userRealm 'IsViral') -eq "True" 26 | 27 | return $isExternalAzureADViral 28 | } 29 | -------------------------------------------------------------------------------- /src/Get-MsIdMsftIdentityAssociation.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Parse Microsoft Identity Association Configuration for a Public Domain (such as published apps) 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdMsftIdentityAssociation https://contoso.com/ 7 | 8 | Get Microsoft Identity Association Configuration for contoso domain. 9 | 10 | .INPUTS 11 | System.Uri 12 | 13 | #> 14 | function Get-MsIdMsftIdentityAssociation { 15 | [CmdletBinding()] 16 | [OutputType([PsCustomObject[]])] 17 | param ( 18 | # Publisher Domain. For example: https://contoso.com/ 19 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 20 | [uri] $Publisher 21 | ) 22 | ## Build common OpenId provider configuration URI 23 | $uriMsftIdentityAssociation = New-Object System.UriBuilder $Publisher.AbsoluteUri 24 | if (!$uriMsftIdentityAssociation.Path.EndsWith('/.well-known/microsoft-identity-association.json')) { $uriMsftIdentityAssociation.Path += '/.well-known/microsoft-identity-association.json' } 25 | 26 | ## Download and parse configuration 27 | $MsftIdentityAssociation = Invoke-RestMethod -UseBasicParsing -Uri $uriMsftIdentityAssociation.Uri.AbsoluteUri # Should return ContentType 'application/json' 28 | return $MsftIdentityAssociation 29 | } 30 | -------------------------------------------------------------------------------- /src/Get-MsIdO365Endpoints.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get list of URLs and IP ranges for O365 4 | 5 | .DESCRIPTION 6 | http://aka.ms/ipurlws 7 | 8 | .EXAMPLE 9 | PS > Get-MsIdO365Endpoints 10 | 11 | Get list of URLs and IP ranges for O365 Worldwide cloud. 12 | 13 | .EXAMPLE 14 | PS > Get-MsIdO365Endpoints -Cloud China -ServiceAreas Exchange,SharePoint 15 | 16 | Get list of URLs and IP ranges for Exchange and SharePoint in O365 China Cloud. 17 | 18 | .EXAMPLE 19 | PS > Get-MsIdO365Endpoints -Cloud Worldwide -ServiceAreas Common | Where-Object id -In 54,56,59,96 20 | 21 | Get list of URLs and IP ranges related to Azure Active Directory. 22 | 23 | .INPUTS 24 | System.String 25 | 26 | #> 27 | function Get-MsIdO365Endpoints { 28 | [CmdletBinding()] 29 | [OutputType([PSCustomObject])] 30 | param( 31 | # Name of O365 Cloud. Valid values are: 'Worldwide','USGovGCCHigh','USGovDoD','Germany','China' 32 | [Parameter(Mandatory = $false, Position = 1)] 33 | [ValidateSet('Worldwide', 'USGovGCCHigh', 'USGovDoD', 'Germany', 'China')] 34 | [string] $Cloud = 'Worldwide', 35 | # Office 365 tenant name. 36 | [Parameter(Mandatory = $false)] 37 | [string] $TenantName, 38 | # Exclude IPv6 addresses from the output 39 | [Parameter(Mandatory = $false)] 40 | [switch] $NoIPv6, 41 | # Name of Service Area. 42 | [Parameter(Mandatory = $false)] 43 | [ValidateSet('Common', 'Exchange', 'SharePoint', 'Skype')] 44 | [string[]] $ServiceAreas, 45 | # Client Request Id. 46 | [Parameter(Mandatory = $false)] 47 | [guid] $ClientRequestId = (New-Guid) 48 | ) 49 | 50 | [hashtable] $EndpointsParameters = @{ 51 | clientrequestid = $ClientRequestId 52 | } 53 | if ($TenantName) { $EndpointsParameters.Add('TenantName', $TenantName) } 54 | if ($NoIPv6) { $EndpointsParameters.Add('NoIPv6', $NoIPv6) } 55 | if ($ServiceAreas) { $EndpointsParameters.Add('ServiceAreas', ($ServiceAreas -join ',')) } 56 | 57 | [System.UriBuilder] $O365EndpointsUri = 'https://endpoints.office.com/endpoints/{0}' -f $Cloud 58 | $O365EndpointsUri.Query = ConvertTo-QueryString $EndpointsParameters 59 | 60 | $O365Endpoints = Invoke-RestMethod -UseBasicParsing -Uri $O365EndpointsUri.Uri -ErrorAction Stop 61 | return $O365Endpoints 62 | } 63 | -------------------------------------------------------------------------------- /src/Get-MsIdOpenIdProviderConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Parse OpenId Provider Configuration and Keys 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com | Get-MsIdOpenIdProviderConfiguration 7 | 8 | Get OpenId Provider Configuration for a specific Microsoft organizational tenant (Azure AD). 9 | 10 | .EXAMPLE 11 | PS > Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com | Get-MsIdOpenIdProviderConfiguration -Keys 12 | 13 | Get public keys for OpenId Provider for a specific Microsoft organizational tenant (Azure AD). 14 | 15 | .EXAMPLE 16 | PS > Get-MsIdAuthorityUri -Msa | Get-MsIdOpenIdProviderConfiguration 17 | 18 | Get OpenId Provider Configuration for Microsoft consumer accounts (MSA). 19 | 20 | .INPUTS 21 | System.Uri 22 | 23 | #> 24 | function Get-MsIdOpenIdProviderConfiguration { 25 | [CmdletBinding()] 26 | [OutputType([PsCustomObject[]])] 27 | param ( 28 | # Identity Provider Authority URI 29 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 30 | [uri] $Issuer, 31 | # Return configuration keys 32 | [Parameter(Mandatory = $false)] 33 | [switch] $Keys 34 | ) 35 | 36 | process { 37 | Get-OpenIdProviderConfiguration @PSBoundParameters 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Get-MsIdSamlFederationMetadata.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Parse Federation Metadata 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com -AppType 'Saml' | Get-MsIdSamlFederationMetadata 7 | 8 | Get SAML or WS-Fed Federation Metadata for a specific Microsoft tenant. 9 | 10 | .EXAMPLE 11 | PS > Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com -AppType 'Saml' | Get-MsIdSamlFederationMetadata -AppId 00000000-0000-0000-0000-000000000000 12 | 13 | Get SAML or WS-Fed Federation Metadata for a specific application within a specific Microsoft tenant. 14 | 15 | .EXAMPLE 16 | PS > Get-MsIdSamlFederationMetadata 'https://adfs.contoso.com' 17 | 18 | Get SAML or WS-Fed Federation Metadata for an ADFS farm. 19 | 20 | .INPUTS 21 | System.Uri 22 | 23 | #> 24 | function Get-MsIdSamlFederationMetadata { 25 | [CmdletBinding()] 26 | [Alias('Get-MsIdWsFedFederationMetadata')] 27 | [OutputType([xml], [System.Xml.XmlElement[]])] 28 | param ( 29 | # Identity Provider Authority URI 30 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 31 | [uri] $Issuer, 32 | # Azure AD Application Id 33 | [Parameter(Mandatory = $false, Position = 2)] 34 | [guid] $AppId 35 | ) 36 | 37 | process { 38 | Get-SamlFederationMetadata @PSBoundParameters 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Get-MsIdServicePrincipalIdByAppId.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Lookup Service Principal by AppId 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdServicePrincipalIdByAppId 10000000-0000-0000-0000-000000000001 7 | 8 | Return the service principal id matching appId, 10000000-0000-0000-0000-000000000001. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | #> 14 | function Get-MsIdServicePrincipalIdByAppId { 15 | [CmdletBinding()] 16 | [OutputType([string])] 17 | param ( 18 | # AppID of the Service Principal 19 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 20 | [string[]] $AppId 21 | ) 22 | 23 | begin { 24 | ## Initialize Critical Dependencies 25 | $CriticalError = $null 26 | if (!(Test-MgCommandPrerequisites 'Get-MgServicePrincipal' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } 27 | } 28 | 29 | process { 30 | if ($CriticalError) { return } 31 | 32 | foreach ($_AppId in $AppId) { 33 | ## Filter service principals by appId and return id 34 | Get-MgServicePrincipal -Filter "appId eq '$_AppId'" -Select id | Select-Object -ExpandProperty id 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Get-MsIdSigningKeyThumbprint.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get signing keys used by Azure AD. 4 | 5 | .EXAMPLE 6 | PS > Get-MsIdSigningKeyThumbprint 7 | 8 | Get common Azure AD signing key thumbprints. 9 | 10 | .EXAMPLE 11 | PS > Get-MsIdSigningKeyThumbprint -Tenant 12 | 13 | Get Azure AD signing key thumbprints for the given tenant. 14 | 15 | .EXAMPLE 16 | PS > Get-MsIdSigningKeyThumbprint -Tenant -Latest 17 | 18 | Get the latest Azure AD signing key thumbprint for the given tenant. 19 | 20 | .EXAMPLE 21 | PS > Get-MsIdSigningKeyThumbprint -DownloadPath C:\temp 22 | 23 | Export the certificates to a folder destination. 24 | 25 | #> 26 | 27 | function Get-MsIdSigningKeyThumbprint{ 28 | Param( 29 | # Tenant ID 30 | $Tenant = "common", 31 | 32 | # Cloud environment 33 | $Environment="prod", 34 | 35 | # Return the latest certificate 36 | [switch]$Latest, 37 | 38 | # Location to save certificate 39 | [string]$DownloadPath 40 | ) 41 | 42 | process { 43 | 44 | $authority = "https://login.microsoftonline.com/" 45 | if($Environment.ToLower() -eq "china"){ $authority = "https://login.chinacloudapi.cn/" } 46 | 47 | $keysUrl = "$authority$Tenant/discovery/keys"; 48 | $keysJson = ConvertFrom-Json (Invoke-WebRequest $keysUrl).Content 49 | 50 | $certs = @() 51 | foreach ($key in $keysJson.keys) { 52 | $bytes = [System.Text.Encoding]::UTF8.GetBytes($key.x5c) 53 | $cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @(,$bytes) 54 | 55 | $certs += new-object PSObject -Property @{ 'Kid'=$key.kid; 'Thumbprint'=$cert.Thumbprint; 'NotAfter'=$cert.NotAfter; 'NotBefore'=$cert.NotBefore; 'Cert'=$cert } 56 | } 57 | 58 | if ($Latest) { 59 | $certs = $certs | sort -Descending {$_.NotBefore} | Select -First 1 60 | } 61 | 62 | if ($DownloadPath) { 63 | foreach ($cert in $certs) { 64 | $path = Join-Path $DownloadPath ($cert.Thumbprint.ToLower() + ".cer") 65 | [System.IO.File]::WriteAllBytes($path, $cert.Cert.Export("Cert")); 66 | Write-Host "Certificate successfully exported to $path" 67 | } 68 | }else{ 69 | Write-Output $certs.Thumbprint 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /src/Import-MsIdAdfsSamplePolicy.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Imports the 'MsId Block Off Corp and VPN' sample AD FS access control policy. This policy is meant to be used as test policy. 4 | .DESCRIPTION 5 | Imports the 'MsId Block Off Corp and VPN' sample AD FS access control policy. Pass locations in the format of range (205.143.204.1-205.143.205.250) or CIDR (12.159.168.1/24). 6 | 7 | This policy is meant to be used as test policy! 8 | .EXAMPLE 9 | PS >Import-MsIdAdfsSamplePolicy -Locations 205.143.204.1-205.143.205.250,12.159.168.1/24,12.35.175.1/26 10 | 11 | Create the policy to the local AD FS server. 12 | 13 | .EXAMPLE 14 | PS >Import-MsIdAdfsSamplePolicy -Locations 205.143.204.1-205.143.205.250 -ApplyTo App1,App2 15 | 16 | Create the policy to the local AD FS server and apply it to to the list of applications. 17 | 18 | #> 19 | function Import-MsIdAdfsSamplePolicy { 20 | [CmdletBinding()] 21 | param( 22 | # Network locations 23 | [Parameter(Mandatory=$true)] 24 | [string[]]$Locations, 25 | # Relying party names to apply the policy 26 | [Parameter(Mandatory=$false)] 27 | [string[]]$ApplyTo 28 | ) 29 | 30 | $name = "MsId Block Off Corp and VPN" 31 | 32 | if (Import-AdfsModule) { 33 | Try { 34 | 35 | # build for each location 36 | $values = "" 37 | foreach ($location in $Locations) { 38 | $values += "$($location)" 39 | } 40 | 41 | # load and update metadata file 42 | $metadataBase = Get-Content "$($PSScriptRoot)\internal\AdfsSamples\AdfsAccessControlPolicy.xml" -Raw 43 | $metadataStr = $metadataBase -replace '.*',"$values" 44 | $metadata = New-Object -TypeName Microsoft.IdentityServer.PolicyModel.Configuration.PolicyTemplate.PolicyMetadata -ArgumentList $metadataStr 45 | 46 | $policy = Get-AdfsAccessControlPolicy -Name $name 47 | if ($null -eq $policy) { 48 | Write-Verbose "Creating Access Control Policy $($name)" 49 | $null = New-AdfsAccessControlPolicy -Name $name -Identifier "DenyNonCorporateandNonVPN" -PolicyMetadata $metadata 50 | } 51 | else { 52 | throw "The policy '" + $name + "' already exists." 53 | } 54 | 55 | if ($null -ne $ApplyTo) { 56 | foreach ($app in $ApplyTo) { 57 | Set-AdfsRelyingPartyTrust -TargetName $app -AccessControlPolicyName $name 58 | } 59 | } 60 | } 61 | Catch { 62 | Write-Error $_ 63 | } 64 | } 65 | else { 66 | Write-Error "The Import-MsIdAdfsSampleApps cmdlet requires the ADFS module installed to work." 67 | } 68 | } -------------------------------------------------------------------------------- /src/Invoke-MsIdAzureAdSamlRequest.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Invoke Saml Request on Azure AD. 4 | 5 | .EXAMPLE 6 | PS > $samlRequest = New-MsIdSamlRequest -Issuer 'urn:microsoft:adfs:claimsxray' 7 | PS > Invoke-MsIdAzureAdSamlRequest $samlRequest.OuterXml 8 | 9 | Create new Saml Request for Claims X-Ray and Invoke on Azure AD. 10 | 11 | .INPUTS 12 | System.String 13 | 14 | #> 15 | function Invoke-MsIdAzureAdSamlRequest { 16 | [CmdletBinding()] 17 | [OutputType()] 18 | param ( 19 | # SAML Request 20 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 21 | [object[]] $SamlRequest, 22 | # Azure AD Tenant Id 23 | [Parameter(Mandatory = $false)] 24 | [string] $TenantId = 'common' 25 | ) 26 | 27 | process { 28 | foreach ($_SamlRequest in $SamlRequest) { 29 | if ($_SamlRequest -is [string]) { 30 | $xmlSamlRequest = ConvertFrom-SamlMessage $_SamlRequest 31 | } 32 | else { 33 | $xmlSamlRequest = $_SamlRequest 34 | } 35 | $EncodedSamlRequest = $xmlSamlRequest.OuterXml | Compress-Data | ConvertTo-Base64String 36 | 37 | [System.UriBuilder] $uriAzureAD = 'https://login.microsoftonline.com/{0}/saml2' -f $TenantId 38 | $uriAzureAD.Query = ConvertTo-QueryString @{ 39 | SAMLRequest = $EncodedSamlRequest 40 | } 41 | 42 | Write-Verbose ('Invoking Azure AD SAML2 Endpoint [{0}]' -f $uriAzureAD.Uri.AbsoluteUri) 43 | Start-Process $uriAzureAD.Uri.AbsoluteUri 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/MSIdentityTools.psm1: -------------------------------------------------------------------------------- 1 | ## Set Strict Mode for Module. https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-strictmode 2 | Set-StrictMode -Version 3.0 3 | 4 | ## Display Warning on old PowerShell versions. https://docs.microsoft.com/en-us/powershell/scripting/install/PowerShell-Support-Lifecycle#powershell-end-of-support-dates 5 | if ($PSVersionTable.PSVersion -lt [version]'7.0') { 6 | Write-Warning 'It is recommended to use this module with the latest version of PowerShell which can be downloaded here: https://aka.ms/install-powershell' 7 | } 8 | 9 | #Write-Warning 'It is recommended to update Microsoft Graph PowerShell SDK modules frequently because many commands in this module depend on them.' 10 | 11 | class SamlMessage : xml {} 12 | -------------------------------------------------------------------------------- /src/New-MsIdClientSecret.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Generate Random Client Secret for application registration or service principal in Azure AD. 4 | 5 | .EXAMPLE 6 | PS > New-MsIdClientSecret 7 | 8 | Generates a new client secret 32 characters long. 9 | 10 | .EXAMPLE 11 | PS > New-MsIdClientSecret -Length 64 -Base64Encode 12 | 13 | Generates a new client secret 64 bytes long and then base64 encodes it. 14 | 15 | #> 16 | function New-MsIdClientSecret { 17 | [CmdletBinding()] 18 | [OutputType([securestring])] 19 | param ( 20 | # Specifies the number of random characters or bytes to generate. 21 | [Parameter(Mandatory = $false)] 22 | [int] $Length = 32, 23 | # Generate a binary key and encode it to base64. 24 | [Parameter(Mandatory = $false)] 25 | [switch] $Base64Encode 26 | ) 27 | 28 | if ($Base64Encode) { 29 | [securestring] $Secret = ConvertTo-SecureString (ConvertTo-Base64String ([byte[]](Get-Random -InputObject ((([byte]::MinValue)..([byte]::MaxValue)) * $Length) -Count $Length))) -AsPlainText -Force 30 | } 31 | else { 32 | [char[]] $Numbers = (48..57) 33 | [char[]] $UpperCaseLetters = (65..90) 34 | [char[]] $LowerCaseLetters = (97..122) 35 | [char[]] $Symbols = '*+-./:=?@[]_' 36 | [securestring] $Secret = ConvertTo-SecureString ((Get-Random -InputObject (($UpperCaseLetters + $LowerCaseLetters + $Numbers + $Symbols) * $Length) -Count $Length) -join '') -AsPlainText -Force 37 | } 38 | return $Secret 39 | } 40 | -------------------------------------------------------------------------------- /src/New-MsIdTemporaryUserPassword.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Generate Random password for user in Azure AD. 4 | 5 | .EXAMPLE 6 | PS > New-MsIdTemporaryUserPassword 7 | 8 | Generates a new password. 9 | 10 | #> 11 | function New-MsIdTemporaryUserPassword { 12 | [CmdletBinding()] 13 | [OutputType([securestring])] 14 | param () 15 | 16 | [char[]] $Consonants = 'bcdfghjklmnpqrstvwxyz' 17 | [char[]] $Vowels = 'aou' 18 | [securestring] $Password = ConvertTo-SecureString ('{0}{1}{2}{3}{4}' -f (Get-Random -InputObject $Consonants).ToString().ToUpper(), (Get-Random -InputObject $Vowels), (Get-Random -InputObject $Consonants), (Get-Random -InputObject $Vowels), (Get-Random -Minimum 1000 -Maximum 9999)) -AsPlainText -Force 19 | return $Password 20 | } 21 | -------------------------------------------------------------------------------- /src/Show-MsIdJwtToken.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Show Json Web Token (JWT) decoded in Web Browser using diagnostic web app. 4 | 5 | .EXAMPLE 6 | PS > $MsalToken.IdToken | Show-MsIdJwtToken 7 | 8 | Show OAuth IdToken JWT decoded in Web Browser. 9 | 10 | .INPUTS 11 | System.String 12 | 13 | #> 14 | function Show-MsIdJwtToken { 15 | [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] 16 | [Alias('Show-Jwt')] 17 | param ( 18 | # JSON Web Token (JWT) 19 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 20 | [string[]] $Tokens, 21 | # OAuth2 Redirect Uri of test application to send Json Web Token 22 | [Parameter(Mandatory = $false)] 23 | [uri] $OAuth2RedirectUri = 'https://jwt.ms/', 24 | # Suppress Prompts 25 | [Parameter(Mandatory = $false)] 26 | [switch] $Force 27 | ) 28 | 29 | begin { 30 | if ($Force -and -not (Get-Variable Confirm -ValueOnly -ErrorAction Ignore)) { $ConfirmPreference = 'None' } 31 | } 32 | 33 | process { 34 | foreach ($Token in $Tokens) { 35 | 36 | if ($OAuth2RedirectUri.AbsoluteUri -ne 'https://jwt.ms/') { 37 | Write-Warning ('The token is being sent to the following web service [{0}]. This command is intended for troubleshooting and should only be used if you trust the service endpoint receiving the token.' -f $OAuth2RedirectUri.AbsoluteUri) 38 | if (!$PSCmdlet.ShouldProcess($OAuth2RedirectUri.AbsoluteUri, "Send token")) { continue } 39 | } 40 | 41 | $OAuth2RedirectUriWithToken = New-Object System.UriBuilder $OAuth2RedirectUri -Property @{ Fragment = "id_token=$Token" } 42 | Start-Process $OAuth2RedirectUriWithToken.Uri.AbsoluteUri 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Split-MsIdEntitlementManagementConnectedOrganization.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .Synopsis 3 | Split elements of a connectedOrganization 4 | .Description 5 | Split elements of one or more Azure AD entitlement management connected organizations, returned by Get-MgEntitlementManagementConnectedOrganization, to simplify reporting. 6 | .Inputs 7 | Microsoft.Graph.PowerShell.Models.MicrosoftGraphConnectedOrganization 8 | 9 | .EXAMPLE 10 | PS > Get-MgEntitlementManagementConnectedOrganization -All | Split-MsIdEntitlementManagementConnectedOrganization -ByIdentitySource | ft ConnectedOrganizationId,tenantId,domainName 11 | 12 | Display one row for each identity source in all the connected organizations with the tenant id or domain name of the identity source. 13 | 14 | #> 15 | function Split-MsIdEntitlementManagementConnectedOrganization { 16 | [CmdletBinding(DefaultParameterSetName = 'SplitByIdentitySource', PositionalBinding = $false, ConfirmImpact = 'Medium')] 17 | param( 18 | 19 | [Parameter(ValueFromPipeline = $true, ParameterSetName = 'SplitByIdentitySource')] 20 | [object[]] 21 | # The connected organization. 22 | ${ConnectedOrganization}, 23 | # Flag to indicate that the output should be split by identity source. 24 | [Parameter(Mandatory = $true, ParameterSetName = 'SplitByIdentitySource')] 25 | [switch] 26 | ${ByIdentitySource} 27 | 28 | ) 29 | 30 | begin { 31 | 32 | } 33 | 34 | process { 35 | if ($ByIdentitySource) { 36 | 37 | if ($null -ne $ConnectedOrganization.IdentitySources) { 38 | foreach ($is in $ConnectedOrganization.IdentitySources) { 39 | # identity sources, as an abstract class, does not have any properties 40 | 41 | $aObj = [pscustomobject]@{ 42 | ConnectedOrganizationId = $ConnectedOrganization.Id 43 | } 44 | 45 | $addl = $is.AdditionalProperties 46 | foreach ($k in $addl.Keys) { 47 | $isk = $k 48 | $aObj | Add-Member -MemberType NoteProperty -Name $isk -Value $addl[$k] -Force 49 | } 50 | 51 | Write-Output $aObj 52 | } 53 | } 54 | } 55 | } 56 | 57 | end { 58 | 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Test-MsIdAzureAdDeviceRegConnectivity.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Test connectivity on Windows OS for Azure AD Device Registration 4 | 5 | .EXAMPLE 6 | PS > Test-MsIdAzureAdDeviceRegConnectivity 7 | 8 | Test required hostnames 9 | 10 | .EXAMPLE 11 | PS > Test-MsIdAzureAdDeviceRegConnectivity -AdfsHostname 'adfs.contoso.com' 12 | 13 | Test required hostnames and ADFS server 14 | 15 | .INPUTS 16 | System.String 17 | 18 | .LINK 19 | https://docs.microsoft.com/en-us/samples/azure-samples/testdeviceregconnectivity/testdeviceregconnectivity/ 20 | 21 | #> 22 | function Test-MsIdAzureAdDeviceRegConnectivity { 23 | [CmdletBinding()] 24 | param ( 25 | # ADFS Server 26 | [Parameter(Mandatory = $false)] 27 | [string] $AdfsHostname 28 | ) 29 | 30 | begin { 31 | ## Initialize Critical Dependencies 32 | $CriticalError = $null 33 | if ($PSEdition -ne 'Desktop' -or !(Test-PsElevation)) { 34 | Write-Error 'This command uses a Scheduled Job to run under the system context of a Windows OS which requires Windows PowerShell 5.1 and an elevated session using Run as Administrator.' -ErrorVariable CriticalError 35 | return 36 | } 37 | } 38 | 39 | process { 40 | ## Return Immediately On Critical Error 41 | if ($CriticalError) { return } 42 | 43 | Invoke-CommandAsSystem { 44 | param ([string]$AdfsHostname) 45 | [System.Security.Principal.WindowsIdentity]::GetCurrent().Name 46 | 47 | [System.Collections.Generic.List[string]] $listHostname = @( 48 | 'login.microsoftonline.com' 49 | 'device.login.microsoftonline.com' 50 | 'enterpriseregistration.windows.net' 51 | 'autologon.microsoftazuread-sso.com' 52 | ) 53 | if ($AdfsHostname) { $listHostname.Add($AdfsHostname) } 54 | 55 | $listHostname | Test-NetConnection -Port 443 | Format-Table ComputerName, RemotePort, RemoteAddress, TcpTestSucceeded 56 | } -ArgumentList $AdfsHostname -ErrorAction Stop 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Update-MsIdApplicationSigningKeyThumbprint.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Update a Service Princpal's preferredTokenSigningKeyThumbprint to the specified certificate thumbprint 4 | 5 | .DESCRIPTION 6 | Update a Service Princpal's preferredTokenSigningKeyThumbprint to the specified certificate thumbprint 7 | For more information on Microsoft Identity platorm signing key rollover see https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-signing-key-rollover 8 | 9 | .EXAMPLE 10 | PS > Update-MsIdApplicationSigningKeyThumbprint -ApplicationId -KeyThumbprint 11 | 12 | Update Application's preferred signing key to the specified thumbprint 13 | 14 | .EXAMPLE 15 | PS > Update-MsIdApplicationSigningKeyThumbprint -ApplicationId -Default 16 | 17 | Update Application's preferred signing key to default value null 18 | 19 | .EXAMPLE 20 | PS > Get-MsIdSigningKeyThumbprint -Latest | Update-MsIdApplicationSigningKeyThumbprint -ApplicationId 21 | 22 | Get the latest signing key thumbprint and set it as the perferred signing key on the application 23 | 24 | #> 25 | function Update-MsIdApplicationSigningKeyThumbprint { 26 | [CmdletBinding()] 27 | param ( 28 | # Tenant ID 29 | $Tenant = "common", 30 | 31 | # Application ID 32 | [parameter(mandatory = $true)] 33 | [string]$ApplicationId, 34 | 35 | # Thumbprint of certificate 36 | [parameter(ValueFromPipeline = $true)] 37 | [string]$KeyThumbprint, 38 | 39 | # Return preferredTokenSigningKeyThumbprint to default value 40 | [parameter(parametersetname = "Default")] 41 | [switch]$Default 42 | ) 43 | 44 | begin { 45 | ## Initialize Critical Dependencies 46 | $CriticalError = $null 47 | if (!(Test-MgCommandPrerequisites 'Get-MgServicePrincipal', 'Update-MgServicePrincipal' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } 48 | } 49 | 50 | process { 51 | if ($CriticalError) { return } 52 | 53 | if ($Default) { 54 | Write-Verbose "Default flag set. preferredTokenSigningKeyThumbprint will be set to null " 55 | $KeyThumbprint = $null 56 | } 57 | 58 | if ($null -ne $KeyThumbprint) { 59 | $KeyThumbprint = $KeyThumbprint.Replace(" ", "").ToLower() 60 | } 61 | 62 | $body = @{preferredTokenSigningKeyThumbprint = $KeyThumbprint } | ConvertTo-Json 63 | $body = $body.replace('""','null') 64 | 65 | Write-Verbose "Retrieving Service Principal" 66 | $sp = Get-MgServicePrincipal -Filter "appId eq '$ApplicationId'" 67 | 68 | if ($null -ne $sp) { 69 | Write-Verbose "Service Principal found: $($sp.DisplayName)" 70 | Write-Verbose "Updating Service Principal preferredTokenSigningKeyThumbprint" 71 | Invoke-MgGraphRequest -Method "PATCH" -Uri "https://graph.microsoft.com/v1.0/servicePrincipals/$($sp.id)" -Body $body 72 | } 73 | else { 74 | Write-Error "Service principal was not found - Please check the Client (Application) ID" 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/internal/AdfsSamples/AdfsAccessControlPolicy.xml: -------------------------------------------------------------------------------- 1 | 2 | false 3 | 4 | 5 | 6 | 7 | Equals 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Amazon Web Services.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Amazon Web Services.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/BOX.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/BOX.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Blackboard.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Blackboard.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Concur.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Concur.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/CornerStone OnDemand.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/CornerStone OnDemand.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Facebook for Work.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Facebook for Work.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Google Cloud Console.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Google Cloud Console.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/SAP Cloud Identity Platform.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/SAP Cloud Identity Platform.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Salesforce.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Salesforce.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Service Now.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Service Now.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Slack.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Slack.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/SuccessFactors.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/SuccessFactors.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Templafy.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Templafy.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Workday.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Workday.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Zoom.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Zoom.json -------------------------------------------------------------------------------- /src/internal/AdfsSamples/Zscaler.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AzureAD/MSIdentityTools/1f7e080fa51bdd1203ad8353e547fdf8c4826cfe/src/internal/AdfsSamples/Zscaler.json -------------------------------------------------------------------------------- /src/internal/Confirm-JsonWebSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Validate the digital signature for JSON Web Signature. 4 | .EXAMPLE 5 | PS C:\>Confirm-JsonWebSignature $Base64JwsString -SigningCertificate $SigningCertificate 6 | Validate the JWS string was signed by provided certificate. 7 | .INPUTS 8 | System.String 9 | #> 10 | function Confirm-JsonWebSignature { 11 | [CmdletBinding()] 12 | [Alias('Confirm-Jws')] 13 | [OutputType([bool])] 14 | param ( 15 | # JSON Web Signature (JWS) 16 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 17 | [string[]] $InputObjects, 18 | # Certificate used to sign the data 19 | [Parameter(Mandatory = $true)] 20 | [System.Security.Cryptography.X509Certificates.X509Certificate2] $SigningCertificate 21 | ) 22 | 23 | process { 24 | foreach ($InputObject in $InputObjects) { 25 | $Jws = ConvertFrom-JsonWebSignature $InputObject 26 | $JwsData = $InputObject.Substring(0, $InputObject.LastIndexOf('.')) 27 | [Security.Cryptography.HashAlgorithmName] $HashAlgorithm = [Security.Cryptography.HashAlgorithmName]::"SHA$($Jws.Header.alg.Substring(2,3))" 28 | switch ($Jws.Header.alg.Substring(0, 2)) { 29 | 'RS' { 30 | $RSAKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($SigningCertificate) 31 | [bool] $Result = $RSAKey.VerifyData([System.Text.Encoding]::UTF8.GetBytes($JwsData), $Jws.Signature, $HashAlgorithm, [Security.Cryptography.RSASignaturePadding]::Pkcs1) 32 | } 33 | 'PS' { 34 | $RSAKey = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($SigningCertificate) 35 | [bool] $Result = $RSAKey.VerifyData([System.Text.Encoding]::UTF8.GetBytes($JwsData), $Jws.Signature, $HashAlgorithm, [Security.Cryptography.RSASignaturePadding]::Pss) 36 | } 37 | 'ES' { 38 | $ECDsaKey = [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDsaPublicKey($SigningCertificate) 39 | [bool] $Result = $ECDsaKey.VerifyData([System.Text.Encoding]::UTF8.GetBytes($JwsData), $Jws.Signature, $HashAlgorithm) 40 | } 41 | } 42 | Write-Output $Result 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-Base64String.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Base64 String to Byte Array or Plain Text String. 4 | .DESCRIPTION 5 | 6 | .EXAMPLE 7 | PS C:\>ConvertFrom-Base64String "QSBzdHJpbmcgd2l0aCBiYXNlNjQgZW5jb2Rpbmc=" 8 | Convert Base64 String to String with Default Encoding. 9 | .EXAMPLE 10 | PS C:\>"QVNDSUkgc3RyaW5nIHdpdGggYmFzZTY0dXJsIGVuY29kaW5n" | ConvertFrom-Base64String -Base64Url -Encoding Ascii 11 | Convert Base64Url String to String with Ascii Encoding. 12 | .EXAMPLE 13 | PS C:\>[guid](ConvertFrom-Base64String "5oIhNbCaFUGAe8NsiAKfpA==" -RawBytes) 14 | Convert Base64 String to GUID. 15 | .INPUTS 16 | System.String 17 | .LINK 18 | https://github.com/jasoth/Utility.PS 19 | #> 20 | function ConvertFrom-Base64String { 21 | [CmdletBinding()] 22 | [OutputType([byte[]], [string])] 23 | param ( 24 | # Value to convert 25 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 26 | [string[]] $InputObjects, 27 | # Use base64url variant 28 | [Parameter (Mandatory = $false)] 29 | [switch] $Base64Url, 30 | # Output raw byte array 31 | [Parameter (Mandatory = $false)] 32 | [switch] $RawBytes, 33 | # Encoding to use for text strings 34 | [Parameter (Mandatory = $false)] 35 | [ValidateSet('Ascii', 'UTF32', 'UTF7', 'UTF8', 'BigEndianUnicode', 'Unicode')] 36 | [string] $Encoding = 'Default' 37 | ) 38 | 39 | process { 40 | foreach ($InputObject in $InputObjects) { 41 | [string] $strBase64 = $InputObject 42 | if (!$PSBoundParameters.ContainsValue('Base64Url') -and ($strBase64.Contains('-') -or $strBase64.Contains('_'))) { $Base64Url = $true } 43 | if ($Base64Url) { $strBase64 = $strBase64.Replace('-', '+').Replace('_', '/').PadRight($strBase64.Length + (4 - $strBase64.Length % 4) % 4, '=') } 44 | [byte[]] $outBytes = [System.Convert]::FromBase64String($strBase64) 45 | if ($RawBytes) { 46 | Write-Output $outBytes -NoEnumerate 47 | } 48 | else { 49 | [string] $outString = ([Text.Encoding]::$Encoding.GetString($outBytes)) 50 | Write-Output $outString 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-HexString.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert from Hex String 4 | .DESCRIPTION 5 | 6 | .EXAMPLE 7 | PS C:\>ConvertFrom-HexString "57 68 61 74 20 69 73 20 61 20 68 65 78 20 73 74 72 69 6E 67 3F" 8 | Convert hex byte string seperated by spaces to string. 9 | .EXAMPLE 10 | PS C:\>"415343494920737472696E6720746F2068657820737472696E67" | ConvertFrom-HexString -Delimiter "" -Encoding Ascii 11 | Convert hex byte string with no seperation to ASCII string. 12 | .INPUTS 13 | System.String 14 | .LINK 15 | https://github.com/jasoth/Utility.PS 16 | #> 17 | function ConvertFrom-HexString { 18 | [CmdletBinding()] 19 | param ( 20 | # Value to convert 21 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 22 | [string[]] $InputObject, 23 | # Delimiter between Hex pairs 24 | [Parameter (Mandatory = $false)] 25 | [string] $Delimiter = ' ', 26 | # Output raw byte array 27 | [Parameter (Mandatory = $false)] 28 | [switch] $RawBytes, 29 | # Encoding to use for text strings 30 | [Parameter (Mandatory = $false)] 31 | [ValidateSet('Ascii', 'UTF32', 'UTF7', 'UTF8', 'BigEndianUnicode', 'Unicode')] 32 | [string] $Encoding = 'Default' 33 | ) 34 | 35 | process { 36 | $listBytes = New-Object object[] $InputObject.Count 37 | for ($iString = 0; $iString -lt $InputObject.Count; $iString++) { 38 | [string] $strHex = $InputObject[$iString] 39 | if ($strHex.Substring(2, 1) -eq $Delimiter) { 40 | [string[]] $listHex = $strHex -split $Delimiter 41 | } 42 | else { 43 | [string[]] $listHex = New-Object string[] ($strHex.Length / 2) 44 | for ($iByte = 0; $iByte -lt $strHex.Length; $iByte += 2) { 45 | $listHex[[System.Math]::Truncate($iByte / 2)] = $strHex.Substring($iByte, 2) 46 | } 47 | } 48 | 49 | [byte[]] $outBytes = New-Object byte[] $listHex.Count 50 | for ($iByte = 0; $iByte -lt $listHex.Count; $iByte++) { 51 | $outBytes[$iByte] = [byte]::Parse($listHex[$iByte], [System.Globalization.NumberStyles]::HexNumber) 52 | } 53 | 54 | if ($RawBytes) { $listBytes[$iString] = $outBytes } 55 | else { 56 | $outString = ([Text.Encoding]::$Encoding.GetString($outBytes)) 57 | Write-Output $outString 58 | } 59 | } 60 | if ($RawBytes) { 61 | return $listBytes 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-JsonWebSignature.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Json Web Signature (JWS) structure to PowerShell object. 4 | .EXAMPLE 5 | PS C:\>$MsalToken.IdToken | ConvertFrom-JsonWebSignature 6 | Convert OAuth IdToken JWS to PowerShell object. 7 | .INPUTS 8 | System.String 9 | #> 10 | function ConvertFrom-JsonWebSignature { 11 | [CmdletBinding()] 12 | [Alias('ConvertFrom-Jws')] 13 | [Alias('ConvertFrom-JsonWebToken')] 14 | [Alias('ConvertFrom-Jwt')] 15 | [OutputType([PSCustomObject])] 16 | param ( 17 | # JSON Web Signature (JWS) 18 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 19 | [string[]] $InputObjects, 20 | # Content Type of the Payload 21 | [Parameter(Mandatory = $false)] 22 | [ValidateSet('text/plain', 'application/json', 'application/octet-stream')] 23 | [string] $ContentType = 'application/json' 24 | ) 25 | 26 | process { 27 | foreach ($InputObject in $InputObjects) { 28 | [string[]] $JwsComponents = $InputObject.Split('.') 29 | switch ($ContentType) { 30 | 'application/octet-stream' { [byte[]] $JwsPayload = $JwsComponents[1] | ConvertFrom-Base64String -Base64Url -RawBytes } 31 | 'text/plain' { [string] $JwsPayload = $JwsComponents[1] | ConvertFrom-Base64String -Base64Url } 32 | 'application/json' { [PSCustomObject] $JwsPayload = $JwsComponents[1] | ConvertFrom-Base64String -Base64Url | ConvertFrom-Json } 33 | Default { [string] $JwsPayload = $JwsComponents[1] | ConvertFrom-Base64String -Base64Url } 34 | } 35 | [PSCustomObject] $JwsDecoded = New-Object PSCustomObject -Property @{ 36 | Header = $JwsComponents[0] | ConvertFrom-Base64String -Base64Url | ConvertFrom-Json 37 | Payload = $JwsPayload 38 | Signature = $JwsComponents[2] | ConvertFrom-Base64String -Base64Url -RawBytes 39 | } 40 | Write-Output ($JwsDecoded | Select-Object -Property Header, Payload, Signature) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-QueryString.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Query String to object. 4 | .DESCRIPTION 5 | 6 | .EXAMPLE 7 | PS C:\>ConvertFrom-QueryString '?name=path/file.json&index=10' 8 | Convert query string to object. 9 | .EXAMPLE 10 | PS C:\>'name=path/file.json&index=10' | ConvertFrom-QueryString -AsHashtable 11 | Convert query string to hashtable. 12 | .INPUTS 13 | System.String 14 | .LINK 15 | https://github.com/jasoth/Utility.PS 16 | #> 17 | function ConvertFrom-QueryString { 18 | [CmdletBinding()] 19 | [OutputType([psobject])] 20 | [OutputType([hashtable])] 21 | param ( 22 | # Value to convert 23 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 24 | [AllowEmptyString()] 25 | [string[]] $InputStrings, 26 | # URL decode parameter names 27 | [Parameter(Mandatory = $false)] 28 | [switch] $DecodeParameterNames, 29 | # Converts to hash table object 30 | [Parameter(Mandatory = $false)] 31 | [switch] $AsHashtable 32 | ) 33 | 34 | process { 35 | foreach ($InputString in $InputStrings) { 36 | if ($AsHashtable) { [hashtable] $OutputObject = @{ } } 37 | else { [psobject] $OutputObject = New-Object psobject } 38 | 39 | if ($InputString) { 40 | if ($InputString[0] -eq '?') { $InputString = $InputString.Substring(1) } 41 | [string[]] $QueryParameters = $InputString.Split('&') 42 | foreach ($QueryParameter in $QueryParameters) { 43 | [string[]] $QueryParameterPair = $QueryParameter.Split('=') 44 | if ($DecodeParameterNames) { $QueryParameterPair[0] = [System.Net.WebUtility]::UrlDecode($QueryParameterPair[0]) } 45 | if ($OutputObject -is [hashtable]) { 46 | $OutputObject.Add($QueryParameterPair[0], [System.Net.WebUtility]::UrlDecode($QueryParameterPair[1])) 47 | } 48 | else { 49 | $OutputObject | Add-Member $QueryParameterPair[0] -MemberType NoteProperty -Value ([System.Net.WebUtility]::UrlDecode($QueryParameterPair[1])) 50 | } 51 | } 52 | } 53 | Write-Output $OutputObject 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-SamlMessage.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Saml Message to XML object. 4 | .EXAMPLE 5 | PS C:\>ConvertFrom-SamlMessage 'Base64String' 6 | Convert Saml Message to XML object. 7 | .INPUTS 8 | System.String 9 | .OUTPUTS 10 | SamlMessage : System.Xml.XmlDocument 11 | #> 12 | function ConvertFrom-SamlMessage { 13 | [CmdletBinding()] 14 | [Alias('ConvertFrom-SamlRequest')] 15 | [Alias('ConvertFrom-SamlResponse')] 16 | #[OutputType([xml])] 17 | param ( 18 | # SAML Message 19 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 20 | [string[]] $InputObject 21 | ) 22 | 23 | process { 24 | foreach ($_InputObject in $InputObject) { 25 | [byte[]] $bytesInput = $null 26 | $xmlOutput = New-Object SamlMessage 27 | try { 28 | $xmlOutput.LoadXml($_InputObject) 29 | } 30 | catch { 31 | try { 32 | $bytesInput = [System.Convert]::FromBase64String($_InputObject) 33 | } 34 | catch { 35 | $bytesInput = [System.Convert]::FromBase64String([System.Net.WebUtility]::UrlDecode($_InputObject)) 36 | } 37 | } 38 | if ($bytesInput) { 39 | try { 40 | $streamInput = New-Object System.IO.MemoryStream -ArgumentList @($bytesInput, $false) 41 | try { 42 | $xmlOutput.Load($streamInput) 43 | } 44 | catch { 45 | $streamInput = New-Object System.IO.MemoryStream -ArgumentList @($bytesInput, $false) 46 | try { 47 | $streamOutput = New-Object System.IO.MemoryStream 48 | try { 49 | [System.IO.Compression.DeflateStream] $streamCompression = New-Object System.IO.Compression.DeflateStream -ArgumentList $streamInput, ([System.IO.Compression.CompressionMode]::Decompress), $true 50 | $streamCompression.CopyTo($streamOutput) 51 | } 52 | finally { $streamCompression.Dispose() } 53 | $streamOutput.Position = 0 54 | $xmlOutput.Load($streamOutput) 55 | #[string] $strOutput = ([Text.Encoding]::$Encoding.GetString($streamOutput.ToArray())) 56 | #$xmlOutput.LoadXml($strOutput) 57 | } 58 | finally { $streamOutput.Dispose() } 59 | } 60 | } 61 | finally { $streamInput.Dispose() } 62 | } 63 | 64 | Write-Output $xmlOutput 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/internal/ConvertFrom-SecureStringAsPlainText.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert/Decrypt SecureString to Plain Text String. 4 | .DESCRIPTION 5 | 6 | .EXAMPLE 7 | PS C:\>ConvertFrom-SecureStringAsPlainText (ConvertTo-SecureString 'SuperSecretString' -AsPlainText -Force) -Force 8 | Convert plain text to SecureString and then convert it back. 9 | .INPUTS 10 | System.Security.SecureString 11 | .LINK 12 | https://github.com/jasoth/Utility.PS 13 | #> 14 | function ConvertFrom-SecureStringAsPlainText { 15 | [CmdletBinding()] 16 | [OutputType([string])] 17 | param ( 18 | # Secure String Value 19 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 20 | [securestring] $SecureString, 21 | # Confirms that you understand the implications of using the AsPlainText parameter and still want to use it. 22 | [Parameter(Mandatory = $false)] 23 | [switch] $Force 24 | ) 25 | 26 | begin { 27 | if ($PSVersionTable.PSVersion -ge [version]'7.0') { 28 | Write-Warning 'PowerShell 7 introduced an AsPlainText parameter to the ConvertFrom-SecureString cmdlet.' 29 | } 30 | if (!${Force}) { 31 | ## Terminating Error 32 | $Exception = New-Object ArgumentException -ArgumentList 'The system cannot protect plain text output. To suppress this warning and convert a SecureString to plain text, reissue the command specifying the Force parameter.' 33 | Write-Error -Exception $Exception -Category ([System.Management.Automation.ErrorCategory]::InvalidArgument) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'ConvertSecureStringFailureForceRequired' -TargetObject ${SecureString} -ErrorAction Stop 34 | } 35 | } 36 | 37 | process { 38 | try { 39 | [IntPtr] $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) 40 | Write-Output ([System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($BSTR)) 41 | } 42 | finally { 43 | [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/internal/ConvertTo-QueryString.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Convert Hashtable to Query String. 4 | .DESCRIPTION 5 | 6 | .EXAMPLE 7 | PS C:\>ConvertTo-QueryString @{ name = 'path/file.json'; index = 10 } 8 | Convert hashtable to query string. 9 | .EXAMPLE 10 | PS C:\>[ordered]@{ title = 'convert&prosper'; id = [guid]'352182e6-9ab0-4115-807b-c36c88029fa4' } | ConvertTo-QueryString 11 | Convert ordered dictionary to query string. 12 | .INPUTS 13 | System.Collections.Hashtable 14 | .LINK 15 | https://github.com/jasoth/Utility.PS 16 | #> 17 | function ConvertTo-QueryString { 18 | [CmdletBinding()] 19 | [OutputType([string])] 20 | param ( 21 | # Value to convert 22 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 23 | [object] $InputObjects, 24 | # URL encode parameter names 25 | [Parameter(Mandatory = $false)] 26 | [switch] $EncodeParameterNames 27 | ) 28 | 29 | process { 30 | foreach ($InputObject in $InputObjects) { 31 | $QueryString = New-Object System.Text.StringBuilder 32 | if ($InputObject -is [hashtable] -or $InputObject -is [System.Collections.Specialized.OrderedDictionary] -or $InputObject.GetType().FullName.StartsWith('System.Collections.Generic.Dictionary')) { 33 | foreach ($Item in $InputObject.GetEnumerator()) { 34 | if ($QueryString.Length -gt 0) { [void]$QueryString.Append('&') } 35 | [string] $ParameterName = $Item.Key 36 | if ($EncodeParameterNames) { $ParameterName = [System.Net.WebUtility]::UrlEncode($ParameterName) } 37 | [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode($Item.Value)) 38 | } 39 | } 40 | elseif ($InputObject -is [object] -and $InputObject -isnot [ValueType]) { 41 | foreach ($Item in ($InputObject | Get-Member -MemberType Property, NoteProperty)) { 42 | if ($QueryString.Length -gt 0) { [void]$QueryString.Append('&') } 43 | [string] $ParameterName = $Item.Name 44 | if ($EncodeParameterNames) { $ParameterName = [System.Net.WebUtility]::UrlEncode($ParameterName) } 45 | [void]$QueryString.AppendFormat('{0}={1}', $ParameterName, [System.Net.WebUtility]::UrlEncode($InputObject.($Item.Name))) 46 | } 47 | } 48 | else { 49 | ## Non-Terminating Error 50 | $Exception = New-Object ArgumentException -ArgumentList ('Cannot convert input of type {0} to query string.' -f $InputObject.GetType()) 51 | Write-Error -Exception $Exception -Category ([System.Management.Automation.ErrorCategory]::ParserError) -CategoryActivity $MyInvocation.MyCommand -ErrorId 'ConvertQueryStringFailureTypeNotSupported' -TargetObject $InputObject 52 | continue 53 | } 54 | 55 | Write-Output $QueryString.ToString() 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/internal/Get-GraphBaseUri.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Return the base URI for Graph API based on the current Graph Context's environment. 4 | .DESCRIPTION 5 | 6 | #> 7 | 8 | function Get-GraphBaseUri { 9 | [CmdletBinding()] 10 | [OutputType([string])] 11 | param () 12 | 13 | begin { 14 | $baseUri = 'https://graph.microsoft.com' 15 | try { 16 | $context = Get-MgContext 17 | $environment = Get-ObjectPropertyValue $context -Name 'Environment' 18 | if($null -eq $environment){ 19 | $environment = 'Global' 20 | } 21 | $baseUri = (Get-MgEnvironment -Name $environment).GraphEndpoint 22 | } 23 | catch { 24 | 25 | } 26 | Write-Output $baseUri 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/internal/Get-MsftUserRealm.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get User Realm Information for a Microsoft user account. 4 | .EXAMPLE 5 | Get-MsftUserRealm user@domain.com 6 | .EXAMPLE 7 | 'user1@domainA.com','user2@domainA.com','user@domainB.com' | Get-MsftUserRealm 8 | #> 9 | function Get-MsftUserRealm { 10 | [CmdletBinding()] 11 | [OutputType([PsCustomObject[]])] 12 | param ( 13 | # User Principal Name 14 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 15 | [string[]] $User, 16 | # Check For Microsoft Account 17 | [Parameter(Mandatory = $false)] 18 | [switch] $CheckForMicrosoftAccount, 19 | # API Version 20 | [Parameter(Mandatory = $false)] 21 | [string] $ApiVersion = '2.1' 22 | ) 23 | 24 | process { 25 | foreach ($_User in $User) { 26 | $uriUserRealm = New-Object System.UriBuilder 'https://login.microsoftonline.com/common/userrealm' 27 | $uriUserRealm.Query = ConvertTo-QueryString @{ 28 | 'api-version' = $ApiVersion 29 | 'checkForMicrosoftAccount' = $CheckForMicrosoftAccount 30 | 'user' = $_User 31 | } 32 | 33 | $Result = Invoke-RestMethod -UseBasicParsing -Method Get -Uri $uriUserRealm.Uri.AbsoluteUri 34 | Write-Output $Result 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/internal/Get-ObjectPropertyValue.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Get object property value. 4 | .EXAMPLE 5 | PS C:\>$object = New-Object psobject -Property @{ title = 'title value' } 6 | PS C:\>$object | Get-ObjectPropertyValue -Property 'title' 7 | Get value of object property named title. 8 | .EXAMPLE 9 | PS C:\>$object = New-Object psobject -Property @{ lvl1 = (New-Object psobject -Property @{ nextLevel = 'lvl2 data' }) } 10 | PS C:\>Get-ObjectPropertyValue $object -Property 'lvl1', 'nextLevel' 11 | Get value of nested object property named nextLevel. 12 | .INPUTS 13 | System.Collections.Hashtable 14 | System.Management.Automation.PSObject 15 | #> 16 | function Get-ObjectPropertyValue { 17 | [CmdletBinding()] 18 | [OutputType([psobject])] 19 | param ( 20 | # Object containing property values 21 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 22 | [AllowNull()] 23 | [psobject] $InputObjects, 24 | # Name of property. Specify an array of property names to tranverse nested objects. 25 | [Parameter(Mandatory = $true, ValueFromRemainingArguments = $true)] 26 | [string[]] $Property 27 | ) 28 | 29 | process { 30 | foreach ($InputObject in $InputObjects) { 31 | for ($iProperty = 0; $iProperty -lt $Property.Count; $iProperty++) { 32 | ## Get property value 33 | if ($InputObject -is [hashtable]) { 34 | if ($InputObject.ContainsKey($Property[$iProperty])) { 35 | $PropertyValue = $InputObject[$Property[$iProperty]] 36 | } 37 | else { $PropertyValue = $null } 38 | } 39 | else { 40 | $PropertyValue = Select-Object -InputObject $InputObject -ExpandProperty $Property[$iProperty] -ErrorAction Ignore 41 | if ($null -eq $PropertyValue) { break } 42 | } 43 | ## Check for more nested properties 44 | if ($iProperty -lt $Property.Count - 1) { 45 | $InputObject = $PropertyValue 46 | if ($null -eq $InputObject) { break } 47 | } 48 | else { 49 | Write-Output $PropertyValue 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/internal/Get-OpenIdProviderConfiguration.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Parse OpenId Provider Configuration and Keys 4 | .EXAMPLE 5 | PS C:\>Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com | Get-OpenIdProviderConfiguration 6 | Get OpenId Provider Configuration for a specific Microsoft organizational tenant (Azure AD). 7 | .EXAMPLE 8 | PS C:\>Get-MsIdAuthorityUri -TenantId tenant.onmicrosoft.com | Get-OpenIdProviderConfiguration -Keys 9 | Get public keys for OpenId Provider for a specific Microsoft organizational tenant (Azure AD). 10 | .EXAMPLE 11 | PS C:\>Get-MsIdAuthorityUri -Msa | Get-OpenIdProviderConfiguration 12 | Get OpenId Provider Configuration for Microsoft consumer accounts (MSA). 13 | .EXAMPLE 14 | PS C:\>Get-OpenIdProviderConfiguration 'https://accounts.google.com/' 15 | Get OpenId Provider Configuration for Google Accounts. 16 | .INPUTS 17 | System.Uri 18 | #> 19 | function Get-OpenIdProviderConfiguration { 20 | [CmdletBinding()] 21 | [OutputType([PsCustomObject[]])] 22 | param ( 23 | # Identity Provider Authority URI 24 | [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1)] 25 | [uri] $Issuer, 26 | # Return configuration keys 27 | [Parameter(Mandatory = $false)] 28 | [switch] $Keys 29 | ) 30 | ## Build common OpenId provider configuration URI 31 | $uriOpenIdProviderConfiguration = New-Object System.UriBuilder $Issuer.AbsoluteUri 32 | if (!$uriOpenIdProviderConfiguration.Path.EndsWith('/.well-known/openid-configuration')) { $uriOpenIdProviderConfiguration.Path += '/.well-known/openid-configuration' } 33 | 34 | ## Download and parse configuration 35 | $OpenIdProviderConfiguration = Invoke-RestMethod -UseBasicParsing -Uri $uriOpenIdProviderConfiguration.Uri.AbsoluteUri # Should return ContentType 'application/json' 36 | if ($Keys) { 37 | $OpenIdProviderConfigurationJwks = Invoke-RestMethod -UseBasicParsing -Uri $OpenIdProviderConfiguration.jwks_uri # Should return ContentType 'application/json' 38 | return $OpenIdProviderConfigurationJwks.keys 39 | } 40 | else { 41 | return $OpenIdProviderConfiguration 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/internal/Get-ParsedTokenFromResponse.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Parses token from response as plain text string. 4 | .EXAMPLE 5 | PS C:\>Get-ParsedTokenFromResponse $response 6 | Parses token from $response as plain text string. 7 | #> 8 | function Get-ParsedTokenFromResponse { 9 | [CmdletBinding()] 10 | [OutputType([string])] 11 | param ( 12 | # HTTP response 13 | [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] 14 | [string] $HttpResponse, 15 | [Parameter(Mandatory=$true, Position = 1)] 16 | # Protocol SAML or WsFed 17 | [ValidateSet("SAML", "WsFed")] 18 | [string]$Protocol 19 | 20 | ) 21 | 22 | $token = "" 23 | 24 | if ($Protocol -eq "SAML") { 25 | # 26 | if($HttpResponse -match '