Thursday, 16 July 2015

Getting UM Voicemail Greetings in Exchange 2013

Back in 2012 I posted a script to audit Exchange UM greetings.  The script worked nicely for Exchange 2010 but, because of some cmdlet changes, it no longer works in Exchange 2013.  I still needed to be able to audit UM greetings, but had to come up with a new method to do it.

To solve the problem I found a way to use the Exchange Web Services Managed API to return the information I needed from the mailbox.

I wrote a new script which used the EWS API to look inside the mailbox and search for the special hidden messages which contain the UM greetings.  As an added bonus, the new script can also download the greeting audio and save it to disk.

The script takes three parameters - the username, a switch to specify if the audio should be saved, and a path to save the audio to.

If the audio is not saved the script will just return information about the greetings that are recorded in the mailbox.

Obviously, you need the EWS API installed to use the script.

# Get-UMGreeting.ps1
# Script to retrieve greetings from an Exchange UM Mailbox
# Ben Lye -

# Parameters for the script
 [Parameter(Mandatory=$true)] $User,
 [switch] $SaveAudio,
 [string] $Path

# Get the UM mailbox
if ( !($Mailbox = Get-Mailbox $User -ErrorAction SilentlyContinue) ) {
 throw "Mailbox could not be found for user '$($User)'."

if ( $Mailbox.Count -gt 1 ) {
 throw "Multiple mailboxes found. Input must be a unique mail-enabled user."

If ( $SaveAudio -and -not($Path) ) {
 throw "Path must be specified when -SaveAudio is set"

If ( $Path ) {
 If (-not (Test-Path $Path) ) {
  Throw "Path does not exist!"
 If (-not ((Get-Item $Path) -is [System.IO.DirectoryInfo])) {
  Throw "Path must be to a directory!"


# Get the SMTP address from the mailbox
$SmtpAddress = $Mailbox.PrimarySmtpAddress.ToString()

# Path to the Exchange Web Services DLL
$EWSManagedApiPath = "C:\Program Files (x86)\Microsoft\Exchange\Web Services\2.1\Microsoft.Exchange.WebServices.dll"
if ( !(Get-Item -Path $EWSManagedApiPath -ErrorAction SilentlyContinue) ) {
 throw "EWS Managed API could not be found at $($EWSManagedApiPath)."
# Load EWS Managed API
Add-Type -Path $EWSManagedApiPath
# Create EWS Object
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013)

# Get the autodiscover URL

# Use EWS Impersonation to locate the root folder in the mailbox
$Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $SmtpAddress)
$FolderId = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root
$Folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$FolderId)

If (-not $Folder) {
 Throw "Unable to bind to mailbox root folder"
# Create the search filter to find the UM custom greetingsd
$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+ContainsSubstring([Microsoft.Exchange.WebServices.Data.ItemSchema]::ItemClass,"IPM.Configuration.Um.CustomGreetings",[Microsoft.Exchange.WebServices.Data.ContainmentMode]::Substring,[Microsoft.Exchange.WebServices.Data.ComparisonMode]::IgnoreCase);

# Define the EWS search view
$view = New-Object Microsoft.Exchange.WebServices.Data.ItemView(100, 0, [Microsoft.Exchange.WebServices.Data.OffsetBasePoint]::Beginning)
$view.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$view.Traversal = [Microsoft.Exchange.WebServices.Data.ItemTraversal]::Associated

# Create the object to return
$Output = New-Object System.Object
$Output | Add-Member -type NoteProperty -name "DisplayName" -value $Mailbox.DisplayName
$Output | Add-Member -type NoteProperty -name "PrimarySmtpAddress" -value $SmtpAddress
$Output | Add-Member -type NoteProperty -name "HasCustomVoicemailGreeting" -value $False
$Output | Add-Member -type NoteProperty -name "VoicemailGreetingModifiedDate" -value $Null
$Output | Add-Member -type NoteProperty -name "HasCustomAwayGreeting" -value $False
$Output | Add-Member -type NoteProperty -name "AwayGreetingModifiedDate" -value $Null

# Do the search and enumerate the results
If ($results = $service.FindItems( $FolderId, $searchFilter, $view )) {
 If ($SaveAudio) {
  # Define the property set required to get the binary audio data
  $psPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
  # Add the binary audio data property to the property set
  $PidTagRoamingBinary = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x7C09,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);  
  # Load the new  properties

 # Loop through the results
 $Item = $Null
 ForEach ($Item in $Results.Items) {
  # If this is the main greeting, set the flag to true
  If ($Item.ItemClass -eq "IPM.Configuration.Um.CustomGreetings.External" ) {
   $Output.HasCustomVoicemailGreeting = $True
   $Output.VoicemailGreetingModifiedDate = $Item.LastModifiedTime
   $Filename = $Mailbox.Name + "_Standard.wav"
  # If this is the extended away greeting, set the flag to true
  If ($Item.ItemClass -eq "IPM.Configuration.Um.CustomGreetings.Oof" ) {
   $Output.HasCustomAwayGreeting = $True
   $Output.AwayGreetingModifiedDate = $Item.LastModifiedTime
   $Filename = $Mailbox.Name + "_Extended.wav"
  If ($SaveAudio) {
   # Write the audio data to the file

return $Output
Download Script

- Ben


  1. Hi Ben,

    Exellent work with this, and indeed the previous 2010 script which lead me to it! Who would have thought this was so difficult to achieve? It seems like a fairly reasonable requirement, and its rather dissapointing that MS don't cater for it directly with their powershell commandlets.

    I have a similar requiement to perform a "custom greeting" audit, but rather than on the default mailbox greeting, its against a UMCallAnsweringRule. The company uses answering rules to give callers the option to return to reception by pressing 1, or leaving a message by pressing #. Whilst many employees have configured these options, they've not recorded a custom greeting to go with it - and this leads to confusion as callers are read options by the text to speach engine in a way that gets them to press 1, when they mean to press #.

    Is it possible to add a feature to this script to check a given CallAnsweringRule for custom greetings too? I'm not entirely sure how to go about that, or even find out if its possible >.< - but it would seem likely its a combination of PS with queries via the EWS API as you've done above :) Unfortunately, i haven't got a clue how to work with the API, or even if the information i want is available within it! >.<

    I'm going to see what i can figure out, but i don't imagine i'll get all that far :( if you've any pointers, or indeed if you fancy giving it a go, any effort would be greatly appreciated !


  2. Hi Nat,

    That is an interesting problem - I'll take a look at it as soon as I get a chance. I'm thinking it will take some digging into the hidden messages that Exchange uses to store recordings, so probably won't be easy, but I do enjoy a challenge :-)


    1. hehe - nothing like un-documented, hidden messages to make a task a challenge! I'll watch this space to see how you get on :D

      thanks for taking a look, its very much appreciated (no expectations, of course) :)


  3. The Internet is the world's biggest library The Internet is the world's biggest library and e-Learning has turned into a well-known and successful approach to teach, both in the corporate world and in the educated community.

  4. The Internet is the world's biggest library The Internet is the world's biggest library and e-Learning has turned into a well-known and successful approach to teach, both in the corporate world and in the educated community. See more professional voicemail greetings

  5. I was hunting for this script since 2008... I am not kidding! There was a blog post and script I found at that year somewehere on the big internet, and I was blatantly an idiot I did not save immediately to my local drive. Months later, when I actually needed that damn script, I was never able to find it again. There is at least 1 technet forum topic, where people demand for such solution, and nobody was able to provide such things for years:


Note: only a member of this blog may post a comment.