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. 🙂

31 Comments

  1. WilliamSeppen May 28, 2020 at 11:24 am

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

  2. Jeremy Bradshaw May 28, 2020 at 5:52 pm

    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

  3. Sasa June 3, 2020 at 7:09 am

    Hi Kelvin. Is there a way to use secure model to connect to MS Teams?

    1. Kelvin Tegelaar June 3, 2020 at 8:59 am

      The Teams Team is currently working on that, expected is September this year.

      1. Sasa June 3, 2020 at 10:09 am

        Great. Thanks

  4. Daniel Parker June 7, 2020 at 3:08 pm

    Hi Kelvin. Is there a way to use secure model to connect to SP Online?

  5. Pingback: Documenting with PowerShell: Documenting Microsoft Teams - CyberDrain

  6. Paul June 12, 2020 at 2:59 am

    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?

    1. Kelvin Tegelaar June 12, 2020 at 9:00 am

      No. MSOL and SFB have to have a partner app for this method to work.

      1. Paul Obrien June 14, 2020 at 10:35 pm

        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!

      2. Paul Obrien June 15, 2020 at 10:25 pm

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

  7. Sasa June 30, 2020 at 4:25 pm

    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?

  8. Ryan August 11, 2020 at 11:22 am

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

    1. Ryan August 11, 2020 at 11:33 am

      All good, found the comments on the other article explaning it.

  9. Ramon Ayala August 21, 2020 at 5:33 pm

    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.

  10. Terry September 10, 2020 at 9:07 am

    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;

    1. TERRY April 8, 2021 at 4:42 am

      Rectify, Security and Compliance Center session accepts -Tenant parameter in access token.

      $sccToken = New-PartnerAccessToken -ApplicationId ‘a0c73c16-a7e3-4564-9a95-2bdf47383716’ -Scopes ‘https://ps.compliance.protection.outlook.com/.default’ -UseAuthorizationCode -Tenant:$DirectoryId;

      But New-PSSession -ConnectionUri does not accept DelegatedOrg=TENANT_NAME.onmicrosoft.com query option.

      $authValue = ConvertTo-SecureString -String:(‘Bearer ‘ + $sccToken.AccessToken) -AsPlainText -Force;
      $cred = New-Object System.Management.Automation.PSCredential($sccToken.Account.Username,$authValue);
      $proxyOptions = New-PSSessionOption -ProxyAccessType:IEConfig;
      $newSession = New-PSSession -ConfigurationName:’Microsoft.Exchange’ -ConnectionUri:(‘https://ps.compliance.protection.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true’) -Credential:$cred -Authentication:Basic -AllowRedirection -SessionOption:$proxyOptions -ErrorAction:Continue;

  11. Joe September 19, 2020 at 11:43 am

    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 ?

    1. Kelvin Tegelaar September 19, 2020 at 11:45 am

      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.

      1. Joe Tahsin September 28, 2020 at 9:59 pm

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

  12. Zach Frazier October 6, 2020 at 11:14 pm

    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?

      1. Kurt Fearnley November 25, 2020 at 12:41 am

        Is there any way we can do an unattended refresh of these tokens?

  13. Harsh January 1, 2021 at 12:23 pm

    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?

    1. Kelvin Tegelaar January 1, 2021 at 1:50 pm

      To use the secure application model, you must be a Microsoft partner and have access to the partner center.

      1. Harsh January 2, 2021 at 9:17 am

        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 ?

  14. Iain February 9, 2021 at 1:37 pm

    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

  15. Andrew February 12, 2021 at 10:18 pm

    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

  16. Alex February 17, 2021 at 9:09 pm

    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?

  17. Tekwhat May 20, 2021 at 3:20 pm

    Do I have to accept this partner app on all of my customers to be able to run this, if so, how? I am getting this error when trying to set everyone’s audit log.

    New-PSSession : [nam12b.ps.compliance.protection.outlook.com] Connecting to remote server
    nam12b.ps.compliance.protection.outlook.com failed with the following error message : Access is denied. For more
    information, see the about_Remote_Troubleshooting Help topic.
    At H:\datacomm\redacted\Exchange_scripts\O365\Enable_Audit_Log_All_Teanets_automated.ps1:25 char:18
    + … ccSession = New-PSSession -ConfigurationName Microsoft.Exchange -Conn …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : OpenError: (System.Manageme….RemoteRunspace:RemoteRunspace) [New-PSSession], PSRemotin
    gTransportException
    + FullyQualifiedErrorId : AccessDenied,PSSessionOpenFailed
    Import-PSSession : Cannot validate argument on parameter ‘Session’. The argument is null. Provide a valid value for
    the argument, and then try running the command again.
    At H:\datacomm\redacted\Exchange_scripts\O365\Enable_Audit_Log_All_Teanets_automated.ps1:26 char:21
    + import-pssession $SccSession -disablenamechecking -allowclobber
    + ~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [Import-PSSession], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ImportPSSessionCommand

Leave a comment

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.