Store tokens as PSCredential's in secure XML - JeremyTBradshaw/MSGraphPSEssentials GitHub Wiki
ℹ Version 0.2.0 of MSGraphPSEssentials introduced a new function - New-RefreshTokenCredential - and the existing function New-MSGraphAccessToken received a new parameter - -RefreshTokenCredential. These additions cater exactly to the task that is covered below.
You may be aware of the commonly accepted secure solution of storing account credentials in Windows using Get-Credential | Export-Clixml. Get-Credential builds a [System.Management.Automation.PSCredential] object which is made up of a plain text ([string]) UserName property, and a secure string ([securestring]) Password property.  Since PowerShell 3.0, the Export-Clixml cmdlet has been made to be aware of PSCredential objects and will use the Windows Data Protection API to encrypt the secure string password property within the XML file which is written to disk.
MSGraphPSEssentials' New-RefreshTokenCredential function allows us to easily store Azure AD refresh tokens the exact same way.  The function saves the Application ID to the UserName property of a new PSCredential object, and then bundles the entire token PS objects that are spit out from New-MSGraphAccessToken securely into the Password property.  If you haven't seen what these token objects look like, they look like this:
$TokenObject = New-MSGraphAccessToken -ApplicationId 18cb2c2f-d2a0-496e-a7a5-e7206b9d3798 -Scopes Mail.ReadWrite.Shared, Mail.Send.Shared, offline_access -Endpoint Organizations
# Take a peak inside:
$TokenObject [enter]
token_type     : Bearer
scope          : Sites.Manage.All profile openid email Mail.ReadWrite.Shared Mail.Send.Shared
expires_in     : 3599
ext_expires_in : 3599
access_token   : eyJ0eXAiOiJKV1Qi...< lots of characters >...5DJ6Pw
refresh_token  : 0.ASwAHZjO8NQxGk...< lots of characters >...ipaEQA
The goal: Store the Application ID and this $TokenObject's contents securely in a PSCredential object that we can then export securely using Export-Clixml.
The solution is actually pretty simple, so first let's look at how we can get this done manually (mostly). Since the Application ID is not sensitive, we can store this in the UserName property of a PSCredential. We just need to somehow get the tokens object into text form, make that secure, and then store that in the Password property of the PSCredential. Here's how to do that:
[PSCredential]::new(
    '18cb2c2f-d2a0-496e-a7a5-e7206b9d3798',
    (ConvertTo-Json $TokenObject | ConvertTo-SecureString -AsPlainText -Force)
) |
Export-Clixml AppCred_18cb2c2f-d2a0-496e-a7a5-e7206b9d3798.xml
And that's that, the Application ID as well as the entire token object are safely stored to disk, with the sensitive tokens encrypted, decryptable only by the current user, on the current system. Mission accomplished.
Now, on the other side of this, to re-import this for use later on in our scripts (e.g. after we've closed PowerShell, logged off, maybe even rebooted) we need to import the XML file, then convert the secure Password property back to plain text, and finally rehydrate it back into an object that we can use with the MSGraphPSEssentials functions.
For this, I'm going to share a tiny function that I already have posted as a Gist on GitHub (ConvertFrom-SecureStringToPlainText.ps1).
# Setting up the aforementioned function (and a nice and short alias for it):
function ConvertFrom-SecureStringToPlainText ([System.Security.SecureString]$SecureString) {
    [System.Runtime.InteropServices.Marshal]::PtrToStringAuto(
        [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
    )            
}
New-Alias -Name s2p -Value ConvertFrom-SecureStringToPlainText
# Now onto importing the XML:
$AppCred = Import-Clixml .\AppCred_18cb2c2f-d2a0-496e-a7a5-e7206b9d3798.xml
# We can perform the re-conversion on the fly while we get a new access token:
$NewTokenObject = New-MSGraphAccessToken -ApplicationId $AppCred.UserName -RefreshToken (s2p $AppCred.Password | ConvertFrom-Json)
New with v0.2.0 and higher
Now that the semi-manual approach has been covered, let's see how New-RefreshTokenCredential simplifies the process for us.  We'll start from the top:
$TokenObject = New-MSGraphAccessToken -ApplicationId 18cb2c2f-d2a0-496e-a7a5-e7206b9d3798 -Scopes Mail.ReadWrite.Shared, Mail.Send.Shared, offline_access -Endpoint Organizations
Next, let's pump the token object through our new function:
$RTCredential = New-RefreshTokenCredential -ApplicationId '18cb2c2f-d2a0-496e-a7a5-e7206b9d3798' -TokenObject $TokenObject
$RTCredential | Export-Clixml .\RefreshToken.xml
# Better yet!:
New-RefreshTokenCredential -ApplicationId '18cb2c2f-d2a0-496e-a7a5-e7206b9d3798' -TokenObject $TokenObject | Export-Clixml .\RefreshToken.xml
That's it!  Two lines (or even just 1).  Finally, the second enhancement with v0.2.0 is the new -RefreshTokenCredential parameter for the New-MSGraphAccessToken function, which allows us to supply one of our previously exported refresh tokens.  See it in action below:
$RTCredential = Import-Clixml .\RefreshToken.xml
$NewTokenObject = New-MSGraphAccessToken -RefreshTokenCredential $RTCredential
# Better yet, again:
$NewTokenObject = New-MSGraphAccessToken -RefreshTokenCredential (Import-Clixml .\RefreshToken.xml)
And that concludes this wiki sample. I hope you enjoy this added functionality. My next wiki entry will be one which covers scheduling tasks on Windows computers using the local SYSTEM account, with the help of the SysInternals PSExec.exe, and automating much of what MSGraphPSEssentials has to offer.