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

29 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
  11. Harsh

    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?

    Reply
      1. Harsh

        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 ?

        Reply
  12. Iain

    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

    Reply
  13. Andrew

    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

    Reply
  14. Alex

    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?

    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.