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:
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”
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
to
$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.
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 : kim.akers@office365itpros.com
Sender : stampingtool189@stampingtool.cc
Subject : metal stamping die manufacturer
SenderDomain : stampingtool.cc
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 : kim.akers@office365itpros.com
Sender : sales05.1871565@c.07hbkn.cn
Subject : Re:UL/CE air purifiers
SenderDomain : c.07hbkn.cn
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 : oisin.johnston@office365itpros.com
Sender : quote229@toolmolding.cc
Subject : plastic injection mould maker and moulding company
SenderDomain : toolmolding.cc
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
The issue you are facing is probably caused by using the default values for the Page and PageSize parameters of the Get-QuarantineMessage cmdlet:
-Page
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.
-PageSize
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):
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”
$today=(get-date)
$keydate=$today.Date.AddDays(-10)
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 emailcompliance@contuso.com
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
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
to
$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.
Have a look at the Get-QuarantineMessage documentation https://docs.microsoft.com/en-us/powershell/module/exchange/get-quarantinemessage?view=exchange-ps and see if playing with the StartExpiresDate and StartReceiveDate parameters (and their End equivalents) help you to focus in a date range.
@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
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 : kim.akers@office365itpros.com
Sender : stampingtool189@stampingtool.cc
Subject : metal stamping die manufacturer
SenderDomain : stampingtool.cc
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 : kim.akers@office365itpros.com
Sender : sales05.1871565@c.07hbkn.cn
Subject : Re:UL/CE air purifiers
SenderDomain : c.07hbkn.cn
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 : oisin.johnston@office365itpros.com
Sender : quote229@toolmolding.cc
Subject : plastic injection mould maker and moulding company
SenderDomain : toolmolding.cc
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
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
The issue you are facing is probably caused by using the default values for the Page and PageSize parameters of the Get-QuarantineMessage cmdlet:
-Page
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.
-PageSize
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
do
{
$CurrMessages = Get-QuarantineMessage -PageSize 1000 -Page $Page
$Page++
$Messages += $CurrMessages
}
until ($CurrMessages -eq $null)
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”
$today=(get-date)
$keydate=$today.Date.AddDays(-10)
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 emailcompliance@contuso.com
Regards,
Rob (NZ)
Thanks Rob!