0

Check If An Email Was Read using Graph API PowerShell SDK

With the growing number of people migrating from the Azure AD module to Microsoft Graph API, it’s great to see some features finally become available via the command line interface .aka. PowerShell. Today we’ll cover step by step on how to check if an email was read using Graph API PowerShell SDK.
 

Requirements

The use case may vary, sometimes you might want to check how many people read a recent communication that was sent by the organization. Other times you may need statistics for general read status. Whatever the case may be, it can be helpful to get this kind of data.
 

In order to set this up and check to see if a specific email was read or not, there are certain requirements that need to be put in place in order to successfully query a mailbox. Let’s cover those now.
 

  • Microsoft.Graph PowerShell Module
  • EXACT subject line of the email you’re searching for
  • An Azure App Registration setup with following API permissions
    • Directory.Read.All
    • Mail.ReadBasic.All

Mail Read Status Graph API Permissions
 

If you have never created an Azure App or need help getting started with Microsoft Graph API, please follow this guide on how to Connect To Microsoft Graph API Using PowerShell. It will take you from zero knowledge to getting everything up and running in no time.

How To Check If An Email Was Read using Graph API PowerShell SDK

Now that we have the Azure App Registration and Service Principal created with the above permissions, let’s look at how we can start getting message read status for our mail items.
 

Get-MSGraphMailReadStatus PowerShell Script

To give you some insight, this script uses Get-MgUserMessage graph cmdlet under the hood to get the actual message. There is also a parameter to see which folder the mail item is currently located. This helps if a user says they’ve lost an email, when in reality they’ve accidently dragged it into another folder without realizing it.
 

Function Get-MSGraphMailReadStatus {
<#
.SYNOPSIS
    Get Email read status for users using Graph Api. A session using Connect-Graph must be open as a requirement.

.NOTES
    Name: Get-MSGraphMailReadStatus
    Author: theSysadminChannel
    Version: 1.2

.LINK
    https://thesysadminchannel.com/how-to-check-if-an-email-was-read-using-graph-api-powershell-sdk/ -
#>

    [CmdletBinding()]
    param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 0
        )]
        [Alias('UserPrincipalName')]
        [string[]]  $UserId,


        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $Subject,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $SenderAddress,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $StartDate,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [string]  $EndDate,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [switch]  $ShowMailFolder,


        [Parameter(
            Mandatory = $false,
            ValueFromPipeline = $false,
            ValueFromPipelineByPropertyName = $false
        )]
        [int]  $Top = 1
    )

    BEGIN {
        $ConnectionGraph = Get-MgContext
        if (-not $ConnectionGraph) {
            Write-Error "Please connect to Microsoft Graph" -ErrorAction Stop
        }

        if ($PSBoundParameters.ContainsKey('StartDate') -and -not $PSBoundParameters.ContainsKey('EndDate')) {
            Write-Error "EndDate is required when a StartDate is entered" -ErrorAction Stop
        }


        if ($PSBoundParameters.ContainsKey('EndDate') -and -not $PSBoundParameters.ContainsKey('StartDate')) {
            Write-Error "StartDate is required when an EndDate is entered" -ErrorAction Stop
        }
    }

    PROCESS {
        foreach ($User in $UserId) {
            try {
                $GraphUser = Get-MgUser -UserId $User -ErrorAction Stop | select Id, DisplayName, UserPrincipalName
                Write-Verbose "Checking Status for $($GraphUser.DisplayName)"

                #Building the filter query
                $FilterQuery = "Subject eq '$Subject'"
                if ($PSBoundParameters.ContainsKey('SenderAddress')) {
                    $FilterQuery = $FilterQuery + " AND from/emailAddress/address eq '$SenderAddress'"
                }


                if ($PSBoundParameters.ContainsKey('StartDate') -and $PSBoundParameters.ContainsKey('EndDate')) {
                    $BeginDateFilter = (Get-Date $StartDate).AddDays(-1).ToString('yyyy-MM-dd') #Adding a day to either side to account for UTC time and MS operators.
                    $EndDateFilter = (Get-Date $EndDate).AddDays(1).ToString('yyyy-MM-dd')
                    $FilterQuery = $FilterQuery + " AND ReceivedDateTime ge $BeginDateFilter AND ReceivedDateTime le $EndDateFilter"
                }

                if ($Top -gt 10) {
                    #Since graph defaults to the first 10 in ascending order (oldest to newest) we have to do some tomfoolery to adjust it
                    $Message = Get-MgUserMessage -UserId $User -Filter $FilterQuery -Top $Top -Property Sender, toRecipients, Subject, IsRead, ReceivedDateTime, ParentFolderId | Sort-Object ReceivedDateTime -Descending
                  } else {
                    $Message = Get-MgUserMessage -UserId $User -Filter $FilterQuery           -Property Sender, toRecipients, Subject, IsRead, ReceivedDateTime, ParentFolderId | Sort-Object ReceivedDateTime -Descending | select -First $Top
                }

                #Building output object
                $Object = [Ordered]@{
                    MailboxDisplayName = $GraphUser.DisplayName
                    Mailbox            = $GraphUser.UserPrincipalName
                    UserId             = $GraphUser.Id
                    SenderAddress      = $null
                    #SenderName        = $null
                    RecipientAddress   = $null
                    #RecipientName     = $null
                    Subject            = $null
                    IsRead             = $null
                    ReceivedDate       = $null
                }

                if ($PSBoundParameters.ContainsKey('ShowMailFolder')) {
                    $Object.Add( 'MailFolder', $null )
                }

                if (-not $Message) {
                    Write-Verbose "0 Messages with the subject: '$Subject' were found on Mailbox: '$($GraphUser.DisplayName)'"

                    #Output object
                    [PSCustomObject]$Object

                } else {
                    Write-Verbose "$($Message.Count) Message(s) with the subject: '$Subject' were returned on Mailbox: '$($GraphUser.DisplayName)'"
                    foreach ($MailItem in $Message) {
                        $Object.SenderAddress      = $MailItem.sender.emailaddress | select -ExpandProperty Address
                        #$Object.SenderName        = $MailItem.sender.emailaddress | select -ExpandProperty Name
                        $Object.RecipientAddress   = $MailItem.toRecipients.emailaddress | select -ExpandProperty Address
                        #$Object.RecipientName     = $MailItem.toRecipients.emailaddress | select -ExpandProperty Name
                        $Object.Subject            = $MailItem.Subject
                        $Object.IsRead             = $MailItem.IsRead
                        $Object.ReceivedDate       = $MailItem.ReceivedDateTime.ToLocalTime()

                        if ($PSBoundParameters.ContainsKey('ShowMailFolder')) {
                            $MailFolder = Get-MgUserMailFolder -MailFolderId $MailItem.ParentFolderId -UserId $GraphUser.UserPrincipalName | select -ExpandProperty DisplayName

                            $Object.MailFolder = $MailFolder
                        }

                        [PSCustomObject]$Object

                        $Message    = $null
                        $MailItem   = $null
                        $MailFolder = $null
                    }
                }

            } catch {
                Write-Error $_.Exception.Message

            }

            #end foreach block
            $Object = $null
        }
    }

    END {}
}

Script Parameters

    -UserId

DataType: string/array
Description: Checks against this persons mailbox. Multiple UPNs/ObjectIds separated by a comma are acceptable.
 

    -Subject

DataType: string
Description: Queries the mailbox using the EXACT text specified
 

    -SenderAddress

DataType: string
Description: Specify the ‘From’ Address in your search. Format should be [email protected]
 

    -StartDate

DataType: string
Description: Specify the date when the query should start filtering for. Format should be MM/DD/YYYY
 

    -EndDate

DataType: string
Description: Specify the date when the query should end the filter. Format should be MM/DD/YYYY
 

    -ShowMailFolder

DataType: switch
Description: When this switch is used, it will display what folder the email is currently located in. This makes the overall query slower so use only when needed
 

    -Top

DataType: int
Description: Defaulted to 1. This is the limit of emails per mailbox that you would like to find
 

Example 1 – Specifying a user, a subject and the parent folder

PS C:\> Get-MSGraphMailReadStatus -UserId [email protected] `
-Subject "You’ve renewed your Office 365 E1 subscription" -ShowMailFolder

Get Mail Read Status Example 1

Conclusion

Hopefully this article was able to show you how to check if an email was read using Graph API PowerShell. Since this utilizes Graph API, it supports PowerShell 7 and can be incredible fast when using Foreach-Object -Parallel. I’ve used this several times in my environment and its great addition to my tool belt.

5/5 - (1 vote)

Paul

Hi, my name is Paul and I am a Sysadmin who enjoys working on various technologies from Microsoft, VMWare, Cisco and many others. Join me as I document my trials and tribulations of the daily grind of System Administration.

Leave a Reply

Your email address will not be published.