I have a feeling I might be giving people a blogging overdose, but I’ve been playing with so much cool stuff the last couple of days. So lets get the ball rolling; I’ve finally found a method to connect to the SCC succesfully using the Secure Application model.
I’ve also found that non-partners can use the Secure Application Model too, for Exchange and the SCC. This is thanks to some people in the PowerShell Discord that inspired me to get to coding. π
First off, partners can still use the older blog here to use the secure application model. This script is targeted to Microsoft Partners.
So, what exactly is the Secure Application Model?
The Secure application model is a method of connecting to Office365 services by using oauth instead of a regular username/password combination. By using oauth you use tokens instead. These tokens have a specific life-time and are revoked if they are not used.
The great benefit it gives is that you can run headless scripts, while still having MFA enabled. You won’t need to authenticate with MFA each time the script runs.
Non-Microsoft partners
To get started, you’ll need to give consent to the Exchange Application for your tenant. Microsoft uses the well-known-application-id “a0c73c16-a7e3-4564-9a95-2bdf47383716” for this. You can give consent by executing the following code:
$Exchangetoken = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -Scopes 'https://outlook.office365.com/.default' -Tenant $TenantID -UseDeviceAuthentication write-host "Exchange Token: $($ExchangeToken.RefreshToken)"
After this, you should store this token somewhere safe like an Azure Keyvault or password manager. With this token we can start connecting to resources.
Connecting to the Security center can be done with the following code. If you want to connect to both Exchange, and the Security center you will have to use a different token for each, as the token gets invalidated after use by one of the applications.
$ExchangeRefreshToken = 'ExchangeRefreshToken' $UPN = "Exisiting-UPN-with-Permissions" $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $SccSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true&DelegatedOrg=$($customerId)" -Credential $credential -AllowRedirection -Authentication Basic import-session $SccSession -disablenamechecking -allowclobber
To connect to Exchange, you can use this code.
$ExchangeRefreshToken = 'ExchangeRefreshToken' $UPN = "Exisiting-UPN-with-Partner-Permissions" $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $Exchangetoken -Scopes 'https://outlook.office365.com/.default' $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection Import-PSSession $session -AllowClobber -DisableNameChecking
And that’s it for non partners. With this you can run unattended, headless scripts for O365 as a non-microsoft partner. You will need to install the PartnerCenter module to create the authentication tokens.
Partner Methods
After you collect your tokens, you can use this method to connect to the Security Center using the Secure Application Model for partners. This allows your to connect to each tenant under your administration.
$ApplicationId = 'YourApplicationID' $ApplicationSecret = 'ApplicationSecret' | Convertto-SecureString -AsPlainText -Force $TenantID = 'Your-tenantID' $RefreshToken = 'RefreshToken' $ExchangeRefreshToken = 'ExchangeRefreshToken' $UPN = "Exisiting-UPN-with-Partner-Permissions" $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){ $customerId = $customer.DefaultDomainName $token = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' $tokenValue = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $SccSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true&DelegatedOrg=$($customerId)" -Credential $credential -AllowRedirection -Authentication Basic import-pssession $SccSession -disablenamechecking -allowclobber #YourCommands here #/End of Commands }
The rest of the partner methods remain the same, unless you want to connect to both the Security center and Exchange at the same time, then use the following code.
$ApplicationId = 'YourApplicationID' $ApplicationSecret = 'ApplicationSecret' | Convertto-SecureString -AsPlainText -Force $TenantID = 'Your-tenantID' $RefreshToken = 'RefreshToken' $ExchangeRefreshToken = 'ExchangeRefreshToken' $UPN = "Exisiting-UPN-with-Partner-Permissions" $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) { $customerId = $customer.DefaultDomainName write-host "Connecting to the Security Center for client $($customer.name)" $SCCToken = New-PartnerAccessToken -ApplicationId 'a0c73c16-a7e3-4564-9a95-2bdf47383716'-RefreshToken $ExchangeRefreshToken -Scopes 'https://outlook.office365.com/.default' $SCCTokenValue = ConvertTo-SecureString "Bearer $($SCCToken.AccessToken)" -AsPlainText -Force $SCCcredential = New-Object System.Management.Automation.PSCredential($upn, $SCCTokenValue) $SccSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true&DelegatedOrg=$($customerId)" -Credential $SCCcredential -AllowRedirection -Authentication Basic import-session $SccSession -disablenamechecking -allowclobber #YourCommands here #/End of Commands Remove-session $SccSession write-host "Connecting to the Exchange managed console for client $($customer.name)" Write-host "Enabling all settings for $($Customer.Name)" -ForegroundColor Green $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 $credentialExchange = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($customerId)&BasicAuthToOAuthConversion=true" -Credential $credentialExchange -Authentication Basic -AllowRedirection -erroraction Stop Import-PSSession -Session $ExchangeOnlineSession -AllowClobber -DisableNameChecking #YourCommands here #/End of Commands Remove-PSSession $ExchangeOnlineSession }
And that’s it! I hope that helps partners and non-partners alike. As always, Happy PowerShelling. π
Genius~! Thank you so much, I am not a partner but this works perfect. You are the most valued player of managed services providers guy! the MVP for MSPs~!
Hey, thanks for this post and the notification email that I got. I also want to share this doozy!
https://github.com/MicrosoftDocs/office-docs-powershell/blob/master/exchange/docs-conceptual/exchange-online/exchange-online-powershell-v2/app-only-auth-powershell-v2.md
I suppose, it’s not for partners, but instead just straight EXO customers. But nice to know cert. authentication with App Registrations is coming to EXO. I have a mini PS module ready to go for when the time comes:
https://github.com/JeremyTBradshaw/PowerShell/blob/master/.Modules/msGraphFunctions.psm1
Hi Kelvin. Is there a way to use secure model to connect to MS Teams?
The Teams Team is currently working on that, expected is September this year.
Great. Thanks
Hi Kelvin. Is there a way to use secure model to connect to SP Online?
Pingback: Documenting with PowerShell: Documenting Microsoft Teams - CyberDrain
Excuse my ignorance, but can you connect-msolservice and skypeforbusiness online using this non-partner method too? is there a well-known app just to get a basic msol connection?
No. MSOL and SFB have to have a partner app for this method to work.
Thanks for the reply Kelvin. Do you know of any way where you can register an app against a tenant directly (instead of Partner Center) and use this as a ‘persistent’ connection to MSOL and SFB? With Modern Auth, trying to run scheduled scripted processes against these things is not easy!
Hi Kelvin, figured out a way last night to connect to MSOL directly.. variation on your partner center script.. will put together and publish
Hi Kelvin,
I would appreciate it if you have any idea with this…
I run first part of the script ($aplication ID, $refresh token etc.)
Then foreach ($customer in $customers){
.. I connect to client’s Azure
.. I connect to Client’s Exchange
My commands here and all goes well without any issue for the first client
}
When script goes to a second client, I get this error.
“New-PartnerAccessToken : Error: ClientId is not a Guid.”
Do you have any clue why is that?
$token = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’-RefreshToken $ExchangeRefreshToken -Scopes ‘https://outlook.office365.com/.default’
Is that generic MS ApplicationID required if we have set our own up with the other secure access script? Do i swap out for our App ID that was generated?
All good, found the comments on the other article explaning it.
Having some difficulty with the Security and Compliance Center for clients. For some, but very few, it can connect just fine to their environment. But for the majority I get Access Denied Error when doing the New-PSSession. I can’t seem to figure out why some work and others don’t.
Thank you for your access token method sharing!
I also encounter Security and Compliance Center connection problem by your code.
Finally, I found Security and Compliance Center session does not accept -Tenant parameter in access token.
You can create a new compliance access token by -UseAuthorizationCode, e.g.
$sccToken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://ps.compliance.protection.outlook.com/.default’ -UseAuthorizationCode;
Or you can exchange a compliance access token by Exchange Online access token that without -Tenant parameter, e.g.
$exoToken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://outlook.office365.com/.default’ -UseAuthorizationCode;
$sccToken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://ps.compliance.protection.outlook.com/.default’ -RefreshToken $exoToken.RefreshToken;
Hi,
great article, im almost there, im aable to create the CSP partner token on the csp tenant, but when i try to use that token(refreshtoken) to create a customer specific token then i get the following error :
New-PartnerAccessToken : AADSTS50020: User account ‘{EmailHidden}’ from identity provider
‘https://sts.windows.net/mytentantID/’ does not exist in tenant ‘customerTenant’ and cannot access the
application ‘applicationid'(spa-app01) in that tenant. The account needs to be added as an
external user in the tenant first. Sign out and sign in again with a different Azure Active Directory user account.
Timestamp: 2020-09-19 09:39:46Z
What am i doing wrong here ?
You either have a guest account in the same tenant that needs to be removed, or you have not given all consents during the creation of your application.
Thanks! got it fixed totally. I’ll publish soon an article by myself. Will reference to your blog for sure π !
Hey Kelvin,
I just noticed my refresh token expired and now the scripts aren’t running. I’m having a hard time finding how to generate a new refresh token. Did I do something wrong? I read and saw that the refresh token is good for 90 days and I see that in my return value below
[4:50:35 PM] [ERR] AADSTS700082: The refresh token has expired due to inactivity.Β The token was issued on 2020-06-30T04:14:01.1743396Z and was inactive for 90.00:00:00.
Trace ID: ca22c8fc-9f6d-4c7d-a5eb-4fd360af2000
Does this mean the script wasn’t running for 90 days and it expired or is this the 90 day expiry regardless of use. If the later, how do i fix that?
At the bottom of this blog: https://www.cyberdrain.com/connect-to-exchange-online-automated-when-mfa-is-enabled-using-the-secureapp-model/
You’ll find a script to refresh the tokens.
Is there any way we can do an unattended refresh of these tokens?
Yes! Check out https://www.cyberdrain.com/connect-to-exchange-online-automated-when-mfa-is-enabled-using-the-secureapp-model/. All the way at the bottom it has a script for refreshing the tokens, using the ones you’ve generated prior.
Hi Kelvin,
Great script for secure login, I have global admin access on my tenant. I want to automate user license assignment and there creating this for MSOLService.
Did 1st part of the script. in 2nd part, it asked me for the consent and in modern auth, I provided my global admin creds.. and It gave error
Authentication failed. You can return to the application. Feel free to close this browser tab.
Error details: error invalid_client error_description: AADSTS650052: The app needs access to a service (\”https://api.partnercenter.microsoft.com\”) that your organization \”xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\” has not subscribed to or enabled. Contact your IT Admin to review the configuration of your service subscriptions. Trace ID: 426275a1-cf82-4220-9538-9733b15c5200 Correlation ID: 761b75c3-e3d6-4dc8-93f4-d81952a4f636 Timestamp: 2021-01-01 10:58:08Z
Can you please help?
To use the secure application model, you must be a Microsoft partner and have access to the partner center.
Hi Kelvin. How to do that? and will it cost us?
And if Microsoft partner is not option for us, Do you any way to run connect-msolservice unattended ?
I’m looking to run Connect-MsolService for my own tenant, I have implemented MSAL and can generate tokens, is it possible to use these token to create an MsolService session? Thanks
I am trying to connect to the Windows Graph API using my Partner Center. Looking at the code, you make a reference to $RefreshToken but I do not know where to get this value from. I have all the other values. What do I need to do to generate the $RefreshToken value? Thanks.
$ApplicationId = ‘YourApplicationID’
$ApplicationSecret = ‘ApplicationSecret’ | Convertto-SecureString -AsPlainText -Force
$TenantID = ‘Your-tenantID’
$RefreshToken = ‘RefreshToken’
$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
I used the command: $Exchangetoken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://outlook.office365.com/.default’ -Tenant $TenantID -UseDeviceAuthentication.
I got access token and other values, but no refresh token…. the refresh token is just empty. Someone has any idea what caused that? I was suspecting it could be the scope issue? Or is there something I need to configure on the Exchange side in order to get the refresh token?