Using Powershell to Simplify Mailbox Auditing (Part 2)

If you would like to read the other parts in this article series please go to:

Introduction

In this part of the series I will continue developing the automated script that I began in Part 1. The main objectives for this part are to deliver the following functions:

Function Name

Purpose

gen_AuditXMLFiles

Requests the mailbox audits for the last 24 hours

perform_MailboxQuery

Queries the audit mailbox for the XML files generated by the gen_AuditXMLFiles function and extracts them to disk

format_XMLTOHTML

Formats the audits to HTML and stores them

Continuing the Script

Code

Description

function gen_AuditXMLFiles{

     

      $CovStDate = (Get-Date).addDays(-1)

      $CovEnDate = (Get-Date).addDays(1)

      $StartDate = “{0:dd/MM/yyyy}” -f $CovStDate

      $EndDate = “{0:dd/MM/yyyy}” -f $CovEnDate

     

      $Users = returnUsers

     

      ForEach($Usr in $Users){

            New-MailboxAuditLogSearch -Mailboxes $Usr.samAccountName -LogonTypes $AccessTypes -StartDate $StartDate -EndDate $EndDate -StatusMailRecipients $AuditMailbox -ShowDetails      

}

}

The purpose of the gen_AuditXMLFiles is to get the complete mailbox audits for all the mailboxes that are linked to the AD accounts within the auditing OU in AD.

The function is designed to get all accesses within 24 hours (although this could lead to duplicate reports depending on your scheduling – I have used 24 hours to ensure that the previous day and all of the current day is take into account).

In order to do the above the script makes use of the value in $CovStDate which is derived using the Get-Date CMDlet with -1 as a parameter (to get yesterday’s date from midnight) – this is used as the start date for the Audit logs.

$CovEnDate is the end period where the reports should be gathered from – this also uses the Get-Date function – but this time with 1 as the parameter (therefore $CovStDate will be yesterday at midnight and $CovEnDate will be tomorrow at midnight)

The New-MailboxAuditLogSearch CMDlet expects the date range to be in the short date format (for UTC MM/dd/yyyy and for GMT dd/MM/yyyy) therefore I have added two new variables $StartDate and $EndDate and converted them to a format which the CMDlet expects.

The script then gets all of the users in the OU into a variable called $Users by calling the returnUsers function (defined in part 1).

We then using a ForEach loop, cycle through each user in $Users and use the New-MailboxAuditLogSearch to get the reports, using the start and end dates contained within the $StartDate and $EndDate variables.

The reports are sent to the mailbox account -StatusMailRecipients) which is designated in the $AuditMailbox global constant (see part 1).

Notice that the script also uses the -ShowDetails parameter – this is essential to getting detailed XML output which contains which messages or objects where modified.

function perform_MailboxQuery{

      $ChkOutlook = (Get-Processselect-string -quiet “OUTLOOK”)

     

      if ($ChkOutlook -eq $null)

      {

      Start-Process “Outlook.exe” -ArgumentList “/safe /profile $OLKProfile”

      start-sleep -s 10

      }

      else

      {

      Write-Host “OUTLOOK is running” -ForegroundColor Green

      }

      $Outlook = New-Object -ComObject Outlook.Application

      $Namespace = $Outlook.GetNameSpace(‘MAPI’)

      $IDInbox = 6   

      $Inbox = ($NameSpace.GetDefaultFolder($IDInbox)).Items.Restrict(“[Unread]=true”)

      if ($inbox.Count -gt 0)

      {

      do

      {

            foreach ($MailItem in $inbox)

            {

                foreach ($attachment in $MailItem.Attachments)

                {

                    $XMLfile =  “$(Get-Date -format ‘yyyy_MM_dd_hh_mm_ss’).xml”

                              $attachment.SaveAsFile( “$AuditFolder\$XMLfile “)

                              Start-Sleep -Seconds 2

                }

                        $MailItem.Unread = $False

                        $MailItem.Delete()

        }

                  $inbox = ($NameSpace.GetDefaultFolder($IDInbox)).Items.Restrict(“[Unread]=true”)

      } while ($inbox.Count -gt 0)

           

            if($EmptyDeletedItemsAfterProcessing -eq $true){

                  empty_DeletedItems

            }

      }

      Start-Sleep -Seconds 7

      Stop-Process -Name “Outlook”

}

The perform_MailboxQuery function is designed to make use of Outlook to connect to the Audit Mailbox, cycle through the items that have been sent to the mailbox and extract the XML attachments to a disk location.

Firstly the function checks to see if Outlook is running – this is done by checking each process that is running on the machine which matches the string value of “Outlook” – the results of this are put in a variable called $ChkOutlook.

Using an if statement, if the value of $ChkOutlook is $null (empty) we assume that Outlook is not running – and then use the Start-Process CMDlet to fire up Outlook – and pass two parameters /safe (start outlook in safe mode, therefore bypassing any add-ins that might interfere with the script and the /Profile parameter which is the MAPI profile of the Audit Mailbox that Outlook should start with (this value is held in $OLKProfile which is a global constant that was defined in part 1).

You should ensure that you have created a MAPI profile for the Audit mailbox (as mentioned in part 1).

We then pause the script for 10 seconds to allow for Outlook to Start.

If Outlook is already running, then the function notes it – and moves on.

A new variable called $Outlook is defined which contains the Com Object reference for the Outlook.Application namespace (this is where we begin to use some Automation Objects).

We then setup the namespace for the Outlook.Application object in a variable called $NameSpace (we are in essence saying that the automation session to Outlook will be using the MAPI protocol).

We define the default folder that we will be working within (the Inbox) in the $IDInbox variable giving it a value of 6 (as 6 is the id of the inbox within Outlook Automation).

The script will then get all unread items in the Inbox into a variable called $Inbox (the script determines Unread items via a restriction filter “[Unread]=true”).

Using an if statement we determine if there are any items in the $inbox, if there are we then use a do… while loop to iterate through each mail item checking for an attachment.

When an attachment is found – it is named in the format of ‘yyyy_MM_dd_hh_mm_ss’).xml and saved to the audit folder location which is defined in the $AuditFolder global constant.

In between each save we pause the script for 2 seconds to ensure that the filename is unique – mark the item in the Inbox as unread and move it to the “Deleted Items” folder.

If the $EmptyDeletedItemsAfterProcessing global constant is set to $true the script will call the empty_DeletedItems function (defined in part 3) and then close down Outlook.

Navigating the XML File

Before we finish up this part by defining the function which navigates the Audit Log XML files – it might help to understand what that function tries to achieve.

The XML files that are produced by the New-MailboxAuditLogSearch always contain the following entries within the tree:

<SearchResults>

      <Event></Event>

<SearchResults>

When an action (or event) has taken place on an item within the source mailbox the above changes to look like the following:

<SearchResults>

      <Event>

            <SourceItems>

                  <Item></item>

            </SourceItems>

</Event>

</SearchResults>

Therefore the function navigates nodes of the file as per the arrows in figure 1, but can also deal with the <SourceItems> nodes not being present.


Figure 1:
Example Navigation in the Audit XML File

Code

Description

function format_XMLTOHTML{

     

      $XMLFiles = Get-ChildItem “$AuditFolder\*.xml”

     

      foreach($xFile in $XMLfiles){

            $hName = $xFile

            $fileName = “$xFile.html”

            WriteHeader $fileName

           

            [System.Xml.XmlDocument]$xd = New-Object system.Xml.XmlDocument

            $XMLfile = Resolve-Path($xFile)

            Write-Host “XML File: $XMLfile” -ForegroundColor White

            $xd.load($XMLfile)

            $NodeLst = $xd.selectNodes(“/SearchResults/Event”)

                 

                  Add-Content $fileName “<table>”

                  Add-Content $fileName “<tr>”

                  Add-Content $fileName “<td><strong>Original Server</strong></td>”

                  Add-Content $fileName “<td><strong>Source Account SID</strong></td>”

                  Add-Content $fileName “<td><strong>Source Display Name</strong></td>”

                  Add-Content $fileName “<td><strong>Mailbox Owner</strong></td>”

                  Add-Content $fileName “<td><strong>Client</strong></td>”

                  Add-Content $fileName “<td><strong>Client Protocol</strong></td>”

                  Add-Content $fileName “<td><strong>Specific Folder</strong></td>”

                  Add-Content $fileName “<td><strong>Logon Type</strong></td>”

                  Add-Content $fileName “<td><strong>Operation Type</strong></td>”

                  Add-Content $fileName “<td><strong>Result</strong></td>”

                  Add-Content $fileName “<td><strong>Source IP Address</strong></td>”

                  Add-Content $fileName “<td><strong>Last Accessed</strong></td>”

                  Add-Content $fileName “<td><strong>Items</strong></td>”

                  Add-Content $fileName “</tr>”

            Foreach($nd in $NodeLst){

                  $Server = $nd.GetAttribute(“OriginatingServer”)

                  $Sid = $nd.GetAttribute(“LogonUserSid”)

                  $UDN = $nd.GetAttribute(“LogonUserDisplayName”)

                  $mbxOwner = $nd.GetAttribute(“MailboxOwnerUPN”)

                  $CPN = $nd.GetAttribute(“ClientProcessName”)

                  $CIS = $nd.GetAttribute(“ClientInfoString”)

                  $FPN = $nd.GetAttribute(“FolderPathName”)

                  $LT = $nd.GetAttribute(“LogonType”)

                  $OP = $nd.GetAttribute(“Operation”)

                  $OR = $nd.GetAttribute(“OperationResult”)

                  $IP = $nd.GetAttribute(“ClientIPAddress”)

                  $LA = $nd.GetAttribute(“LastAccessed”)

                  Add-Content $fileName “<tr>”

                  Add-Content $fileName “<td>$Server</td>”

                  Add-Content $fileName “<td>$SID</td>”

                  Add-Content $fileName “<td>$UDN</td>”

                  Add-Content $fileName “<td>$mbxOwner</td>”

                  Add-Content $fileName “<td>$CPN</td>”

                  Add-Content $fileName “<td>$CIS</td>”

                  Add-Content $fileName “<td>$FPN</td>”

                  Add-Content $fileName “<td>$LT</td>”

                  Add-Content $fileName “<td>$OP</td>”

                  Add-Content $fileName “<td>$OR</td>”

                  Add-Content $fileName “<td>$IP</td>”

                  Add-Content $fileName “<td>$LA</td>”

                  Add-Content $fileName “<td>”

                 

                  $sub = $nd.SourceItems.Item

                 

                  Foreach($itm in $sub){

                        $ItemSub = $itm.Subject

                        $IfP = $itm.FolderPathName

                        If($ItemSub -eq $null){

                              Add-Content $fileName “<p>No associated item(s)</p>”

                        }

                        Add-Content $fileName “<p>$Ifp : $ItemSub</p>”

                  }

                  Add-Content $fileName “</td>”

                  Add-Content $fileName “</tr>”

                 

            }

            Add-Content $fileName “</table>”

           

     

      WriteFooter $fileName

           

      }

}

The format_XMLTOHTML function is designed to get the data from all of the XML files that are stored in the $AuditFolder and format them to HTML.

To begin with the function retrieves all of the XML files in the AuditFolder into a variable called $XMLFiles.

We then cycle through each file in $XMLFiles variable using a foreach loop – we get the individual XML filename into a variable called $hName which is used in conjunction with the $fileName variable to construct the final name of the formatted HTML file which corresponds to the XML file that the function is working on at the time.

We begin the construction of the HTML file by calling the WriteHeader function passing the $fileName variable to it, this creates our HTML file.

We then get the content of the current XML file within the loop into a variable called $xd and establish the starting node (in the variable called $NodeLst) as “/SearchResults/Event”.

Using the Add-Content CMDlet the function builds a table within the HTML file with relevant headings (e.g. SID, Source Display Name, Mailbox Owner etc.)

Then using a Foreach loop the function cycles through the values in $NodeLst and uses the $nd.GetAttribute method to extract the values for each of the table headings.

If the <event> node has <SourceItems> we then cycle through all of these and add them to the relevant section in the HTML table.

The function then closes the HTML off by calling the WriteFooter function (defined in part 1).

Conclusion

In this part we have continued with the development of the sample auditing script by defining the following functions;

  • gen_AuditXMLFiles

  • perform_MailboxQuery

  • format_XMLTOHTML

In the final part we will finish off by defining the:

  • empty_DeletedItems

  • manage_XMLPostProcessing

We will also explain how the script can be scheduled to run, ensure that your system meets the minimum requirements for the script to run optimally and give you a link to download the script in its entirety.

If you would like to read the other parts in this article series please go to:

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