0
votes

I am writing a PowerShell script which reads through emails in an o365 email box.

It has to connect to the inbox, read through emails and make a decision based on the subject line, and then open those emails with a specific subject line and download any attachments the email might contain to a folder.

It [then] has to move the processed mail message to a mailbox sub-folder.

I have done this in the past using Exchange web services, and basic authentication to connect, but basic authentication is no longer available, and so I am having to re-do the script using modern authentication techniques.

I have been given an application id and secret from my Azure AD admin's with permissions for the relevant mailbox.

I've been googling away, and have managed to get some way using Microsoft Graph - using this section of Powershell:

$ClientID = "my-client-id"
$DirectoryID = "my-directory-id"
$ClientSecret = "my-client-secret"

$Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID

$mailbox = Get-EXOMailbox -UserPrincipalName [email protected]

This successfully gets me a mailbox object, but from here I am at a loss as to how to go about retrieving emails from it for processing.

The Microsoft doco and Google isn't helping me much at present on how extract emails from the mailbox having obtained the object.

Any thoughts/suggestions or pointers to relevant tutorials?

1
thanks Kiran, that's how you'd do it - unfortunately, Graph seems to require a digital certificate [that I don't have] rather than [simply] specifying an application id/secret [that I do have]... are there examples that allow you to connect using a application id/secret? thanks heaps, David - David
I recently used a managed identity on Azure Functions to send emails using Graph. The only permission I granted was "Mail.Send"(The only way to do this is via powershell). Didnt use any certificates. Check this out: docs.microsoft.com/en-us/azure/app-service/… - Kiran
Hi Kiran, thanks heaps for your pointers... you really helped me get on the path to a solution. See the final solution below. - David
nice :) .. The Microsoft.Graph SDk (powershellgallery.com/packages/Microsoft.Graph/1.6.1) wraps the REST API so if you use the module you could potentially avoid some of the boiler plate code but anyways as long as its working all is good. - Kiran

1 Answers

0
votes

I managed to figure out how to access emails just usiing application id and secret without using any non-standard libraries. You'll need to install the Graph and powershell utilities, but they're free from Microsoft, so I call them standard. Then import them into my powershell script thus:

Import-Module Microsoft.Graph.Mail
Import-Module Microsoft.PowerShell.Utility

After that, use these methods to access your o365 email system using the microsoft graph REST API.

# This AuthenticateWithSecret is a slightly modified version of the method 
# described by https://adamtheautomator.com/powershell-graph-api/
function Get-AccessToken
{
    param(
        [string] $AppId,
        [string] $TenantName,
        [string] $AppSecret)

    $Scope = "https://graph.microsoft.com/.default"
    $Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"

    # Add System.Web for urlencode
    Add-Type -AssemblyName System.Web

    # Create body
    $Body = @{
        client_id = $AppId
        client_secret = $AppSecret
        scope = $Scope
        grant_type = 'client_credentials'
    }

    # Splat the parameters for Invoke-Restmethod for cleaner code
    $PostSplat = @{
        ContentType = 'application/x-www-form-urlencoded'
        Method = 'POST'
        # Create string by joining bodylist with '&'
        Body = $Body
        Uri = $Url
    }

    # Request the token!
    $Request = Invoke-RestMethod @PostSplat
    return $Request
}


# This method builds a  header object that can be passed
# in with each request to the Graph REST API, for authentication
# purposes
function Get-Header
{
    param($theRequest)

    $tokenType = $theRequest.token_type
    $accessToken = $theRequest.access_token

    # Create header
    $theHeader = @{
        Authorization = "$($tokenType) $($accessToken)"
    }
    return $theHeader
}


# This method gets an object containing the email folders from
# a mailbox specified by its UserPrincipalName - which is typically
# the email address of the user concerned.  By default it will return
# the first 200 folders it finds, but you can specify however many
# that you wish to return using the $numberOfFoldersToGet parameter.
function Get-Folders
{
    param(
        $Credential,
        [string] $UserPrincipalName,
        [int]$numberOfFoldersToGet=200)

    $Header = Get-Header -theRequest $Credential
    $restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/?`$top=$numberOfFoldersToGet"
    $folderResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json" 
    return $folderResult
}


# This is a little helper function to get the specific folder object
# from an array of folder objects.  You specify the folder that you 
# want in the array based o its displayName using the $folderNameToFind
# parameter.
function Get-FolderFromListOfFolders
{
    param($listOfFolders,
          $folderNameToFind)

    $specificFolder = ""

    # Yeah, yeah, I know - we're doing this the brute-force way - just 
    # looping through all the folders until we find the one we want.  
    # Unless you have an insane number of folders, this shouldn't take 
    # *that* long, but this loop could be re-written to use a nicer search
    # if it's really that big a problem.

    foreach($fdr in $allFolders)
    {
        $thisFolderName = $fdr.displayName
        if($thisFolderName -eq $folderNameToFind)
        {
            $specificFolder = $fdr
            break
        }
    }
    return $specificFolder
}


# This function allows you to retrieve an object describing a specific
# mail folder in an o365 Outlook, which you can specify by name.  It allows
# you to access any folder by name - not just the common ones.
function Get-SpecificFolder
{
    param(
        $Credential,
        [string] $UserPrincipalName,
        [string] $folderName)

    $allTheFolders = Get-Folders -Credential $Credential -UserPrincipalName $UserPrincipalName
    $allFolders = $allTheFolders.value
    $specificFolder = Get-FolderFromListOfFolders -listOfFolders $allFolders -folderNameToFind $folderName
    $folderId = $specificFolder.id

    $Header = Get-Header -theRequest $Credential
    $theRestQuery = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId"
    $folderResult = Invoke-RestMethod -Uri $theRestQuery -Headers $Header -Method Get -ContentType "application/json" 
    return $folderResult
}


# This function returns an object containing all the emails in a given 
# Mail folder in Outlook in o365
function GetEmails
{
    param(
        $Credential,
        [string] $UserPrincipalName,
        [string] $folderId)

    $Header = Get-Header -theRequest $Credential
    $restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId/messages"
    $emailResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
    return $emailResult
}

You can use these methods in this way. First, you need to specify these variables

$ClientID = "My-azure-ad-client-id"
$DirectoryID = "My-directory-id"
$ClientSecret = "My-client-secret"
$MailboxName = "the-email-address-of-the-o365-mailbox-i-want-to-access"
$MailboxFolderName = "the-folder-name-of-the-mailfolder-containing-emails"

Then, you can get a credential object thus:

$Credential = Get-AccessToken -AppId  $ClientID -TenantName $DirectoryID -AppSecret $ClientSecret

Then get your email folder object thus

$myfolder = Get-SpecificFolder -Credential $Credential -UserPrincipalName $MailboxName -folderName $MailboxFolderName

Then, get the id of your folder - this allows you to access any folder - even non-standard ones by name.

$folderId = $myfolder.id

Now, get the email objects from the folder

$emails = GetEmails -Credential $Credential -UserPrincipalName $MailboxName -folderId $folderId

Then get the actual array of emails

$theEmails = $emails.value

Now loop through your array of emails and do stuff with it.

foreach($email in $theEmails)
{
    Write-Output $email.subject
}