Analyzing Quarantined Messages with PowerShell

Exchange Online Protection Puts Problem Messages into Quarantine

In a previous post, we cover the basics of reviewing email quarantined by Exchange Online Protection using the Security and Compliance Center. As discussed there, it’s important to review quarantined email to understand if any messages which shouldn’t be blocked are trapped there waiting for release. No one wants to have an important message expire in the quarantine (after 15 days by default) and not get to its intended recipient.

The problem is the time needed to review quarantined messages for a busy tenant. Scrolling up and down a large list to decide whether to release messages can consume hours, especially if you don’t allow users to release quarantined email.

PowerShell Can Help

Exchange Online includes several cmdlets to work with quarantined messages. It might be easier to run a daily job to grab details of what’s waiting in the quarantine, do some basic analysis, and create a CSV file of the messages that can be reviewed. Any messages that shouldn’t be released can be removed from the file, and the remainder released for delivery.

Scripting Quarantine Analysis

I created a script (downloadable from GitHub) to illustrate the principal. The script fetches details of messages in quarantine using the Get-QuarantineMessage cmdlet and populates a PowerShell list with details of each message. You could use the output of Get-QuarantineMessage directly, but this approach allows for some additional processing of each message, such as extracting its source domain and calculating how long more it will remain in quarantine.

We then use the list to do some basic analysis to find out why messages are being quarantined, who’s receiving these messages, and where the messages come from:

$Report | Group Type | Sort Count -Descending | Format-Table Name, Count
Type of Quarantined messages

Count Name
----- ----
   10 Spam
    5 Phish
    3 HighConfPhish
    1 Malware

Messages quarantined per recipient address

Finally, we export the messages to a CSV file. The intention here is that someone can review the list of messages and decide which to release for onward delivery. All other lines in the CSV file are removed. To release the messages, we can then import message details from the CSV and use the Release-QuarantineMessage cmdlet to release them:

Import-CSV c:\temp\QuarantinedMessages.csv | Release-QuarantineMessage -ReleaseToAll

It’s all very straightforward PowerShell so you can customize it to add whatever idea you think is valuable. For instance, you could email the CSV file to reviewers.

Simple ideas can be the best. And applying PowerShell to solve problems is a simple idea that works well in lots of places within Office 365. Which is why the Office 365 for IT Pros eBook includes so many examples of PowerShell in action.

8 Replies to “Analyzing Quarantined Messages with PowerShell”

  1. Hi Great script, I have 1 question. This seems to pull only the current dates quarantined emails. I tried changing the line $Report = [System.Collections.Generic.List[Object]]::new(); $Now = Get-Date


    $Report = [System.Collections.Generic.List[Object]]::new(); $Now = Get-Date.AddDays(-14) but it errors out.

    How would I get the report to pull all quarantined messages.

  2. @Tony Redmond – this functionality is broken. No matter what -start -end you specify the cmdlet returns only last “page” of messages i.e last 24 hours

    1. It works for me… The script uses a straightforward Get-QuarantineMessage without any parameters, so it fetches all messages. You can filter them as necessary.

      Identity : ef03b4a8-318e-42a0-f0ce-08d8f6d0a91d\e28b5d1a-e60e-5bf0-e8b2-95ea020a925c
      Received : 03/04/2021 19:45
      Recipient :
      Sender :
      Subject : metal stamping die manufacturer
      SenderDomain :
      Type : Spam
      Expires : 18/04/2021 01:00
      Time Remaining : 3 days 3 hours

      Identity : a15bbe1d-3282-4f07-03c2-08d8f421944c\ef711b12-9492-a07d-357b-46f2f355d667
      Received : 31/03/2021 09:47
      Recipient :
      Sender :
      Subject : Re:UL/CE air purifiers
      SenderDomain :
      Type : Spam
      Expires : 15/04/2021 01:00
      Time Remaining : 0 days 3 hours

      Identity : 57c6b61e-4239-4a72-e26b-08d8f3fedf51\a6d19d71-c1a4-79dd-825e-39144c7ec012
      Received : 31/03/2021 05:38
      Recipient :
      Sender :
      Subject : plastic injection mould maker and moulding company
      SenderDomain :
      Type : Spam
      Expires : 15/04/2021 01:00
      Time Remaining : 0 days 3 hours

      Or you can use the parameters supported for date filtering. For instance, I did this and it worked just fine. What issue are you seeing?

      Get-QuarantineMessage -StartReceivedDate “1-apr-2021” -EndReceivedDate “7-Apr-2021” | ft receivedtime

      06/04/2021 20:56:21
      06/04/2021 12:21:00
      04/04/2021 22:32:58
      03/04/2021 19:45:32

    2. The issue you are facing is probably caused by using the default values for the Page and PageSize parameters of the Get-QuarantineMessage cmdlet:

      The Page parameter specifies the page number of the results you want to view. Valid input for this parameter is an integer between 1 and 1000. The default value is 1.


      The PageSize parameter specifies the maximum number of entries per page. Valid input for this parameter is an integer between 1 and 1000. The default value is 100.

      To get all the messages (well, maximum 1000*1000 of them) you could use something like this (or replace the simple ‘parameterless’, Get-QuarantineMessage cmdlet in Tony’s script):

      $Messages = $null
      $Page = 1
      $CurrMessages = Get-QuarantineMessage -PageSize 1000 -Page $Page
      $Messages += $CurrMessages
      until ($CurrMessages -eq $null)

  3. Hi Tony – thanks for posting this article – it has helped me develop something similar that can run daily to release quarantine emails to a shared mailbox – that are due to expire. This for a customer who has this as a compliance requirement. Very frustrating that this option is not provided by 365. My code is based on this central function – happy to share and also happy for feedback!

    write-host “Set Date variables”
    write-host “get quarantine emails older than 10 days that are not released”
    $q = Get-QuarantineMessage | Where-Object {($_.receivedtime -ge $keydate) -and ($_.ReleaseStatus -ne ‘RELEASED’)}
    $q | release-quarantinemessage -user

    Rob (NZ)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.