In the past few month the number of incomming request related to calendar issues increased. There are several reasons for this like message body truncation, Richt text or HTML formated messages get converted to plain text. Those are most likely related to iOS devices and there is a KB available for this here.
But not only iOS is causing issues. Especially when it comes to delegate scenarios with more than one delegate and when all of them have multiple clients with different versions (e.g.: Outlook 2010/2013, Outlook for Mac and a whole bunch of mobile devices).
To get to a point: Just ignored the following recommendations
- Best practices when using the Outlook Calendar
- Description of common scenarios in which Calendar information may be removed from the Calendar or may be inaccurate
- Recommendations for configuring delegate scenarios in outlook 2010
But how do you troubleshoot those issues? There are several techniques and I will cover some of them in this post:
In this update I fixed a bug, which caused the script to loop, when more than 1000 items where found. I also improved the performance by using ConvertIds rather than ConvertId method. With this only one EWS call for all items instead one for each is made.
Just have a look at the section for the script here.
What you are looking for
When you start analysing calendar items it all comes down to a few properties of those items, which helps you to find the what, who, when and which client. The following list is the attributes (at least I’m using for analysis):
|PidTagLastModificationTime||The time the item was last modified.|
|PidTagLastModifierName||The user who modified the item.|
|PidLidCleanGlobalObjectId||The unique GOID across multiple mailboxes. This is the most important unique identifier especially for recurrent appointments!|
|PidLidGlobalObjectId||The GOID of an item.|
|0x8059001E||This extended property records the client, which modified the item.|
|0x8054001E||This extended property records the last action performed on the item.|
When you are on Exchange 2013 then you really have the best out-of-the-box options. You can export the diagnostic logs using the CmdLet Get-CalendarDiagnosticLog and take advantage of the CmdLet Get-CalendarDiagnosticAnalysis.
With Exchange 2010 you can get the dignostic logs like in Exchange 2013 with the CmdLet Get-CalendarDiagnosticLog, but you cant take advantage of the CmdLet Get-CalendarDiagnosticAnalysis like in Exchange 2013.
How does it looks like?
You can always use MFCMAPI to examine the properties. Here is an example how it looks like when you open a mailbox and check the properties of an item
In Exchange 2013 you can use the previous mention CmdLets and create either a HTML report or a CSV file, which could be analysed in Excel or your favorite tool.
In this example I’m analyzing an appointment with the subject “Troubleshoot calendaritems 01”:
Get-CalendarDiagnosticLog -Identity ingogege -Subject 'Troubleshoot calendaritems 01' -LogLocation C:\Temp
You will find the output in the folder I specified
Now use Get-CalendarDiagnosticAnalysis and analyse the gathered log
Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Ingo Gegenwarth' -DetailLevel Advanced >C:\Temp\CalDiag01.csv
in this case we have a CSV file as output, which looks like this
Import-Csv C:\Temp\CalDiag01.csv | Out-GridView
Well, okay this one doesn’t look really interesting as not much happened. But this was just a simple example. Let’s see how it looks in the second one.
In this example we have a manager/delegate scenario. Mickey is the delegate of Ingo and setup a meeting series with Han.
First step is to extract the calendar items from all involved mailboxes
Get-CalendarDiagnosticLog -Identity ingogege -Subject 'Troubleshoot calendaritems 02' -LogLocation C:\Temp Get-CalendarDiagnosticLog -Identity mickeygege -Subject 'Troubleshoot calendaritems 02' -LogLocation C:\Temp Get-CalendarDiagnosticLog -Identity hansolo -Subject 'Troubleshoot calendaritems 02' -LogLocation C:\Temp
In the next step let Exchange analyse the items and create a HTML report
Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Ingo Gegenwarth' -DetailLevel Advanced -OutputAs HTML >C:\Temp\ingo.html Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Mickey Gegenwarth' -DetailLevel Advanced -OutputAs HTML >C:\Temp\mickey.html Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Han Solo' -DetailLevel Advanced -OutputAs HTML >C:\Temp\han.html
Alternatively create a CSV file and open it in Excel
Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Ingo Gegenwarth' -DetailLevel Advanced >C:\Temp\ingo.csv Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Mickey Gegenwarth' -DetailLevel Advanced >C:\Temp\mickey.csv Get-CalendarDiagnosticAnalysis -LogLocation 'C:\Temp\Han Solo' -DetailLevel Advanced >C:\Temp\han.csv
As you can see you will get detailed information when, who and which client modified the items and much more for each mailbox
and here the CSV imported into Excel
You really get a full set of information and I recommend to go with the CSV files.
Note: The CmdLet Get-CalendarDiagnosticAnalysis is currently only available within Exchange 2013 on-prem. Neither in Exchange 2010 nor in EXO!
This is really a huge step forward. Nevertheless I run into the issue that I don’t have the CmdLet available to work on all those incoming requests. The only way was to gather the logs and then use MFCMAPI to check each item…..as you can imagine this is very time consuming.
Therefore I wrote a script using EWS, which will get all the details for me:
What do you need to run the script?
- You need to have Microsoft Exchange Web Services (EWS) installed
You should have the role ApplicationImpersonation or you will need FullAccess on the mailboxes you want to scan
The script could be downloaded here
The script accepts the following parameters:
|EmailAddress||The e-mail address of the mailbox, which will be checked. The script accepts piped objects from Get-Mailbox or Get-Recipient.|
|Credentials||Credentials you want to use. If omitted current user context will be used.|
|Impersonate||Use this switch, when you want to impersonate.|
|Subject||Use a string as a filter to search for. Cannot be combined with CleanGlobalObjectID or GlobalObjectID.|
|StartDateLastModified||Filter for items, which have been modified after this date. The parameter needs to be a type of [datetime]. You can combine this with EndDateLastModified to filter for a range.|
|EndDateLastModified||Filter for items, which have been modified before this date. The parameter needs to be a type of [datetime]. You can combine this with StartDateLastModified to filter for a range.|
|CleanGlobalObjectID||When you know the ID (e.g.: using MFCMAPI), you can search for it. Cannot be combined with Subject or GlobalObjectID.|
|GlobalObjectID||When you know the ID (e.g.: using MFCMAPI), you can search for it. Cannot be combined with Subject or CleanGlobalObjectID.|
|CalendarOnly||By default the script will enumerate all folders. If you want to limit to folders with type “IPF.Appointment” use this switch.|
|Server||By default the script tries to retrieve the EWS endpoint via Autodiscover. If you want to run the script against a specific server or endpoint, just provide the name in this parameter. Not the URL!|
|AllFolders||By this all folders under MsgRootFolder will be searched|
|AllItemProps||All properties of an item will be returned|
|SortByDateTimeCreated||The output will be sort by DateTimeCreated|
|DestinationID||If you ant to have the ItemId converted provide the destination format. Valid formats are “EwsLegacyId”, “EwsId”, “EntryId”, “HexEntryId”, “StoreId”, “OwaId” based on https://msdn.microsoft.com/library/microsoft.exchange.webservices.data.idformat(v=exchg.80).aspx|
|TrustAnySSL||Switch to trust any certificate.|
|DateFormat||By default the script enumerates the current culture and the datetime format. It will then append the milliseconds to it. If you want to specify your own format (e.g.:’yyyyMMdd hhmmssfff’) use this.|
|WebServicesDLL||Path to the DLL|
|StartDate||Filter by Start date of a single appointment. Note: Cannot be used to find recurring meetings!|
|EndDate||Filter by End date of a single appointment. Note: Cannot be used to find recurring meetings!|
|StartDateTimeCreated||When filter by Datetimecreated, all items created after this date are returned.|
|EndDateTimeCreated||When filter by Datetimecreated, all items created before this date are returned.|
|UseLocalTime||When this switch is used, DateTimeCreated and LastModifiedTime is converted to local time of the machine where the script is running on.|
By default the script is searching the following folders:
- all folders with the folderclass “IPF.Appointment”
- WellKnownFolders: Inbox,SentItems,RecoverableItemsRoot
- all subfolders under the WellKnownFolder RecoverableItemsRoot except “Audits”
If you use the switch -CalendarOnly only the folders with the folderclass “IPF.Appointment” will be searched.
When you select -AllFolders, the script will extend the default selection and search all found folders under MsgFolderRoot.
How it looks in action?
I take the second example. We have 3 people involved: Ingo, Mickey and Han.
First put their primary SMTP addresses into an array
[array]$mbs = "firstname.lastname@example.org","email@example.com","firstname.lastname@example.org"
Then search those mailboxes for items with the string ‘calendaritems 02’ in the subject
$basic=.\Get-CalendarItems.ps1 -EmailAddress $mbs -Impersonate -Subject 'calendaritems 02'
now you can look at all the versions in all the mailboxes what exactly happened
as you can see you will get from all mailboxes the data into one single file in order of what happened. Several clients e.g.: OWA, ActiveSync were involved. But you can even more data. Now I’m searching for the PidCleanGlobalObjectID and use also the switch -AllItemProps
$enhanced= .\Get-CalendarItems.ps1 -EmailAddress $mbs -Impersonate -CleanGlobalObjectID 040000008200E00074C5B7101A82E00800000000F0838A8F1882D001000000000000000010000000099DC0EA675D1A4CA9BB87AF552001AB -AllItemProps
The script will now gather allmost all properties of the items
as the script adds the items itself as an object to the output you can examine all the properties. Take item number 8 from the result
let’s have a look at the ModifiedOccurrences
the occurence on the 6/26/2015 was changed from starting time 10:00 AM to 07:00 PM and looking at the following screenshot you see that this change was sent by the user Ingo Gegenwarth using Outlook (Client=MSExchangeRPC) at 08:18:01.529 AM
I know this is not easy to read. Here is another example.
Real life example
A user complaint that he had a wrong date in his calendar than the organizer. The organizer had the 13.04.2015 in his calendar, while the attendee had the 15.04.2015. I extracted all the items from their mailboxes and started analyzing it in Excel. I marked organizers items yellow
so as I used the switch -AllItemProps, I have the items itself as an object. I also piped the result into a variable $organizer and now I can easily compare what the difference is of the property ModifiedOccurrences
as you can see the ActiveSync client reverted the change and caused the discrepancy between the organizer and attendee.
When it comes to a support case you will use the build-in CmdLets mentioned above. If you want to analyze some issues by yourself, I would use the script as it gives you a better overview and it’s order by time. The script has also some disadvantages as you will need to have special permissions and EWS Api installed. But it also has some advantages as you can really extract almost all properties (e.g.: body of an item), which the CmdLets don’t do.
I was approached by a few colleagues and other peoples, which used the script to troubleshoot some calendar issues. The issue they were facing was that they couldn’t get any entry for the client or for the action, which was performed on the item.
In all cases the property CalendarVersionStoreDisabled of those mailboxes was set to $True. In order to get those extende MAPI properties written and to get the different versions of the item logged you need to make sure that this value is set to $False, which is the default.
From my experience with the last cases of calendar item issues I also change the script as follows:
- I predefined the parameter DateFormat with the value “yyyyMMdd HHmmssfff” in order to have a better sorting in e.g.:Excel
- When you use the switch AllItemProps now the script reports all ModifiedOccurences and DeletedOccurrences, which makes it easier to troubleshoot recurrent meetings
Initial the script did not include a few IPM item classes in the search in order to have a better overview. Now those are included:
Besides this the property Appointment.Recurrence is now converted in a string and available in the output, when the switch is used AllItemProps:
This is a result of an increasing number of reportes issues of meeting cancelation.