Connect to Exchange Online automated when MFA is enabled (Using the SecureApp Model)

So in the past months Microsoft has been forcing CSPs and MSPs to use MFA, something I strongly encourage and am glad with. The only issue with this was that Microsoft made this move without accounting for automation and automated jobs that need to run, especially jobs that run unattended and over multiple delegates.

To make sure that we could be able to use this I’ve been advocating and discussing this with Microsoft a lot, including a public part here, but after several months and great help from
Janosch Ulmer at Microsoft I’ve been able to compose a script for everyone to use, to connect to all Microsoft resources using the secure application model, including Exchange Online.

Compiling all of these scripts took me quite some time. If you have questions or issues just let me know!

The Script

Param
(
    [Parameter(Mandatory = $false)]
    [switch]$ConfigurePreconsent,
    [Parameter(Mandatory = $true)]
    [string]$DisplayName,
    [Parameter(Mandatory = $false)]
    [string]$TenantId
)

$ErrorActionPreference = "Stop"

# Check if the Azure AD PowerShell module has already been loaded.
if ( ! ( Get-Module AzureAD ) ) {
    # Check if the Azure AD PowerShell module is installed.
    if ( Get-Module -ListAvailable -Name AzureAD ) {
        # The Azure AD PowerShell module is not load and it is installed. This module
        # must be loaded for other operations performed by this script.
        Write-Host -ForegroundColor Green "Loading the Azure AD PowerShell module..."
        Import-Module AzureAD
    } else {
        Install-Module AzureAD
    }
}

try {
    Write-Host -ForegroundColor Green "When prompted please enter the appropriate credentials... Warning: Window might have pop-under in VSCode"

    if([string]::IsNullOrEmpty($TenantId)) {
        Connect-AzureAD | Out-Null

        $TenantId = $(Get-AzureADTenantDetail).ObjectId
    } else {
        Connect-AzureAD -TenantId $TenantId | Out-Null
    }
} catch [Microsoft.Azure.Common.Authentication.AadAuthenticationCanceledException] {
    # The authentication attempt was canceled by the end-user. Execution of the script should be halted.
    Write-Host -ForegroundColor Yellow "The authentication attempt was canceled. Execution of the script will be halted..."
    Exit
} catch {
    # An unexpected error has occurred. The end-user should be notified so that the appropriate action can be taken.
    Write-Error "An unexpected error has occurred. Please review the following error message and try again." `
        "$($Error[0].Exception)"
}

$adAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId = "00000002-0000-0000-c000-000000000000";
    ResourceAccess =
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id = "5778995a-e1bf-45b8-affa-663a9f3f4d04";
        Type = "Role"},
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id = "a42657d6-7f20-40e3-b6f0-cee03008a62a";
        Type = "Scope"},
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id = "311a71cc-e848-46a1-bdf8-97ff7156d8e6";
        Type = "Scope"}
}

$graphAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId = "00000003-0000-0000-c000-000000000000";
    ResourceAccess =
        [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
            Id = "bf394140-e372-4bf9-a898-299cfc7564e5";
            Type = "Role"},
        [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
            Id = "7ab1d382-f21e-4acd-a863-ba3e13f7da61";
            Type = "Role"}
}

$partnerCenterAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId = "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd";
    ResourceAccess =
        [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
            Id = "1cebfa2a-fb4d-419e-b5f9-839b4383e05a";
            Type = "Scope"}
}

$SessionInfo = Get-AzureADCurrentSessionInfo

Write-Host -ForegroundColor Green "Creating the Azure AD application and related resources..."

$app = New-AzureADApplication -AvailableToOtherTenants $true -DisplayName $DisplayName -IdentifierUris "https://$($SessionInfo.TenantDomain)/$((New-Guid).ToString())" -RequiredResourceAccess $adAppAccess, $graphAppAccess, $partnerCenterAppAccess -ReplyUrls @("urn:ietf:wg:oauth:2.0:oob","https://localhost","http://localhost","http://localhost:8400")
$password = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
$spn = New-AzureADServicePrincipal -AppId $app.AppId -DisplayName $DisplayName


    $adminAgentsGroup = Get-AzureADGroup -Filter "DisplayName eq 'AdminAgents'"
    Add-AzureADGroupMember -ObjectId $adminAgentsGroup.ObjectId -RefObjectId $spn.ObjectId

write-host "Installing PartnerCenter Module." -ForegroundColor Green
install-module PartnerCenter -Force
write-host "Sleeping for 30 seconds to allow app creation on O365" -foregroundcolor green
start-sleep 30
write-host "Please approve General consent form." -ForegroundColor Green
$PasswordToSecureString = $password.value | ConvertTo-SecureString -asPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($($app.AppId),$PasswordToSecureString)
$token = New-PartnerAccessToken -ApplicationId "$($app.AppId)" -Scopes 'https://api.partnercenter.microsoft.com/user_impersonation' -ServicePrincipal -Credential $credential -Tenant $($spn.AppOwnerTenantID) -UseAuthorizationCode
write-host "Please approve Exchange consent form." -ForegroundColor Green
$Exchangetoken = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -Scopes 'https://outlook.office365.com/.default' -Tenant $($spn.AppOwnerTenantID) -UseDeviceAuthentication
write-host "Last initation required: Please browse to https://login.microsoftonline.com/$($spn.AppOwnerTenantID)/adminConsent?client_id=$($app.AppId)"
write-host "Press any key after auth. An error report about incorrect URIs is expected!"
[void][System.Console]::ReadKey($true)
Write-Host "================ Secrets ================"
Write-Host "`$ApplicationId         = $($app.AppId)"
Write-Host "`$ApplicationSecret     = $($password.Value)"
Write-Host "`$TenantID              = $($spn.AppOwnerTenantID)"
write-host "`$RefreshToken          = $($token.refreshtoken)" -ForegroundColor Blue
write-host "`$Exchange RefreshToken = $($ExchangeToken.Refreshtoken)" -ForegroundColor Green
Write-Host "================ Secrets ================"
Write-Host "    SAVE THESE IN A SECURE LOCATION     " 

The following script creates a new application, and connects to all resources. At the end you will receive several private keys. Store these in a secure location for future usage such as a Azure Keyvault or IT-Glue. With these keys you can connect to all your delegated resources without MFA. There currently are some slight issues on the Azure side with performing consent, and as thus you’ll have to click a couple of times. Sorry about that 🙂

So, now that you have these keys you can use the following scripts to connect to the correct resources:

MSOL-Module
$ApplicationId         = 'xxxx-xxxx-xxxx-xxxx-xxx'
$ApplicationSecret     = 'YOURSECRET' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'xxxxxx-xxxx-xxx-xxxx--xxx'
$RefreshToken          = 'LongResourcetoken'
$ExchangeRefreshToken  = 'LongExchangeToken'
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)

$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 

Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
AzureAD Module
$ApplicationId         = 'xxxx-xxxx-xxxx-xxxx-xxx'
$ApplicationSecret     = 'YOURSECRET' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'xxxxxx-xxxx-xxx-xxxx--xxx'
$RefreshToken          = 'LongResourcetoken'
$ExchangeRefreshToken  = 'LongExchangeToken'
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)

$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 

Connect-AzureAD -AadAccessToken $aadGraphToken.AccessToken -AccountId 'VALIDEMAILADDRESS' -MsAccessToken $graphToken.AccessToken -TenantId $tenantID
Exchange Online

For the Exchange Online module we’ll need to do a little more effort – You will need the tenantid of the client you are connecting too. I’ll show you how to perform a specific action for each client you have delegated access too.

$ApplicationId         = 'xxxx-xxxx-xxx-xxxx-xxxx'
$ApplicationSecret     = 'TheSecretTheSecrey' | Convertto-SecureString -AsPlainText -Force
$TenantID              = 'YourTenantID'
$RefreshToken          = 'RefreshToken'
$ExchangeRefreshToken  = 'ExchangeRefreshToken'
$upn                   = 'UPN-Used-To-Generate-Tokens'
$credential = New-Object System.Management.Automation.PSCredential($ApplicationId, $ApplicationSecret)

$aadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.windows.net/.default' -ServicePrincipal -Tenant $tenantID 
$graphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes 'https://graph.microsoft.com/.default' -ServicePrincipal -Tenant $tenantID 

Connect-MsolService -AdGraphAccessToken $aadGraphToken.AccessToken -MsGraphAccessToken $graphToken.AccessToken
$customers = Get-MsolPartnerContract -All
foreach($customer in $customers){
    $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.TenantId
    $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
    $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue)
    $customerId = $customer.DefaultDomainName
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection
    Import-PSSession $session
#From here you can enter your own commands
get-mailbox 
#end of commands
Remove-PSSession $session
}

And that’s it! I hope it helps MSPs, CSPs, etc. 🙂 as always, Happy PowerShelling!

Follow me

Kelvin Tegelaar

I am a Microsoft Certified System Engineer working as the CTO of the Managed Services Provider Lime Networks B.V. in the Netherlands. I mostly enjoy automating business processes by deploying PowerShell solutions, but just have a large passion for Microsoft Technology in general.

If you want to contact me directly you can find me on twitter here, or via email: Kelvin {at} limenetworks.nl
Kelvin Tegelaar
Follow me

35 thoughts on “Connect to Exchange Online automated when MFA is enabled (Using the SecureApp Model)

  1. Ban Hang

    Hey just wanted to give you a quick heads up.
    the first <# seems to be lost in encoding. I'm not sure if this is a formatting issue or something to do with browser compatibility but I thought I'd post to let you know. The script is fanstatic and look great though! thanks

    Reply
  2. Heiner

    I have been working on this since the forced MFA change and could not get it working. Thank you so much for posting this.

    Reply
    1. Kelvin Tegelaar Post author

      The Microsoft Teams module currently does not support delegated access yet. I’ve requested Microsoft to look into it and got the confirmation that it should in a future version, but we’ll have to wait for this… 🙂

      Reply
  3. Geert W

    Hi Kelvin! Sadly, nothing is happening for me after ‘Please approve Exchange consent form’ – there just isn’t anything to approve, no pop-ups. Any idea what this can be?

    Reply
    1. Kelvin Tegelaar Post author

      You should be getting the output

      “To sign in, use a web browser to open the page xxxxx and enter the code xxxxx”

      Are you running the script from ISE or VSCode? I’ve had someone else report the same issue today, So I am suspecting this has something to do with the IDE used. I’ve just tested the script in my VSCode instance and it seems to run fine.

      Reply
    2. Kelvin Tegelaar Post author

      Just checked this for you; When running in ISE the script indeed does not finish running. Please save the script somewhere and run it from a non-ise PowerShell window by entering “.\scriptname.ps1”. Hope it helps!

      Reply
  4. Geert W

    Hi Kelvin,

    Thank you – you are right. I was using the ISE and this was causing the problem.
    However, when using the normal PowerShell windows it won’t output a $RefreshToken or $Exchange RefreshToken for me (just empty).

    I will troubleshoot and let you know :).

    Thank you!

    Reply
    1. Kelvin Tegelaar Post author

      The only tip I can give in this is rerunning the script; the Partnercenter sometimes just does not return any token. I believe this is due to the API getting a time-out but the PartnerCenter module does not report this.

      Good luck, and if you need any more help let me know.

      Reply
  5. Pingback: Documenting with PowerShell: Documenting Office365 mailbox permissions - CyberDrain

  6. Pingback: Monitoring with PowerShell: Alerting on large Office 365 mailboxes - CyberDrain

  7. Saul Ansbacher

    Hey, awesome post, very helpful! I was able to connect to the Exchange Online, which I couldn’t before – this worked. But I was having trouble with the AzureAD: connecting to my CUSTOMER’s AzureAD. I managed to work it out. For the benefit for anyone else:

    Connecting to our CUSTOMERS with Msol is fairly straight forward, use the above Connect-MsolService command to connect to YOUR (eg. CSP) Msol service, then Get-MsolPartnerContract will list all YOUR Customers you have DELEGATED permissions to. You can then use a foreach ($customer in $Customers) {} loop.
    Inside the foreach you can then use any Msol command AND specify their TenantID, eg: Get-MsolUser -TenantId $Customer.TenantId will show the CUSTOMER’S users, not yours. You need to add -TenantId to all Msol commands since you are connected to your Msol service endpoint (but you have delegated permissions to your Customers)

    For AzureAD it was slightly more complicated: I could get MY internal (eg. CSP) Users, but not my CUSTOMER’s Users that I had Delegated permissions to, I would get an error like:
    Get-AzureADUser : Error occurred while executing GetUsers
    Code: Request_BadRequest
    Message: Invalid domain name in the request url.

    The solution for Azure AD is:
    Connect to AzureAD as listed above, this will connect to YOUR (eg. CSP) Azure AD
    Get all your Customers that you have Delegated Admin rights to with: Get-AzureADContract
    Then Disconnect-AzureAd (will disconnect from your AzureAD)
    You will need (just like the Exch Online code): $upn = ‘UPN-Used-To-Generate-Tokens’
    Then in your foreach ($customer in $Customers) {} loop you must obtain NEW AAD and Graph Tokens for the Customer’s TenantId, such as:
    $CustAadGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes ‘https://graph.windows.net/.default’ -ServicePrincipal -Tenant $customer.CustomerContextId
    $CustGraphToken = New-PartnerAccessToken -ApplicationId $ApplicationId -Credential $credential -RefreshToken $refreshToken -Scopes ‘https://graph.microsoft.com/.default’ -ServicePrincipal -Tenant $customer.CustomerContextId
    Finally connect to the CUSTOMER’S AzureAD (to which you have Delegated rights):
    Connect-AzureAD -AadAccessToken $CustAadGraphToken.AccessToken -AccountId $upn -MsAccessToken $CustGraphToken.AccessToken -TenantId $customer.CustomerContextId
    [Note that $customer.CustomerContextId is the CUSTOMER’s TenantID, which is obtained from Get-AzureAdContract (when connected to YOUR Azure AD, the first time)]
    You can now run commands like Get-AzureAdUser or Get-AzureAdDomain and it will show the CUSTOMER’s users/info.
    Disconnect-AzureAd at the end of the loop (from the Customer’s AzureAD)

    Thanks again!

    Reply
  8. Vitus Quinny

    Hi Kelvin,

    I’ve copied & saved everything exactly how it is mentioned in “The Script” to “SecureApp.ps1”
    However, when i execute it through Powershell and enter the Credentials after the prompt, it throws me the following:

    When prompted please enter the appropriate credentials… Warning: Window might have pop-under in VSCode
    Creating the Azure AD application and related resources…
    C:\Temp\SecureApp.ps1 : Cannot bind argument to parameter ‘ObjectId’ because it is null.
    At line:1 char:1
    + .\SecureApp.ps1
    + ~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [SecureApp.ps1], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,SecureApp.ps1

    Reply
    1. Kelvin Tegelaar Post author

      Try running the script form VSCode, I’ve seen it do weird things from ISE. If you are already running it from VSCode or a normal PowerShell window let me know, so I can research it.

      Reply
  9. Ariel

    Hi Kelvin,
    Nice script! But I had a question about the PS Session for exchange. I believe that Basic authentication will be deprecated as of October 31st 2020. So can the SPN/App Account be adapted to work with the ExchangeOnlineManagement PS Module? How would one apply the credentials created by your script to the CMDLET Connect-ExchangeOnline?
    read more here:
    hxxps://o365reports.com/2019/09/25/basic-authentication-exchange-online/
    hxxps://o365reports.com/2019/12/11/connect-exchange-online-powershell-without-basic-authentication/#Install-Exchange-Online-PowerShell-V2-Module

    These articles started me down this path that eventually led me to your site.
    Thanks for your work on this script. This was very useful for automation scripts and such!

    Reply
    1. Kelvin Tegelaar Post author

      Hi Ariel,

      Microsoft has announced the new Exchange Online module will be made compatible with the Secure App Model, they just didn’t say when which is a shame.

      I’ve tested this method by disabling all forms of basic authentication too, and it still works due to it using the oauth conversion, so with a bit of luck you won’t need any changes after october 2020.

      Hope that helps!

      Reply
  10. Paul

    Awesome work, thanks so much for your hard work putting this together. Successfully connecting to Exchange Online which is awesome. Also trying to connect to the Compliance Centre and not having the same luck. Used the same format as connecting to Exch session but get an access denied (where the exchange one connects fine). Any ideas how to connect to this endpoint (or maybe doesnt support same method?)?

    $SccSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri “https://ps.compliance.protection.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true” -Credential $credential -Authentication Basic -AllowRedirection

    Import-PSSession $SccSession -Prefix cc

    Reply
    1. Kelvin Tegelaar Post author

      Glad to help! I haven’t checked the security center yet – I expect we’ll need to give an extra consent for the Security Center application. I’ll try to blog about this next year.

      Reply
      1. Paul

        Awesome, cheers Kelvin. I’ve spent this morning trying to get my head around these ‘ResourceAccess’ GUIDs and what they are all for and mean. Out of interest, which one in your registration of the app is allowing access to Exchange Online? Using Get-AzureADServicePrincipal -All $true, i can see a list of the apps and figured which ones you are granting access to.. I see there’s one called Office 365 Exchange Online with AppId 00000002-0000-0ff1-ce00-000000000000 but see your script doesnt reference that one. the other one we connect to a lot is SharePoint API

        Reply
        1. Kelvin Tegelaar Post author

          No problem! You don’t give access to the Exchange Online portion in your own tenant, but do this on the centrally managed application by Microsoft. That’s what the line “$Exchangetoken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://outlook.office365.com/.default’ -Tenant $($spn.AppOwnerTenantID) -UseDeviceAuthentication” is for. This generates consent for the Exchange Online application hosted by microsoft to access you tenant(s).

          Hope that helps.

          Reply
          1. Paul

            Ahh OK. in the Registration script, i added a new line against the compliance scope and it seemed to return a token (different from the exchange token) $ComplianceToken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://ps.compliance.protection.outlook.com/.default’ -Tenant $($spn.AppOwnerTenantID) -UseDeviceAuthentication

            When i then go to auth with it (like your Exchange example) i still get Access Denied.. is that where i need to possibly grant additional consent like your first reply? Appreciate your help.. there doesn’t seem to be much clear end to end documentation on this.

  11. James

    Hi Kelvin,

    I get the same error mentioned above using PowerShell, ISE and VSCode. I’ve named the script SecureApp.ps1.

    C:\users\XXXX\SecureApp.ps1 : Cannot bind argument to parameter
    ‘ObjectId’ because it is null.
    At line:1 char:1
    + .\SecureApp.ps1
    + ~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [SecureApp.ps1], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,SecureApp.ps1

    Any ideas?

    Thanks in advance!

    Reply
        1. James

          Hi Kelvin,

          I appreciate you’re probably busy but just wondering if you have any other ideas on this?

          Thanks,

          James

          Reply
          1. Kelvin Tegelaar Post author

            Hi Adam, Its Kelvin with an L, not Kevin 🙂

            I’ve been trying to simulate the error but can’t seem to do it on my environment. I’m thinking it has to do either with the Azure AD setup, or a permissions blocker somewhere.

            Can you & James try the following:
            Connect-AzureAD
            $(Get-AzureADTenantDetail).ObjectId

            This should return an objectID, if it does not, then I’m thinking it cannot retrieve your tenantID and you should try running the script as “.\SecureAppModel.ps1 -TenantID “. You can find your tenant ID in the Azure AD portal.

            Let me know if this works!

  12. James

    Hi Kelvin,

    I can’t seem to reply to your last comment but I get the same error I’m afraid. That’s when running “.\SecureAppModel.ps1 -TenantID XXXX“

    C:\XXXX\SecureApp.ps1 : Cannot bind argument to parameter
    ‘ObjectId’ because it is null.
    At line:1 char:1
    + .\SecureApp.ps1 -TenantId XXXX
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [SecureApp.ps1], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,SecureApp.ps1

    I’m able to find the tenant ID by running $(Get-AzureADTenantDetail).ObjectId

    Reply
        1. Kelvin Tegelaar Post author

          Hi James,

          After a lot of testing I think I’ve finally figured it out – The script tries to grab the Microsoft Partnership role and grant access to a user group based on this. The only reason for the error to appear that I’ve seen is that you do not have a Microsoft Partnership(CSP partner/Cloud advisory model/others) or that you do not have complete partnership access.

          To use these scripts you must have some form of Partner Administration enabled. you can check this by going to the URL https://partner.microsoft.com/commerce/customers/list and checking if your clients are listed there.

          Regards,

          Reply
          1. James

            Hi Kelvin,

            Thanks for this.

            I do have MS Partnership but was trying to run this on a few of the tenancies individually. Is there a way I can do this?

            Thanks,

            James

          2. Kelvin Tegelaar Post author

            Hi James! I’ve checked; If you uncomment the part about the AdminRole in the script, it might work. if not then I’m afraid I can’t support that configuration. I understand only wanting a subset of clients first for testing, but limiting that is not something I’ve scripted for.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.