Delegate management module updated to v1.4.5

The module has been updated mostly for fixing issues when working with Exchange Online. The first version that supported it didn’t account account for object properties that are different compared to on-premises, as well as how to get user information. These are the changes in this version:

  • Fixed when using a default connection mode of EXO so that the rest of the module knows it.
  • Added option to not use autodiscover when using EXO since those lookups can sometimes add a lot of time to the cmdlets running.  If default mode is EXO and you don’t want to use autodiscover, uncomment that line below the default mode.  If using EXO on-demand, you can set the option with the DoNotUseAutodiscover switch parameter of Set-DelegateManagementMode. (The cmdlet’s help has been updated to reflect this.)
  • Added usage of the Azure Active Directory module when using EXO mode.  This means you need to have the WAAD module installed to work against Exchange Online.  Since that module is 64-bit only, you can only run the delegate management module in a 64-bit PowerShell session.
  • Fixed (hopefully and finally) the Write-Progress prompt that some people were getting that interrupted the cmdlets.  (Thanks, Jim.)
  • Fixed getting Send As permission in EXO due to it using a different cmdlet.
  • Fixed getting folder permissions in EXO due to the object properties being different.
  • Added removal of Deleted Items and Sent Items folder permissions when removing a delegate.

There are other things I have discovered need fixing: Exchange cmdlets loaded by the module are not accessible outside of the module; Exchange cmdlet errors are not caught in PowerShell 4 so the module cmdlets keep running after a terminating error would be detected if running in PowerShell 2; if multiple objects are pipelined to Get-MailboxDelegate and one of them does not have a mailbox, the cmdlet terminates without processing the remaining objects in the pipeline.

I also still intend to add support for hybrid mode.  It is more complicated, though, such as with adding delegates since I need to account for attempts at cross-premises delegation, which isn’t supported.

  DelegateManagement.zip (9.2 KiB)

Delegate management module updated to support Exchange Online

The module for managing Exchange mailbox delegates has been updated with support for Exchange Online. In its current version (v1.4) you can use one mode or the other. The default mode is on-premises, but you can change this on demand to use Exchange Online by using Set-DelegateMananagementMode, a new cmdlet added in this version. If you change it on demand, you will be prompted for your Office 365 credentials. If you will be exclusively working with Exchange Online, you can change the line near the top of the module to default to using that method. In that case, you will be prompted for credentials the first time you run a cmdlet.

It is my intention to update the module to support a hybrid environment, but I first need to set up one in my lab in order to test it.

Delete all empty subfolders of Deleted Items

At my company we use a retention policy to delete items in the Deleted Items folder that are older than 14 days. (This is implemented via Managed Folders, a.k.a. MRM 1.0.) Managed Folders, and even Retention Policies (MRM 2.0), act on items, not folders. A common scenario, though, is that users will soft-delete a folder as an easy way to move all of its items to the Deleted Items folder. After 14 days, the managed folder policy will delete the items in that folder, but it leaves the folder itself. Over time, users may have dozens of empty folders in the Deleted Items folder, and the only way they will ever be deleted is if users delete them one by one. It goes without saying (though I am obviously saying it anyway) that users don’t do this, so lots and lots of empty folders build up over time.

I don’t like this, so I wrote a script that deletes the empty subfolders of Deleted Items for all the mailboxes in the organization. (I actually wrote two scripts, one for the Deleted Items, and one for any specified parent folder.) The script uses the EWS Managed API. You can have any version installed, and it will check for one, starting with the most recent version. The intent of the script is to process all mailboxes in the org, so it retrieves all mailboxes and pipes them into a ForEach-Object loop:

You can always change this line to restrict the returned mailboxes, or use the other script in the download, which supports pipelined input. Note the use of the parentheses when getting the collection of mailboxes. This is to avoid the pipeline constraint when using implicit remoting. The first function called is to get the folder ID of the Deleted Items folder for a given mailbox:

The FolderScope parameter is set to the Deleted Items folder, which is piped to Select-Object to limit the result to the top-level folder, ignoring any subfolders that will also be returned. The FolderId property is the function’s output.

The next step is to convert the folder ID from the management shell to one that is usable by Exchange Web Services:

Any plus (+) signs are transposed to hex since they get in the way. A typical connection to EWS is then established. The AlternateId class is used to specify an ID and what format it is in. Folder IDs in the management shell’s cmdlets are in the OwaId format. To convert them to the EwsId format needed by EWS when referencing an item, the ConvertId() method of the ExchangeService class (the base class of the EWS connection) is used. The UniqueId property is the function’s output.

The last function called is to actually delete any subfolders of the folder with the ID the previous function returned:

You can see this function redundantly establishes a new EWS connection. This is because I was adapting functions used elsewhere for this script and I didn’t optimize it. The function may take less time to complete by using a session that has already been established, but I didn’t determine that or change the previous function to create the session variable in the parent scope so it could be reused. After the session initialization, the function connects to the mailbox and binds to the Deleted Items folder using the folder ID from the previous function.

In order to get the list of empty subfolders, a search is performed for folders that contain no items. This is done by creating a view that will hold the results and defining a search filter whose only restriction is that it contains no items. The search is executed and then looped through each returned folder. Because the search is recursive, the loop will ignore any folder whose parent folder is not Deleted Items. The folder is then deleted.

Write-Output is used, not only to save puppies, but so that you can see what is happening while still allowing you to save the output to file. This can be accomplished with Tee-Object, e.g.:

One caveat of the script as written is that a subfolder that also contains subfolders, which may contain items, will still be deleted. Since I am deleting subfolders of Deleted Items, whose items are deleted after 14 days anyway, I am not too concerned about inadvertently deleting items. If this will be a problem for you, you will need to modify the script accordingly. (I may do so in the future.) Another workaround if this is a problem, is to use the second script (explained next) which has a parameter for skipping folders that have subfolders, regardless of their content.

While writing the script, I thought it might be beneficial to be able to run this process against any parent folder. So I converted the Deleted Items script into one that does just that. You can run this script with parameters that will do the exact same thing as the first script, but it also lets you specify any of the other folder scopes or a custom folder path. You can also indicate that you don’t want subfolders that have subfolders to be included (to avoid the potential issue using the first script). Lastly, this script requires that you provide a mailbox to act on. This can be manually specified with the Identity parameter, or you can pipe any number of mailboxes into it.

The FolderScope and FolderPath parameters are mutually exclusive. Use FolderScope when you want to easily specify one of the inbuilt scopes available to the Get-MailboxFolderStatistics cmdlet. This could be, for example, Inbox, RssSubscriptions, or SentItems. Use FolderPath when you want to specify a literal path to the parent folder, such as /Inbox/Subfolder1. The path to the folder is validated to ensure it starts with a forward slash and does not end with one, which matches the output of the Get-MailboxFolderStatistics cmdlet.

The functions in this script are broken down to be inline (not using functions) in order to support pipelining. The other change is supporting the exclusion of subfolders that have subfolders. This is done by modifying the search to use an additional restriction:

If the parameter to exclude subfolders is used, a search filter collection has to be used because of the need for multiple criteria. You define the search filters the same (using a unique name for each variable), but then you create a search filter collection object, add the search filters to the collection, and then use that object as the search filter in the search execution.

Both scripts are included in the download file, or you can copy the code below.

  Remove-EmptySubfolders.zip (3.4 KiB)

Remove-EmptyDeletedItemsSubfolder

Remove-EmptySubfolder

PowerShell module for managing Exchange mailbox delegates

I have converted my script that displays a mailbox’s delegates into a module that adds the ability to add, modify, and remove a mailbox’s delegates. The benefit of the module is that it negates the need to create an Outlook profile for the owner/manager in order to manipulate his or her delegates. The module requires any version of the EWS Managed API and the Exchange cmdlets via remoting, as detailed below.

Upon importing the module, it will check for any version of the EWS Managed API, starting with version 2.0 and working its way back. This allows for the freedom to have any version installed instead of requiring everyone who uses it to ensure they have the specific one I used when writing it. After the API is loaded, it will check for the Exchange cmdlets. If not loaded into the shell, it will retrieve a list of Exchange servers from Active Directory and attempt to connect to one until successful. If neither the API nor remoting to Exchange is successful, the module will fail to load, telling you why. (The module doesn’t distinguish between the cmdlets being available because they were locally loaded from the snap-in or from remoting. However, since certain cmdlets will fail when not executed remotely because they bypass RBAC, you need to be sure that you have not locally loaded the snap-in.)

Access to a mailbox is done using full mailbox access. If you want to use impersonation, you will want to uncomment line 96. Granting impersonation rights is explained here. The URL used for EWS is determined by autodiscover of the owner/manager mailbox.

These are the cmdlets the module exposes:

Get-MailboxDelegate

The alias for Get-MailboxDelegate is gmd.  It is basically the same as my Get-Delegates.ps1 script, but it has gotten a makeover to support pipelining into and out of. The -Identity parameter (aliased to -Owner and -Manager) is any standard Exchange identity (display name, email address, alias, dn, etc.) and it supports pipelining by property name. If the objects you are pipelining into Get-MailboxDelegate don’t have a property name of Identity, then you will need to use a ForEach loop and use $_.PropertyName to designate which property should be used.

Without any other parameters, all delegates will be retrieved. If you want to get only a specific delegate, you can use the -Delegate parameter. The default output will be to list the properties, but since it is now a collection of objects, you can choose to output it to a table, to a grid view, or export to a file, using the appropriate cmdlets. You can also use these output cmdlets to select only the properties you want. For example, if you only care about the private items property you could use ft owner,delegate,viewprivate. Or, if you only want those who actually can view private items, you could run something like this:

Note that I encapsulated in parentheses the command that I pipeline into Get-MailboxDelegate. This is necessary to avoid the concurrent pipeline limitation in remote PowerShell. It is only necessary if the command prior to the pipeline will be running a cmdlet that leverages remoting. Another option is to store the results of the prior command in a variable and then pipeline that into Get-MailboxDelegate.

All of the module’s cmdlets have built-in help, so you can use PowerShell’s help cmdlet to learn the details of all of them, such as the parameters and their descriptions, usage examples, etc.

Add-MailboxDelegate

The alias for Add-MailboxDelegate is amd. To use this cmdlet, provide an owner and a delegate. You can optionally specify folder permission for Inbox, Calendar, Tasks, Contacts, Sent Items, and Deleted Items; if private items are viewable; if meeting requests are to be received; and the owner’s global handling of meeting requests. I didn’t include the option of setting permission for Journal or Notes because, well, who uses them? The ability to set the permission for Sent Items and Deleted Items is to accommodate those who use GPO to have Outlook store messages sent from another user in that person’s Sent Items folder, and likewise for messages deleted from another mailbox. The option to set the meeting request delivery scope applies to the owner, not the delegate being added, so it is only necessary to include it if you are adding a delegate and you want to change the current setting.

Set-MailboxDelegate

The alias for Set-MailboxDelegate is smd. Use this cmdlet to change any settings for an existing delegate (or to change the meeting request delivery scope for the owner). Provide the owner and the existing delegate to modify and, optionally, which setting you want to change. All other settings will remain as is. If you want to change just the meeting request delivery scope for the owner, specify any existing delegate, but not any other settings (except the delivery scope). Unlike the valid roles for folder permission with Add-MailboxDelegate, you can use None if you want to remove a folder permission. If you want to remove the ability to view private items or to not receive meeting requests, use -ViewPrivateItems:$false or -ReceiveMeetingRequests:$false, respectively. The colon is necessary because both parameters are switches, so their inclusion alone in the command means true, whereas to explicitly set them to false means using the syntax above. (The cmdlet checks if either switch is actually present in the command, so don’t be concerned that not including a switch implies that it should be false.)

Remove-MailboxDelegate

The alias for Remove-MailboxDelegate is rmd. Provide the owner and the delegate to remove. That’s it.

All of the cmdlets perform the expected error checking: the owner is a valid mailbox; any delegate to add, modify, or remove is a valid mailbox; adding a delegate when the delegate already exists; modifying or removing a delegate that is not an existing delegate; using a valid (albeit the limited subset exposed in the API) role for a folder permission; and using a valid meeting request delivery scope.

Download the module or view/copy the code below:

  DelegateManagement.zip (9.2 KiB)


Configure SCOM to alert when an Exchange database fails over

Using Microsoft System Center Operations Manager 2007 R2 (SCOM 2007) to monitor Exchange 2010 is a job unto itself.  It takes a lot of time and knowledge of SCOM to effectively monitor your Exchange environment.  This is due to SCOM’s architecture, but compounded by the Exchange 2010 correlation engine used to generate alerts (which MS has done away with in Exchange 2013).  Anyway, one of the things SCOM doesn’t do is alert you in any way when a database has failed over to another node in a DAG.  I can only speculate why this is the case, but I assume it has to do with a failover, assuming it works successfully, is a non-service-impacting event and that you will presumably have other monitoring in place via SCOM or a different tool to alert you to the root cause of the event.

This doesn’t preclude the fact that I want to know when a database has failed over to another node.  To create an alert for this in Exchange 2010/2013/2016, you are limited to what Exchange puts in the event log in the first place.  Things related to databases and DAGs are in the Crimson logs under Microsoft-Exchange-HighAvailability.  The log to monitor for *over events is Operational.  Event ID 306 is what is logged on the Primary Active Manager (PAM) server when a database is moved to another node.  If you look at the description of the log, you see that it doesn’t contain anything more than the database name, the source and target servers, and any move comment:

 

Event 306 Description

 

The event description doesn’t distinguish between switchovers and failovers, so you might think there isn’t a way to be alerted only when a failover occurs.  Exchange logs, however, much more data than what is shown in the description.  Click on the Details tab and within the Friendly view, you will see what Exchange actually logs about the event:

 

Event 306 Full Details

Event with list of all parameters

 

You can see that Exchange does, in fact, log why the *over happening.  It notes whether it is a failover (ActionInitiator is Automatic) or switchover (ActionInitiator is Admin) and why the move is happening (SystemShutdown, NodeDown, FailureItem, or Cmdlet).  There are other properties that may be helpful for event log collection and/or alerting, too.

To have SCOM generate an alert for an event that indicates a database failover is starting, an alert rule needs to be created.  In the SCOM console, go to the Authoring navigation pane, right-click on Rules (under Management Pack Objects) and select Create a new rule…  Under Alert Generating Rules, then Event Based, select NT Event Log (Alert).  Assuming you have created a custom management pack for Exchange customizations, select that in the drop-down at the bottom of the window, then click Next.

Enter a rule name and an optional description.  Change the Rule Category to be Alert.  In the Rule target, click the Select button, then type in Mailbox, select it in the results pane, then click OK.  Setting the target to mailbox means that the alert will automatically apply to all mailbox servers, watching the appropriate event log on those servers, while not watching other servers where the event will never happen anyway.  When done, the screen should look similar to this:

 

The General tab when creating a new alert rule.

 

Click Next to proceed to the Event Log Type tab.  In the Log name field, enter Microsoft-Exchange-HighAvailability/Operational.  If you have access from the console to an Exchange server with the mailbox role installed, you can browse to it by clicking the ellipsis and changing to the server name, but you can manually enter the name.  If entering it manually, however, it is important to enter it exactly as listed above.  Then click Next.

In the Build Event Expression tab, enter 306 for the value of the first field (Event ID).  The event source can be confusing, depending on where you are looking at it.  For example, if using the native event viewer, the source will be displayed only as HighAvailability.  The source, however, is Microsoft-Exchange-HighAvailability, which can been seen in the Provider Name under System of the Friendly View of the event’s details.  So, enter this value for the Event Source parameter.

Since the alert should only fire for a database failover, not a switchover, you need to filter the event on another value.  To add this, click the Insert drop-down and select Expression.  Click the ellipsis next to the empty parameter name to bring up the Event Property dialog.  If you look at the common event properties drop-down, nothing in there looks like it will work, so how do you restrict the alert to a value in one of the fields listed in the Details tab?  This is where event log parameters come in.

The description of an event is built from a canned string in the event’s message file, often interspersed with one or more variables seen in the Details tab, known as parameters.  Each event defines its own parameters, any of which can be used in the description.  The fact that the database is failing over is noted in the parameter named ActionInitiator.  Parameter names, however, are not used when being referenced; instead, the index number of the parameter in the UserData/EventXML collection.  To determine which parameter number to use for ActionInitiator, use the Friendly View and count down the list of parameter names.  Note that parameters are stored in a 1-based array, so start with 1 (not 0).  ActionInitiator, therefore is in parameter 6.

Back in the Event Property dialog, click the radio button to specify a parameter to use and change the default value of 1 to 6, then click OK.  The Parameter Name will now say Parameter 6.  Set the Operator to Equals, and manually enter a Value of Automatic.  The Build Event Expression tab should look like this:

 

The event parameters to use to detect a database failover.

 

On the Configure Alerts tab you can customize the how alert should manifest: priority, severity, the description, etc.  Using the default event description in the alert description is probably fine, but you can opt to include one or more of the event parameters in the description.  For example, you could have the alert include the ActionReason (parameter 7) so you know why the failover is occurring.  Because this is an alert from a rule, as opposed to a monitor, it requires manual closure, which is a good thing in this case since the point is to be notified when a database fails over so you can investigate as needed.