Using PowerShell and EWS to monitor a mailbox

I support a suite of application services that implement our ITIL processes. One of the functions allows users to create trouble tickets by sending a specially crafted email message to a specific email address. The application has a service that polls that mailbox once a minute to retrieve those messages and create new Incidents. Periodically, that email polling service stops working causing messages to queue up in the mailbox. The process is still running and providing other functions but it is no longer processing the inbound messages. We have monitoring on the system to watch that service and alert us when it hangs or crashes. However, since the service is still running, we never get alerted.

I needed another way to monitor for this problem. What if I could create a script that would check the mailbox to see if there were any messages in the Inbox that had arrived more than a few minutes ago? Since the service polled the mailbox every minute, any message more than five minutes old would indicate that the polling service had stopped functioning. But how can you read the Inbox?

In the past, I have used VBScript to automate Outlook and manage email on my desktop. However, I didn’t want to install Outlook on the server. Installing Outlook incurs licensing costs and is way more overhead than I really need. That also means that I need to manage patching for Outlook and other Office components on a server which we don’t normally do in our environment. Searching the internet I found some scriptable POP3 and IMAP clients. Some were commercial and some open source. But the cost of these options (in money and learning curve) was too high and the supportability was questionable.

We use Microsoft Exchange for our email services. I know that PowerShell is now the preferred method for most management tasks in Exchange. This led me to search for Exchange PowerShell options.

Enter Exchange Web Services (EWS) and its friendly.NET managed API. Installing and using the EWS Managed API is simple. It can be installed on any Windows machine and does not require any other Exchange components. Simply download the MSI package and install it.

I am running this script as a scheduled task on Window Server 2008 R2. The scheduled task runs once per hour and is configured to run with domain credentials that have access to the target mailbox. The script uses EWS to access the mailbox and check for stale messages. It also uses Net.Mail.SmtpClient to send alert messages. I could have used EWS to send the alert message but Net.Mail.SmtpClient is so much easier to use.

param($mailboxName = "new-tickets@contoso.com",
$smtpServerName = "smtp.contoso.com",
$emailFrom = "monitorservice@contoso.com",
$emailTo = "support@contoso.com"
)

# Load the EWS Managed API
Add-Type -Path "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

try {
  $Exchange2007SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1
  $Exchange2010    = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010
  $Exchange2010SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1
  $Exchange2010SP2 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
  $Exchange2013    = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013
  $Exchange2013SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1

  # create EWS Service object for the target mailbox name
  $exchangeService = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList $Exchange2010SP2
  $exchangeService.UseDefaultCredentials = $true
  $exchangeService.AutodiscoverUrl($mailboxName)

  # bind to the Inbox folder of the target mailbox
  $inboxFolderName = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox
  $inboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$inboxFolderName)

  # Optional: reduce the query overhead by viewing the inbox 10 items at a time
  $itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 10
  # search the mailbox for messages older than 15 minutes
  $dateTimeItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived
  $15MinutesAgo = (Get-Date).AddMinutes(-15)
  $searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo -ArgumentList $dateTimeItem,$15MinutesAgo
  $foundItems = $exchangeService.FindItems($inboxFolder.Id,$searchFilter,$itemView)

  # report the results via email and Application event log
  $entryType = "Information"
  $messageBody = "Self-service mailbox scan completed at {0}.`r`n" -f (get-date -format "MM/dd/yyyy hh:mm:ss")
  if ($foundItems.TotalCount -ne 0) {
  $entryType = "Warning"
  $subject = "Self-service mailbox hung"
  $messageBody  = "Inbox has {0} message(s) that are more than 15 minutes old.`r`n" -f $foundItems.TotalCount
  $messageBody += "Inbox has {0} message(s) total.`r`n`r`n" -f $inboxFolder.TotalCount
  $messageBody += "Please restart the Email Engine on SERVER01`r`n"
  $messageBody += "Self-service mailbox scan completed at {0}.`r`n" -f (get-date -format "MM/dd/yyyy hh:mm:ss")
  $messageBody += "Script run from $env:computername`r`n"
  $smtpClient = New-Object -TypeName Net.Mail.SmtpClient -ArgumentList $smtpServerName
  $smtpClient.Send($emailFrom, $emailTo, $subject, $messageBody)
  }
  Write-EventLog -LogName "Application" -Source "Application" -EventId 1 -Category 4 -EntryType $entryType -Message $messageBody
}
catch
{
  $entryType = "Error"
  $subject = "Error in mailbox monitor script"
  $messageBody = "{0}`r`n{1}" -f $_.Exception.Message,$_.InvocationInfo.PositionMessage
  Write-EventLog -LogName "Application" -Source "Application" -EventId 1 -Category 4 -EntryType $entryType -Message $messageBody
  $smtpClient = New-Object -TypeName Net.Mail.SmtpClient -ArgumentList $smtpServerName
  $smtpClient.Send($emailFrom, $emailTo, $subject, $messageBody)
}
Advertisements

I am an experienced IT technologist specializing in optimizing user experiences, providing best-in-class support and developing creative solutions. I script therefore I am. I build tools to improve troubleshooting and gather supporting data.

Tagged with: , ,
Posted in Scripting
21 comments on “Using PowerShell and EWS to monitor a mailbox
  1. Vinodh says:

    Hi Sean wheeler,

    Thanks for your script. I really appreciate your work.

    I am trying to use this script by adding some stile modifications to it. My requirement is to monitoring a specific email notification(email sub: Task failed) received to my team DL & then write it to a log file.Through this i’ll monitor the log file with a specific pattern & trigger an alarm. please help me as to what changes need be made on the script to achieve my requirement.

    Regards,
    Vinodh

    Liked by 1 person

  2. Sean Wheeler says:

    Hi Vinodh,
    Take a look at the EAS Managed API reference (linked in the original article). In line 33 of my script I use the FindItems method to get all of the items in the Inbox. This will return a collection of EmailMessage items. From there you can look at the Subject property of each item to find the messages you are interested in.

    See http://msdn.microsoft.com/en-us/library/microsoft.exchange.webservices.data.emailmessage_members(v=exchg.80).aspx for more info.

    Liked by 1 person

  3. vel says:

    Hi Sean,
    Thanks for the script. Even I’m looking for a solution to create tickets when a mail is sent to a particular email ID in specific format. can you please share that script as well?
    Vel

    Like

    • Sean Wheeler says:

      Hi Vel,
      Creating a script like that would not be very difficult but it will be different for every ticketing system out there. Knowing and coding for the requirements of your ticket system is the hard part. Getting the data from the email message is easy. You just access the Body property of the EmailMessage item.

      Like

  4. Alan says:

    Sean,

    Hope someday things are “that easy” for me. Right now not the case.

    After installing EWS I got the following when running the script…
    Exception calling “Send” with “4” argument(s): “The SMTP host was not specified.”

    Didn’t see $smtpServerName getting assigned anywhere so changed it to a literal.

    Now I’m getting…
    Exception calling “Send” with “4” argument(s): “Service not available, closing transmission channel. The server response was: 4.3.2 Service not available”

    Now I’m stumped. Would appreciate any clues including other references and other ways to approach the problem.

    Problem is, for a general mailbox need to:
    1. identify all unique senders
    2. unique subjects currently being sent by any sender

    The inbox gets automated messages and the sources have not been reliably recorded for a while. I’m being asked to figure it out and powershell seems to me the way to go rather than copy paste to create the list and maybe missing some senders or messages. To say nothing of the effort.

    Thank you for any suggestions.

    Like

    • Sean Wheeler says:

      Hi Alan,
      $smtpServerName is one of the command line parameters defined on Line 2 of the script. You can pass a value via the command line or use the default value as defined in the script.

      The “4.3.2 Service not available” response is likely a permissions issue. Your SMTP server may not allow anonymous senders so you will have to provide credentials. See the documentation: https://msdn.microsoft.com/en-us/library/system.net.mail.smtpclient.credentials(v=vs.110).aspx

      But that is all for sending email. If you just want to read the contents of a mailbox and report on senders and subjects then you don’t need to use system.net.mail.smtpclient at all.

      Like

      • Alan says:

        Hello Sean,

        Thanks for your follow up.

        You’re correct I don’t need to send. I’ve been searching for scripts and trying various ones to perform reading the inbox and producing a list of unique sender/subject pairs.

        Yours is the script that produced the fewest errors of the ten or so I tried so I figured it would be the best place to start trying to produce my unique sender/subject listing.

        Since your reply was sent I’ve made modifications to your scripts as below. Output is always the same and always appear to be produced by the catch statement even though all the variables appear to have values so it doesn’t seem there should be an error causing catch to be entered.

        Any hints you can offer and references you can direct me toward would be much appreciated.

        NOTE: Output is the same results regardless which $Exchange variable is used.

        # Load the EWS Managed API
        Add-Type -Path “C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll”

        try {
        $Exchange2007SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1
        $Exchange2010 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010
        $Exchange2010SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1
        $Exchange2010SP2 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2
        $Exchange2013 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013
        $Exchange2013SP1 = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1

        # create EWS Service object for the target mailbox name
        $exchangeService = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExchangeService -ArgumentList $Exchange2013SP1
        $exchangeService.UseDefaultCredentials = $true
        # actual email is used below when running the script
        $exchangeService.AutodiscoverUrl(“@.com”)

        # bind to the Inbox folder of the target mailbox
        $inboxFolderName = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox
        $inboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$inboxFolderName)

        $itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView

        Write-Output $inboxFolderName, $inboxFolder, $itemView
        }
        catch
        {
        Write-Output “Catch-statement”, “”$”inboxFolderName***”, $inboxFolderName, “”$”inboxFolder***”, $inboxFolder, “”$”itemView***”, $itemView
        }

        Output is always….
        Catch-statement

        $inboxFolderName***
        Inbox

        $inboxFolder***

        Id : AAMkADkwMTk1Y2Q1LWIzMWQtNDFlMy1iZDg4LWY2NzFlYWRiNjU0
        OAAuAAAAAAAcli98zl8yRLNllnTgFtiqAQB0018l56k/SrSrIB7R
        wv2RAAAAFN+WAAA=
        ParentFolderId : AAMkADkwMTk1Y2Q1LWIzMWQtNDFlMy1iZDg4LWY2NzFlYWRiNjU0
        OAAuAAAAAAAcli98zl8yRLNllnTgFtiqAQB0018l56k/SrSrIB7R
        wv2RAAAAFN+TAAA=
        ChildFolderCount : 0
        DisplayName : Inbox
        FolderClass : IPF.Note
        TotalCount : 9
        ExtendedProperties : {}
        ManagedFolderInformation :
        EffectiveRights : CreateAssociated, CreateContents, CreateHierarchy,
        Delete, Modify, Read
        Permissions : {Microsoft.Exchange.WebServices.Data.UserId,
        Microsoft.Exchange.WebServices.Data.UserId}
        UnreadCount : 1
        PolicyTag :
        ArchiveTag :
        WellKnownFolderName :
        Schema : {Id, ParentFolderId, FolderClass, DisplayName…}
        Service : Microsoft.Exchange.WebServices.Data.ExchangeService
        IsNew : False
        IsDirty : False

        $itemView***
        Traversal : Shallow
        OrderBy : {}
        PageSize : 10
        OffsetBasePoint : Beginning
        Offset : 0
        PropertySet :

        Like

      • Sean Wheeler says:

        Alan, since the output you are getting is coming from the Catch block that tells me that an exception occurred. You need to look at the exception to see what is happening. But there are a couple of things I could point to that need attention. First “@.com” does not look like a valid mailbox for Exchange autodiscovery. Second, is an ItemView object represents the view settings in a folder search operation. But you are not executing a FindItems() query.

        But focus on the exception that is occurring. That will really help you understand where the code is failing.

        Like

      • Alan says:

        Sean, thanks again for continuing to help.

        You might have guessed “@.com” is an obfuscated email to avoid posting a live email. If you guessed that you were right. There’s a correct email in the development code.

        Since the catch was running and all the variables appeared to have values, including the last one before the catch statement, I was stumped how to identify what might be causing the error.

        Took out the catch statement and its code and ran the script again. This caused an error to display in the terminal.

        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        New-Object : A constructor was not found. Cannot find an appropriate
        constructor for type Microsoft.Exchange.WebServices.Data.ItemView.
        At H:\Alan’s docs\readoutlook3_wEWS.ps1:20 char:15
        + $itemView = New-Object -TypeName
        Microsoft.Exchange.WebServices.Data.ItemView
        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

        I searched the error and looked up the ItemView class and haven’t yet found anything that helped me understand where that last statement is coming up short.

        I’m also working on another tack, using Powershell Outlook cmdlets to query the Inbox. So far I can query my Inbox but not the target Inbox.

        I’ll get it sooner or later. Hoping for sooner. For now it’s a contest between my head and the wall.

        ~~~~~~~~~~~~alternate code that’s reading my Inbox~~~~~~~~~~~~
        Add-type -assembly “Microsoft.Office.Interop.Outlook” | out-null
        $olFolders = “Microsoft.Office.Interop.Outlook.olDefaultFolders” -as [type]
        $outlook = new-object -comobject outlook.application
        $namespace = $outlook.GetNameSpace(“MAPI”)
        $folder = $namespace.getDefaultFolder($olFolders::olFolderInBox)
        $folder.items | Select-Object -Property SenderName, SenderEmailAddress, Subject

        Like

      • Alan says:

        Thank you, thank you, thank you Sean!

        I have access and am reading the mailbox after a few tweaks to the script in the last link you provided.

        Now need to make some more tweaks

        – filter the $itemlist by date to limit what’s returned
        – figure out how to provide credential beside my own
        I’m logging in with the account that owns the mailbox (because of $service.UseDefaultCredentials = $true I expect)

        and the script will be where it needs to be. I’ll own that and post back when I figure it out.

        Would not have gotten to this point without your help.

        Thank you again.

        -Alan

        Like

  5. Alan says:

    It’s working! Thanks for your help (and others). Still more to learn like why when using EWS the “Sender” is blank but if I use Microsoft.Office.Interop.Outlook and connect to Outlook the sender has the expected info.

    Anyway, here’s the working code for anyone who’s interested…
    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ## Read from Inbox of mail account designated on command line.
    ## Must know credential fro mailbox.
    ## If wish to change date range need to change around line 99
    ## Output file defined around line 123

    ## Get the Mailbox to Access from the 1st commandline argument

    $MailboxName = $args[0]

    ## Load Managed API dll
    ###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
    $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path ‘Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services’|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).’Install Directory’) + “Microsoft.Exchange.WebServices.dll”)
    if (Test-Path $EWSDLL)
    {
    Import-Module $EWSDLL
    }
    else
    {
    “$(get-date -format yyyyMMddHHmmss):”
    “This script requires the EWS Managed API 1.2 or later.”
    “Please download and install the current version of the EWS Managed API from”
    “http://go.microsoft.com/fwlink/?LinkId=255472”
    “”
    “Exiting Script.”
    exit
    }

    ## Set Exchange Version
    $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2

    ## Create Exchange Service Object
    $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

    ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials

    #Credentials Option 1 using UPN for the windows Account
    $psCred = Get-Credential
    $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
    $service.Credentials = $creds
    #Credentials Option 2
    #service.UseDefaultCredentials = $true

    ## Choose to ignore any SSL Warning issues caused by Self Signed Certificates

    ## Code From http://poshcode.org/624
    ## Create a compilation environment
    $Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
    $Compiler=$Provider.CreateCompiler()
    $Params=New-Object System.CodeDom.Compiler.CompilerParameters
    $Params.GenerateExecutable=$False
    $Params.GenerateInMemory=$True
    $Params.IncludeDebugInformation=$False
    $Params.ReferencedAssemblies.Add(“System.DLL”) | Out-Null

    $TASource=@’
    namespace Local.ToolkitExtensions.Net.CertificatePolicy{
    public class TrustAll : System.Net.ICertificatePolicy {
    public TrustAll() {
    }
    public bool CheckValidationResult(System.Net.ServicePoint sp,
    System.Security.Cryptography.X509Certificates.X509Certificate cert,
    System.Net.WebRequest req, int problem) {
    return true;
    }
    }
    }
    ‘@
    $TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
    $TAAssembly=$TAResults.CompiledAssembly

    ## We now create an instance of the TrustAll and attach it to the ServicePointManager
    $TrustAll=$TAAssembly.CreateInstance(“Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll”)
    [System.Net.ServicePointManager]::CertificatePolicy=$TrustAll

    ## end code from http://poshcode.org/624

    ## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use

    #CAS URL Option 1 Autodiscover
    $service.AutodiscoverUrl($MailboxName,{$true})
    “Using CAS Server : ” + $Service.url

    # Bind to the Inbox
    $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox,$MailboxName)
    $MailboxRoot= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
    $InboxFolder= [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

    #Define Date to Query
    $StartDate = [system.DateTime]::Today.AddDays(-14)
    $EndDate = [system.DateTime]::Today.AddDays(1)

    # Create the search filter
    $Sfgt = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, $StartDate)
    $Sflt = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThan([Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived, $EndDate)
    $sfCollection = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And);
    $sfCollection.add($Sfgt)
    $sfCollection.add($Sflt)

    # Perform the search
    $view = new-object Microsoft.Exchange.WebServices.Data.ItemView(5000)
    $frFolderResult = $InboxFolder.FindItems($sfCollection,$view)

    # Write output to file
    $frFolderResult | Select-Object -Property DateTimeReceived, LastModifiedName, Subject | Export-Csv -Path “c:\temp\$MailboxName-Inbox.csv”

    Exit

    Like

  6. Alan says:

    Slowly but surely.

    Took a while to figure out how to “…calling the Load() method on each item in $frFolderResult…”. So simple once I found the answer.

    $frFolderResult | ForEach {$_.Load()}

    My modified output is now…
    $frFolderResult | Select-Object -Property DateTimeReceived, LastModifiedName, Sender, SenderName, From, Subject

    Not quite there yet. For whatever reason one column, SenderName, isn’t populating.

    Haven’t gone back to the output produced when Microsoft.Office.Interop.Outlook was used so haven’t verified that I tried SenderName with that. However I do remember all fields were populated when using that technique.

    Might not really need SenderName but at minimum I should see if the other method populated the field and then understand why this method isn’t.

    More digging.

    Like

  7. Anthony says:

    Is it possible to modify the script to monitor multiple mailbox’s and to send the results as 1 email?

    Like

    • Sean Wheeler says:

      Yes, it is possible. You would just need to bind to each mailbox, iteratively, and check the contents. You would need the proper credentials to connect to each mailbox. It would be easier if you had a “service” account that had rights to all of the mailboxes. Then you configure the scheduled task to run as that one account. This would allow you to use default credentials in the script and not have to embed the username and password in the script.

      Like

  8. BB says:

    I am trying to use this script but it keeps telling me there are 38 messages in the inbox when there is actually 0. What am I missing?

    Like

  9. Tom K. says:

    Alan, did you ever find a solution to the “constructor not found” error? I’m having the same problem.

    Like

  10. […] query the mailbox using EWS and forward the messages along to the agents. Some Google-fu and found: https://seanonit.wordpress.com/2014/10/29/using-powershell-and-ews-to-monitor-a-mailbox/. That lasted about a minute before Azure popped in my […]

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

sean on it
Categories
Follow Sean on IT on WordPress.com
Blog Stats
  • 32,732 hits
The Access Onion

Access Management and Identity Federation on a plate.

The Old New Thing

Random thoughts and things I have learned in my IT journey.

Anonymous's Activities

Random thoughts and things I have learned in my IT journey.

PowerShell.org

A Community for PowerShell People

%d bloggers like this: