Exchange Online and the Secure App Model
Am I missing something in that I cannot see a way to implement it. A lot of my automation relies on the ability to see things like transport rules and Exchange configuration settings that come out of the Exchange Online Powershell cmdlets.
Is this stuff exposed in the Graph API or ANY API? I can't see it anywhere. Are there plans to update the Exchange Online Powershell Module to support the Secure App Model before 1st of August?
I think the main issue is that you're using -TenantId $TenantID (which is probably your TenantID) rather than -TenantId <someClientTenantID>. You need to use your tenant ID when connecting to your Azure AD, and client TenantID when connecting to a client's AzureAD (with all new AAD and Graph Tokens for the Tenant).
I have converted over all my client/delegatedAdmin Azure AD + Exchange Online scripts and they're all working unattended just fine now. Here's what I did (maybe a little verbose, I know you have Msol and ExO working, but for the benefit of anyone else...)
I used @KelvinTegelaar 's version of Create-AzureADApplication.ps1 (which is itself a modified version of @idwilliams 's script to add the Exchange Online stuff and always Consent). I did the Consenting with my account, which is a Partner Center Admin - you need it to be MFA enabled and have to Consent a few times with the script.
NOTE: The Azure AD App Redirect URI of "urn:ietf:wg:oauth:2.0:oob" will be added as type = Web, it needs to be changed to "Public client/native (mobile & desktop)" manually.
At the end of the script I got several values, which I've stored securely (we use PasswordState as it has an API, but Azure Vault would also work). The RefreshTokens are only good for 90 days, so you'll need to have a process of "using" them to get an AccessToken (which also returns a renewed RefreshToken) and storing that new RefreshToken - I do it weekly. I have mine saved as:
$ApplicationId = 'e7282f0e-my-azure-ad-app-id-e7e6a479894e' $ApplicationSecret = 'XF+zHGx+super+secret+key+LNPiyUkylk=' | Convertto-SecureString -AsPlainText -Force # I use my domain name Tenant Domain: $TenantID = 'my-msp-tenant-domain.ca' # This format also works, and is what the script delivers $TenantID2 = '6169ca06-my-tenat-guid-id-69acca8d2bb6' # This one is NOT output, but you need it - it's the MFA-enabled Account you Consented with in the script:
# This is my email address / O365 UPN: $Upn = 'email@example.com' # These are actually really, really long, no spaces: $RefreshToken = 'OAQABAAAAAACQN9=big=long=token=all=as=one=line=RoDlwt1oD-tW-mxRAbyAA' $ExchangeRefreshToken = 'OAQABAAA=DIFFERENT=big=long=token=all=as=one=line=O7Cm3-8gPF4l15IccyAA'
For Azure AD you need to get the AccessTokens twice - once to connect to your/company/MSP Azure AD to get all the Clients/Customers - well their TenantIDs, and then to each Customer (I suppose your could save them, but your client list could change):
# CSP Partner connection:
$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 $Upn -MsAccessToken $graphToken.AccessToken -TenantId $tenantID
# Find all your Customers/Clients, pick the last one: $Customers = Get-AzureADContract -All $True | select DefaultDomainName, CustomerContextID $Customer = $Customers[-1] $Customer
# Disconnect from your AzureAD Disconnect-AzureAD # Now connect to that Customer, uses the same $credential as above, but the -TenantID is of the client:
# These are Customer Specific Tokens: $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 # now connect to the CLIENT's AzureAD Connect-AzureAD -AadAccessToken $CustAadGraphToken.AccessToken -AccountId $upn -MsAccessToken $CustGraphToken.AccessToken -TenantId $customer.CustomerContextId
# Now you can work on the customer's Azure AD Get-AzureADDomain Get-AzureADUser | select -first 10 # Then: Disconnect-AzureAD when done with each Customer, unless also connecting to ExchOnline
NOTE: You use the same $credential, but for CSP Partner (your Azure AD) you connect using your $tenantID, you can then Get-AzureADContract and see all your customers. Then Disconnect, and re-connect using the Customer's TenantID getting all NEW AccessTokens. You need to connect/disconnect using Customer Specific Tokens for each customer.
I found this whole process took longer than the old process, so your scripts will take longer. Also: AccessTokens are only good for 60 mins, so if you have a long running process for a client (eg. doing something on every User or Mailbox for a client) you may need to disconnect and re-connect when you're near 50 mins (I use a connection timer and check it at the start of the foreach ($mbx in $AllMailboxes) loop).
If you have any issues or still can't make it work, let me know and I'll see if I can help.
For the benefit of anyone else, here's how you would connect to Exchange Online PowerShell:
# Customer: $ExchToken = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' -Tenant $customer.CustomerContextId $tokenValue = ConvertTo-SecureString "Bearer $($ExchToken.AccessToken)" -AsPlainText -Force $ExchCredential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($Customer.DefaultDomainName)&BasicAuthToOAuthConversion=true" -Credential $Exchcredential -Authentication Basic -AllowRedirection Import-PSSession $session Get-AcceptedDomain get-mailbox | select -first 10 # After each Customer: Remove-PSSession $Session Disconnect-AzureAD
It assumes you have already connected to your MSP Azure AD to get a list of all Customers' TenantIDs and then just want to connect to each Customer's Exch Online. It has its own Exch specific $cred and $refreshToken.
And for completeness, MSOnline / Msol:
# CSP Partner: $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 | select DefaultDomainName, TenantId $Customer = $Customers[-1] $Customer # Customer: Get-MsolDomain -TenantId $Customer.TenantId Get-MsolUser -MaxResults 10 -TenantId $Customer.TenantId # No disconnect
The $credential is the same as Azure AD. You can also use the list of Customers from your MSP, but you don't need to specifically connect to each Customer, but you DO need to add -TenantID to each *-Msol* command. There's no disconnect for Msol that I can find.
NOTE: Msol's $Customer.TenantId and AzureAD's $customer.CustomerContextId are the same.
I hope that helps someone else too! Let me know if it works or not.
@sansbacher - such a comprehensive answer. Truly appreciate the effort you have put in to that.
The TenantID I am using is mine - I'm not even attempting to get to my Client's AzureAD at the moment as when I run Get-AzureADContract it doesn't work:
Get-AzureADContract : Error occurred while executing GetContracts
Message: Insufficient privileges to complete the operation.
I thought you had something with the Azure AD App Redirect URI and changing it to Public client/native (mobile & desktop)" manually. I did that but it made no difference. I am a Partner Center Admin, with MFA enabled. The consent process seems to go fine. As mentioned previously the MSOL and Exchange stuff works fine.
From the error it does look like a permission problem of some sort but no idea where to go from here.
I've attached the API Permissions in App Registrations for the created App. Could you compare them to yours and see if there's a difference?
Really appreciate the help here.
OK, so a follow up on this.
Looking in the screenshot on my previous post there is something not quite right with the consent process.
Clicking the button "Grant admin consent for My Company" changed all the statuses to green and then AzureAD started working.
Now to try figure out why that consent process is not working, because it does pop-up and the consent clearly goes through for the stuff that DOESN'T require admin consent.
[Strange I tried to reply, I thought I did. I said my permissions match yours, except all green/consented. And I tried to attach the slightly modified version of Kelvin's script I used, as .TXT, in case you wanted to try that. But my reply seems to have never shown up!]
That one worked, right away! I will try with the attachment again...
That worked! I dunno what happened. Anyway, in brief - from what I recall writing:
Glad to have helped, glad it's now workking.
My permissions for the Azure AD Native App look the same, except for the extra Graph permissions you have below.
I had to do several Consents with Kelvin's script, but you only need to create the App once, you can add new secrets when they expire so I wouldn't stress too much - IF yours is running.
Attached is the slightly edited version of Kelvin's script I ran, which mostly just has some notes as I was curious what changes he made. You could try that and see?
--Saul [editing a reply, let's see if this works!]
@Gavsto : Unfortunately currently I won't have sufficient time to test this and give detailed guidance - for now I can only refer to the documentation and exaples on how to work with the access tokens.
There is the option you raise an advisory request to get 1:1 guidance from a Partner Consutlant that is more experienced in Exchange Online: firstname.lastname@example.org or use this link to raise a request Online.
I have to say that Microsoft's response to these changes, especially related to Exchange Online has been absolutely inadequate and sub-standard.
I'm left in a position now where our clients are actually in a WORSE position with the implementation of these new security measures because none of our custom security monitoring works any more.
You come back with half-baked, hacked together bypasses that go directly against your own security guidance on how you are going to be enforcing these security standards.
It's disgraceful and Microsoft should be ashamed of themselves. They've had YEARS now to upgrade the Exchange Online module.