Find mailbox folders with recently changed content

Mailbox settings can be stored in a number of places inside a mailbox.  A number of my scripts use Exchange Web Services to manipulate these settings, usually when there isn’t another way for an admin to manage them on behalf of a user.  An example is Office 365’s app launcher, which is currently being updated to v3 for tenants.  Because the method of managing the v2 launcher doesn’t work with v3, I had to try and find where the settings are stored.  The first step in doing that is changing a setting the way a user does (in this case, OWA) and then looking for changed folders as an indication that the setting is stored somewhere in one of them.  I decided to share how I do that.

The folder property PR_LOCAL_COMMIT_TIME_MAX (0x670A) stores the last time an item in the folder has changed.  This can be used in a search filter to quickly find all folders that have changed since a specific time.  First, I define all the MAPI properties I am going to be using in a search filter or in the results:

For efficiency, you usually only want to return as many properties as you will be using in some way.  (It is easier/simpler, though, to return the first class properties, when you will be using a lot of the common properties.)  So I define a property set to return only what I need:

After creating a folder view and adding the property set to it, I need to create a search filter.  This filter gets all folders (that aren’t search folders) where the last commit time is newer than a date and time:

Next, I connect to the mailbox’s root folder because I want to search all folders in the mailbox, not just the ones the user can see, and execute a search:

I then loop through the results.  First class properties, even if you manually add them to a property set, can be referenced by the object’s property name, e.g., $folder.DisplayName, but all other properties (known as extended properties) are collectively stored in another property.  You have to attempt to retrieve a defined extended property from this collection.  To do this, you have to define a variable in which the value of the extended property will be stored.  If you don’t, you will get an error.  Then you call the TryGetPropertymethod, specifying the defined property you want and, by reference, which variable you want to put its value.  For example, such as with the last commit time:

(Using the [void]  type accelerator suppresses the output of the object that is returned, which in this case is just True or False as to whether the extended property is in the collection.  It is the same as piping the command to Out-Null .)  While having the folder name is helpful, that alone doesn’t always make it clear where that folder is.  Therefore, I also get the path to the folder.  But the property stores it as a binary value, so I convert it to a string:

I put all the folders in an object, reverse sort by the last modified date, and output it to the screen:

To make it more consumable for others, I prettied it up and put it into a script.  You can download the script or copy the code below (after expanding).

  Get-FoldersWithContentChanges.ps1 (5.6 KiB)


Updated script that applies retention tag to items in a default folder

Articles in the "Retention tag on default folder items" series

  1. Use EWS to apply retention policy to items in a default folder
  2. Script to set retention tag on default folder items updated to v1.1.1
  3. Default folder retention tag script updated to 1.3
  4. Updated script that applies retention tag to items in a default folder [This article]

Edit 12/1/17: The issue with the retention tag GUID not being set by Exchange Online has been resolved.  Therefore, I have removed the code that distinguishes between online and on-premises so that the GUID is always applied.

My script that adds a personal tag to items in a default folder stopped working correctly recently.  Even though it was applying a tag to matching items, the collection size was never getting smaller.  After investigating, I found that a change has been made in Exchange Online.  The property that holds the GUID of the applied tag is no longer used by Exchange Online.  This is also the property I am using to search for items to tag.  You can tell Exchange to set a value for the property, and it will respond without error, but it doesn’t actually update the property.  So my script kept getting items that had already been updated.  I suspect this has something to do with added support for labels from the Security and Compliance Center.

I have updated the script to instead filter items that do not have a retention period set.  To accommodate this distinction from how Exchange on-premises still operates, I have added the Environment parameter, which defaults to Exchange Online, so that the GUID will still be applied when run against an on-premises mailbox.  The code that handles changed result sets (where the number of items in the results changes while in the middle of processing) had a display issue where it would double the number of items processed (it didn’t affect the items, but was only a display issue); I have changed the processing loop to now retrieve all items first, then process them in one batch, rather than retrieving and processing them in 50-item batches.

  Set-DefaultFolderItemsTag.ps1 (9.4 KiB)

Delegate management module updated

The module has been updated to version 1.5.1. This version adds automatic support for localization of the Sent Items and Deleted Items folders. If the display name of those folders in the owner’s mailbox is not in English, the localized display name of the folder will be used when getting, setting, or removing delegates.

I have also added permission validation to the owner’s mailbox for the person executing a cmdlet. When using impersonation, if you don’t have permission to a mailbox Exchange responds with an error indicating as much. But if using full access, Exchange doesn’t respond with such an error, just failing on whatever request is being made. Usually when permission is the issue, the error contains “The specified object was not found in the store,” so the module checks for that error, informs you that it appears you don’t have permission, and then gracefully aborts the cmdlet.

Download the updated module and overwrite your existing copy.  If you were already using v1.5.0, keep your existing settings file so your specific settings remain. (9.2 KiB)

New form for creating travel time appointments in Outlook

Articles in the "Outlook Travel Time Appointments" series

  1. Add travel time appointments in Outlook
  2. New form for creating travel time appointments in Outlook [This article]

I still use my code to create travel time appointments in Outlook.  I updated it a little while ago, though, to streamline it.  It now uses a VBA form to ask for the travel from and travel to times in one dialog:

The travel time form now lets you create both appointments from one dialog.

This saves a little real estate in the ribbon because now you only need one button:

The custom button runs the macro for launching the form and creating the appointments.

It also saves time because you can have it create both appointments in one go.  If you don’t need the to or from appointment created, leave its field blank.  You can download the two form files below, extract them anywhere, then in the VBA editor you can click File->Import File… and select the .frm file. (1.6 KiB)

You can copy the updated macros below and paste them into ThisOutlookSession in the VBA editor (and delete the macros from the old version).  The OpenOutlookFolder macro hasn’t changed, but is included for convenience.  The CreateTravelAppointment macro is now called CreateAppointment and the only change is that it sets the appointment’s category to Travel.  The CreateTravelToAppointment and CreateTravelFromAppointment macros are now combined into one called CreateTravelAppointment.  This is the macro you want your ribbon button to execute.

Delegate management module updated to v1.5.0

It had been some time since I updated the delegate management PowerShell module. I started to update it last year after I decided to move the admin configuration settings to their own file, but didn’t complete it.  After a user of the module recently contacted me about adding one of the supported options for delegate meeting forwarding, I went back and finished updating it.  These are the changes:

  • Configuration settings have been moved to a file.  This allows for drop-in updates to the module without you having to reconfigure your settings again.  The file, DelegateManagementSettings.xml, should be located in the same directory as the module.  A default file is included in the download, but you can also use the new  Set-DelegateManagementSetting  cmdlet with the  CreateDefaultFile  parameter to have the module create one for you.  You can run  Get-DelegateManagementSetting  to view your current settings.  If you want to change one or more settings for the current session, the  Set-DelegateManagementSetting  cmdlet can do that, and you can use the Persist parameter if you want the changed setting to be written to the file.  (Comment-based help is available for both new cmdlets, e.g., Get-Help Set-DelegateManagementSetting .)
  • The following functionality has been moved from static values to configurable settings: whether to use Autodiscover, specifying an EWS URL, whether to use impersonation, and if permission should be added/set by default for the Deleted Items and Sent Items folders when adding a delegate.
  • The Azure AD module is no longer needed for managing delegates in Exchange Online.
  • The ability to specify that meeting requests for the owner/manager should not be forwarded to delegates.  (This is supported with Exchange 2013+ and Exchange Online mailboxes.)  NoForward has been added as a valid value with the MeetingRequestDeliveryScope parameter.
  • All functions for connecting via PowerShell to on-premises Exchange and Exchange Online have been removed.  Connection to the appropriate environment should be handled externally to the module, as would usually be done with any other module or cmdlet.  The exception to this is for Exchange Web Services (seeing as it is a stateless connection).  If managing delegates in Exchange Online, the first time you run a delegate cmdlet it will prompt for EWS credentials, which will be used for all future cmdlets in the same shell.  On-premises will use the current user credentials.  If you would like the module to be able to store and recall credentials automatically (in Credential Manager, for example), specify credentials for on-premises, or other credential options, let me know.

The updated module, along with a settings file, is here: (9.2 KiB)

Meeting cancellation script updated

Articles in the "Meeting cancellation script" series

  1. Cancel future meetings in a mailbox
  2. Meeting cancellation script updated [This article]
  3. Meeting cancellation script updated for large result sets

I have updated the Cancel-MailboxMeetings.ps1 script. These are the changes:

  • Changed the date parameters to align with Microsoft’s Remove-CalendarEvents cmdlet.  StartDate is now QueryStartDate and EndDate is now QueryWindowInDays.  The default time frame is still one year from the query start date, but is now specified as an integer of days instead of a specific date.
  • Added a preview mode via the PreviewOnly switch parameter (also to align with Microsoft).  Using the parameter will list which meetings (by subject and start date, first occurrence start date if a recurring meeting) would be affected.  To provide enough detail, it will indicate if it is a standalone meeting or series and whether the meeting is being canceled (because the mailbox is the organizer) or declined (because the mailbox is an attendee).  If you use the EndOrganizedRecurringMeetings parameter, it will also use wording to show that a meeting series’ end date would be updated instead of canceled.
  • Added detection of modified occurrences that will be lost if a recurring series’ end date is changed.  Because exceptions to a meeting (only modifications, not deletions) are removed when a series’ end date is changed, the script checks for modified occurrences that occurred in the past of the query start date and prompts you to confirm that you want to update that meeting.
  • Added a switch parameter named SuppressLostExceptionPrompt.  Use this if you want to prevent the confirmation prompt about lost modified occurrences and have it change the end date anyway.  This parameter only has an effect if also used with EndOrganizedRecurringMeetings.
  • Although added only while I was testing, but decided to leave in, are some extra details written to the screen if you use the Verbose parameter.

The code in the first post has been updated, as has the downloadable version:

  Cancel-MailboxMeetings.ps1 (12.7 KiB)