Automating with PowerShell: Using the Secure Application model updates.

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. πŸ™‚

23 thoughts on “Automating with PowerShell: Using the Secure Application model updates.

  1. WilliamSeppen

    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~!

    Reply
  2. Jeremy Bradshaw

    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

    Reply
  3. Pingback: Documenting with PowerShell: Documenting Microsoft Teams - CyberDrain

  4. Paul

    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?

    Reply
      1. Paul Obrien

        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!

        Reply
      2. Paul Obrien

        Hi Kelvin, figured out a way last night to connect to MSOL directly.. variation on your partner center script.. will put together and publish

        Reply
  5. Sasa

    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?

    Reply
  6. Ryan

    $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?

    Reply
  7. Ramon Ayala

    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.

    Reply
  8. Terry

    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;

    Reply
  9. Joe

    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 ?

    Reply
      1. Joe Tahsin

        Thanks! got it fixed totally. I’ll publish soon an article by myself. Will reference to your blog for sure πŸ˜‰ !

        Reply
  10. Zach Frazier

    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?

    Reply

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.