Remote Exchange Monitoring and Reporting using Email (Part 2)

If you would like to read the first part in this articile series please go to Remote Exchange Monitoring and Reporting using Email (Part 1).

Introduction

In the first part of this article series, we developed a basic script to gather information about our Exchange infrastructure in situations where we would normally not be able to. However, using Message Tracking Logs we are a bit limited as we cannot look into the message body or attachments, only its subject. In this final part, we will develop a similar script but that uses Exchange Web Services (EWS) instead, further expanding the script’s capabilities.

Exchange Web Services

This second script will rely on Exchange Web Services (EWS) so it can be run from any non-Exchange machine as long as it has the Microsoft Exchange Web Services Managed API 2.2 installed. Similar to the first script, this one will look out for any emails arriving at a particular mailbox named monitoring. If it finds any, it will get the subject of the email, run it in the Exchange Management Shell (EMS) and compose a new email to the original sender with the output of the script/cmdlet.

For security reasons, the script will only run Get-* cmdlets, so no settings can be changed using this process. Obviously this can easily be changed to allow us to make changes to our Exchange environment remotely. However, I am certain the Security policies for most organizations would not allow this…

Additionally, we will only process emails that are sent from a particular sender ([email protected] in this case) to avoid any rogue users or hackers to gain unauthorized information about our environment.

While the first script searched the Message Tracking Logs every 15 minutes in search for new emails between these two users, this script will go through the monitoring mailbox’s Inbox folder, search for any of these emails, process them and then delete them.

First, we start by defining the parameters this script will use. We can specify the monitoring mailbox (in this case [email protected]) and the allowed sender ([email protected]). Depending on which mailbox you chose to read emails from, ensure you have the right permissions to access it!

Param (

       [Parameter(Position = 0, Mandatory =$False)]

       [String] $Mailbox=[email protected],

 

       [Parameter(Position = 1, Mandatory =$False)]

       [String] $Sender=[email protected]

)

Next, if we are not running this script from an Exchange server, we need to load the Exchange Web Services Managed API DLL (do not forget to update the path if you install it on a different location/folder):

$dllPath=“C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll”

[Void] [Reflection.Assembly]::LoadFile($dllPath)

Then we create a new object as an Exchange service and configure it to use AutoDiscover to find out how to connect to our monitoring mailbox. Here we are using Exchange2013_SP1 but it also works with Exchange 2016 RTM:

$service=New-ObjectMicrosoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)

$service.AutodiscoverUrl($Mailbox)

After this our $service object will look like this (some properties have not been included for shortness):

Url                          : https://mail.nunomota.pt/ews/exchange.asmx
TimeZone                     : (UTC) Dublin, Edinburgh, Lisbon, London
UnifiedMessaging             : Microsoft.Exchange.WebServices.Data.UnifiedMessaging
EnableScpLookup              : True
TraceEnablePrettyPrinting    : True
SendClientLatencies          : True
TraceEnabled                 : False
TraceFlags                   : All
TraceListener                : Microsoft.Exchange.WebServices.Data.EwsTraceListener
Credentials                  :
UseDefaultCredentials        : True
Timeout                      : 100000
PreAuthenticate              : False
AcceptGzipEncoding           : True
RequestedServerVersion       : Exchange2013_SP1
UserAgent                    : ExchangeServicesClient/15.01.0225.042
KeepAlive                    : True

We then create another object and use a constructor to link a folder ID to a well-known folder. The WellKnownFolderName property is applicable for clients that target Exchange and gets one of the common folder names such as Inbox, Contacts, DeletedItems, Outbox, MsgFolderRoot, PublicFoldersRoot, RecoverableItemsPurges, and many, many others. We also bind our service created earlier to this folder:

$rfRootFolderID=New-ObjectMicrosoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox, $Mailbox)

$rfRootFolder= [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $rfRootFolderID)

After this bind, our $rfRootFolder variable contains the following information (again, some properties have not been included for shortness) where we can see, for example, how many subfolders the Inbox has, how many emails (10) out of which 2 are unread, what permissions we have on this folder, and more:

Id                          : AQMkADQzNGZhZDc3AC1lNzgzLTRmODItOWQzMC1iODBiNTdlYTYx
ChildFolderCount            : 0
DisplayName                 : Inbox
FolderClass                 : IPF.Note
TotalCount                  : 10
EffectiveRights             : CreateAssociated, CreateContents, CreateHierarchy, Delete, Modify, Read, ViewPrivateItems
UnreadCount                 : 2

Next we define how many items we will be getting on each pass. As per the property above, we already know this folder only has 10 items, but to generalize and keep the script simple to understand, let us get 100 emails at a time:

$ivItemView=  New-ObjectMicrosoft.Exchange.WebServices.Data.ItemView(100)

By default, when using EWS to read email properties, some properties are not returned such as the email body. Also, any internal sender will be displayed as:

/O=NUNOMOTA/OU=EXCHANGE ADMINISTRATIVE GROUP (FYDIBOHF23SPDLT)/CN=RECIPIENTS/CN=NUNO

Instead of [email protected] for example. To overcome this, we need to load what is known as FirstClassProperties for the emails we will be processing.

The set of first-class properties and elements that are returned by the EWS Managed API EmailMessage.Bind method and the EWS GetItem operation is slightly different than the set of first-class properties and elements that is returned by the EWS Managed API ExchangeService.FindItems method and the EWS FindItem operation which I will be using in this script. The first-class properties returned by the FindItems method and FindItem operation are a subset of the properties returned by the Bind method and GetItem operation. Luckily for us, the Sender and From properties are amongst them.

$psPropSet=New-ObjectMicrosoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

Now we are ready to start analyzing emails that are present in the Inbox folder. So, first we get the first 100 emails (as per the $ivItemView defined earlier) and load the FirstClassProperties for each one of them:

Do {

       $fiItems=$service.FindItems($rfRootFolder.Id, $ivItemView)

       [Void] $service.LoadPropertiesForItems($fiItems, $psPropSet)

Next, we go through every one of those emails and check if the sender is the one we specified in the script’s parameters at the beginning. If it is, we call the same runCmdlet function and pass it the email’s subject (which is the cmdlet we want to run):

       ForEach ($emailin$fiItems.Items) {

              If ($email.Sender.Address -eq$Sender) {

                     runCmdlet$email.Subject

The runCmdlet function is identical to the one used in the first script, so I will not cover it again.

Once the function runs the cmdlet and sends the email to the original sender, we delete the email so the next time the script goes through the Inbox folder, it does not process the same email again. The next method will move the email to the Deleted Items folder as we can see from DeleteMode. The possible methods are: MoveToDeletedItems, SoftDelete or HardDelete.

$email.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)

In case there are more than 100 items in the Inbox, we need to get the next 100 and process them:

$ivItemView.Offset +=$fiItems.Items.Count

The Do statement terminates when there are no more items available in the Inbox folder to be processed:

} While($fiItems.MoreAvailable -eq$True)

The final complete script looks like this:

Param (

       [Parameter(Position = 0, Mandatory =$False)]

       [String] $Mailbox=[email protected],

 

       [Parameter(Position = 1, Mandatory =$False)]

       [String] $Sender=[email protected]

)

FunctionrunCmdlet ([String] $cmdlet) {

       If ($cmdlet-match“set-“) {

              $output=“Cmdlet not allowed!”

       } Else {

              Try {

                     $output=Invoke-Expression$cmdlet-ErrorActionStop-ErrorVariableErr

              } Catch {

                     Write-Verbose“Error running cmdlet!”

                     $output=$Err

              }

       }

 

       If ($output) {

       Write-Verbose“Composing response”

              $reportBody=“<!DOCTYPE html>

                           <HTML>

                           <head>

                           <title>Monitoring Report</title>

 

                           <style>

                           body {

                                  font-family:Courier New,Courier,Lucida Sans Typewriter,Lucida Typewriter,monospace;

                                 

                                  background-color: white;

                                  color: #000000;

                           }

 

                           </style></head>

 

                           <BODY>

                           <h2 align=””center””>Monitoring Exchange Report</h2>

                           <h4 align=””center””>$((Get-Date).ToString())</h4>

                           <br>”

 

              $output | Out-FileRemoteMonitoring.txt

              $reportBody+=“<b>$cmdlet</b><br>”

              $reportBody+= [String]::Join(“<br>”, (Get-ContentRemoteMonitoring.txt))

              $reportBody+=“</BODY> </HTML>”

 

              Send-MailMessage-From$Mailbox-To$Sender-Subject“Monitoring Result – $(Get-Date -f “”yyyyMMdd hh:mm””)”-Body$reportBody-BodyAsHTML-SMTPservermail.nunomota.pt-AttachmentsRemoteMonitoring.txt

              $reportBody=$null

       }

}

$dllPath=“C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll”

[Void] [Reflection.Assembly]::LoadFile($dllPath)

$service=New-ObjectMicrosoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)

$service.AutodiscoverUrl($Mailbox)

 

$rfRootFolderID=New-ObjectMicrosoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox, $Mailbox)

$rfRootFolder= [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $rfRootFolderID)

 

#Define ItemView to retrive just 1000 Items 

$ivItemView=  New-ObjectMicrosoft.Exchange.WebServices.Data.ItemView(1000)

$fiItems=$null

 

#Define the properties to get – needed to get email sender for example

$psPropSet=New-ObjectMicrosoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

 

Do {

       $fiItems=$service.FindItems($rfRootFolder.Id, $ivItemView)

       [Void] $service.LoadPropertiesForItems($fiItems, $psPropSet)

 

       ForEach ($emailin$fiItems.Items) {

              If ($email.Sender.Address -eq$Sender) {

                     runCmdlet$email.Subject

                     $email.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems) #SoftDelete, HardDelete

              }

       }

 

       $ivItemView.Offset +=$fiItems.Items.Count

} While($fiItems.MoreAvailable -eq$True)

In this script we only use the email’s subject to specify which cmdlets to run, which can be somewhat limited. We can update the script so that instead of looking in the email’s subject, it looks in the body instead. This allows us to run more complex scripts if we want to. The easiest way to do so is to get the email’s body and save it in a .ps1 script file, run it, capture its output and send that output back to the sender. However, most emails will be sent and received in HTML format, which makes them hard to read from a script perspective with all those HTML tags. To ensure all we get is the text itself, we can tell the script to read every email in Text format instead of HTML. We do so by updating our PropertySet and specify that we want the BodyType as Text:

$psPropSet=New-ObjectMicrosoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

$psPropset.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text

Now, when we process an email, the $email.Body variable will contain the email’s body in text format.

Conclusion

In this two-part article series we developed two scripts to monitor and report on Exchange remotely using emails. In the first part, we developed a basic script that uses Message Tracking Logs, which can be somewhat limited if we want to run complex cmdlets or even full scripts. In this final part, we developed a similar script but that uses EWS instead, further expanding the script’s capabilities.

If you would like to read the first part in this articile series please go to Remote Exchange Monitoring and Reporting using Email (Part 1).

About The Author

Leave a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top