Add travel time appointments in Outlook

Articles in the "Outlook Travel Time Appointments" series

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

I like having travel time appointments in my calendar so that my free/busy accurately reflects that I am unavailable, but also so that I am reminded with enough time that I need to leave. I used to use an add-in from Instyler (no link since the site is no longer active) that allowed me to add travel to and/or from appointments to a selected calendar item, but it doesn’t work with Outlook 2013. So I chose to write a macro several years ago to do it. Like my attachment rename macro, when someone asked about using it in a newsletter, it gave me an opportunity to fix the main limitation in my use case: multiple accounts.

The original code leveraged the Applicaton.CreateItem() method, which creates the item in the default folder of the respective item type (olAppointmentItem, in this case). But I have long had two Exchange accounts in my profile, work and personal. I couldn’t get it to create the appointment item in the selected calendar. So I figured out how to open a particular calendar based on the folder path of the selected calendar.

The macro works against single-occurrence appointments and meetings. (Working with recurrences is messy.) There are actually three subroutines and one function. Two of the subs are for indicating which type of travel appointment to create, to or from. The third sub is for actually creating the appointment item, and the function is for opening the target calendar. I modified the ribbon for appointment items to add a button for creating a “travel to” appointment and a button for creating a “travel from” appointment. Each will prompt for how long the travel time should be, defaulting to 30 minutes. The travel to appointment sets a reminder, but the return travel appointment does not. It also uses the subject of the selected item to construct the subject of the travel time appointment. Paste these four code blocks into the VBA editor and then you can assign your added buttons to the CreateTravelToAppointment and CreateTravelFromAppointment subroutines.

Use EWS to apply retention policy 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 [This article]
  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

When working with retention policies and the types of tags you can apply to folders and items, you can assign a personal tag to any item and to any custom folder. You cannot, however, assign a personal tag to a default folder (such as Deleted Items), even if a retention policy tag has not been assigned to the folder. This means that if no default policy tag has been assigned to the policy, the items in that folder will never expire. The only way for a user to expire items in that folder is to assign a personal tag to each and every item. For the deleted items folder, that can be a lot of items, and its contents are changing daily.

This script uses the EWS Managed API to get all items in the Deleted Items folder that do not have a tag assigned to them and then assign a specific tag to each. To start, you need to connect to EWS:

I am using a function because I lifted this code from another one of my scripts, so calling this function returns an object for the Deleted Items folder. Based on the credentials format and EWS URL, you can see that I am connecting to Exchange Online. This can be easily changed to support on-premises. I am not using autodiscover because it is very slow when querying EXO, and since all EXO mailboxes can be accessed with the a single FQDN, it is simpler this way. I am not using impersonation because I am running this against my own mailbox, but you can uncomment the line if you choose to use it.

To search for items that do not have a tag assigned, this is used:

The MAPI property that indicates whether a tag has been assigned (whether implicitly or explicitly) is an extended property that you declare and add to a property set. The property is binary and contains the GUID of the tag, but since I am only looking for items without a tag, I only care if the property has a value. To do this, you first define a search filter object that says to include items where the property exists. Then to negate that, so I can find items without that property, you create another search filter object using the Not class that contains the other search filter.

Then you can apply the tag and its corresponding days until expiration value:

To assign the tag, you need to know its GUID. You can get this from PowerShell, but if you don’t have access to Exchange to get this, you can manually assign the tag to an item, then use MFCMAPI and look at the item’s properties for the value in PR_POLICY_TAG (0x30190102). The RAW representation of the GUID will need to be converted to the proper byte order, which can be done in a variety of ways, but this site is an easy way. When assigning the tag you also have to set the property that contains the number of days after which the tag is configured to expire. In my case, it is a 30-day tag. (I tested not setting the property and the result in Outlook does show that the tag is assigned but it doesn’t show the expiration date. I don’t know if the property is only used to calculate the displayed date or if MRM actually uses it when expiring items.)

The complete script can be downloaded from the link or copied from the code below. It includes checking for the EWS Managed API and a progress bar (since this is not a fast operation).

  Set-DefaultFolderItemsTag.ps1 (9.4 KiB)


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)


Update to Lync call handling script

Articles in the "Lync call handling script" series

  1. Trigger something whenever you’re on a Lync call
  2. Update to Lync call handling script [This article]
  3. Lync call handling script updated

Yesterday I discovered that my Lync call handling script breaks when the Lync client is closed. I thought I had tested it and found that the client didn’t actually need to be running for the Lync client class object to be created and used. Not only was I wrong about that, but the existing client class object becomes invalid when Lync is closed. I suppose it isn’t that big of a deal to say that you need to rerun the script if you close Lync (and not run it until after Lync has been started), but I took it as a challenge to overcome that need.

I need to know both when Lync has started and when it has been closed. I started with the ClientState enumeration used in the LyncClient class. Since I already have an event for the client state, I figured I could act when the ShuttingDown state occurs. Uh, no, since that state never happens when Lync is running as an interactive process, but only when you are programmatically running Lync without a UI. The client state becomes Invalid when Lync is closed, so then I thought I could act when that state occurs. Also no, since an event doesn’t fire when the state becomes Invalid. I am now forced to look outside of the Microsoft.Lync.Model namespace.

What I decided to do is monitor the Lync process itself. I moved the creation of the client object into a function that can be called as needed, instead of just the one time when the script is run.

The function checks to see if the communicator.exe process is running. If not, it will check every 15 seconds to see if it is. When the process has started, an event is registered that will fire when the process has exited. Now that the process has started, the client can be created. The LyncProcess-Handler function that is called by the event action unregisters the script events and then calls the above function to start the monitoring of the Lync process again. This way the script only has to be run once and it will accommodate whether Lync isn’t running in the first place and if it closes later.

The original code that creates the initial event registrations for contact activity and client state have also been moved into a function so it can be called again whenever the Lync process is started.

The script has been updated (available below), as well as the full version that is included in the body of the first post.

  Monitor-LyncSelfActivityChange.zip (640.5 KiB)

Trigger something whenever you’re on a Lync call

Articles in the "Lync call handling script" series

  1. Trigger something whenever you’re on a Lync call [This article]
  2. Update to Lync call handling script
  3. Lync call handling script updated

A coworker of mine works out of his home office and wanted his family to know when he is on a phone call so they won’t interrupt him. He already had a network-controllable light that he put into the hallway outside the office. What he needed was a way to trigger the light when he is on the phone (he is enterprise voice-enabled in Lync). Here are the details of the PowerShell script that I wrote for him.

The script leverages the API in the Lync SDK. You only need to install the Lync SDK runtime library, but there isn’t an individual download for that. To install the Lync SDK, however, requires that Visual Studio is installed. That may not be the case for many of you, so I have included the redistributable runtime library (a mere 750KB MSI) in the script download.

The runtime library installs the assemblies into the global access cache (GAC), so to use one of them in a script, you need to load the assembly:

You can then create an object for the Lync client:

In order to know someone’s current presence state (or activity in Lync parlance), you work against a contact object. Therefore, to know your own activity, you first need to create an object for yourself as a contact:

To get your current activity, you use the GetContactInformation() method, with the argument being the type of information to retrieve:

The activities that indicate you are on the phone, or off-hook in telecom parlance, are In a call and In a conference call. To use this information to do something, you need to know when Lync changes to and/or from these activities, and for that you can use .NET Framework object events. Instead of constantly querying for your activity, you can use events to have PowerShell act when something happens. The Contact class has an event for when contact information changes, and here is the list of what properties will result in that event firing.

To register for an event, you have to include the object whose class contains the event, as well what action to take:

$event is one of the built-in variables that the event can include in the response. And this is the function that will run as a result of the event firing:

Since the list of changed properties that triggers a changed contact event is numerous, the first thing the function does is check to see if the changed property is Activity. The event doesn’t include what the activity is, so you have to get the current activity. If the activity is one of the values that indicates you are on the phone, it calls a function to run your code that does something. (I did it this way, instead of having you put your code inside this function, so that it is easier to paste your code into a function at the beginning of the script and for doing so if there are future versions of the script.) A global variable of $offhook is set. This allows the script to keep track of whether you are off the phone, after having been on the phone, in subsequent event firings. If you are no longer on the phone, after having been on the phone, it calls another function to run your code that “undoes” whatever it did when you got on the phone.

The script will normally not output anything to the screen, allowing you to run it in a shell you are using for something else. If you are testing your code or just want to see more information about your activity changes, you can include the -Verbose parameter. This will result in more details being output to the screen as they occur, such as current activity, functions being called, and whether the client is signed in. If you do this, you will likely want to run the script in its own shell so it doesn’t get in the way of what you might be doing in the first shell.

Now, to put all of this into a reusable script, there are other things to consider. If you sign out, or get otherwise disconnected from Lync, the contact object is no longer valid and has to be created again when you sign in. To account for this, I use another event that fires on client state changes. If not signed in, it unregisters the contact change event, and when you get signed in again it creates the contact object and registers the event.

Another consideration is the scope of the script. When you run a script, its code is run in its own scope, terminating when the script reaches the end. Since the registered events will call functions after the script has completed, they need to be run in a scope that stays resident upon completion, i.e., global scope. To do this, you dot-source the script by entering a period and a space before the path to the script. To account for this, the script checks to ensure that it was dot-sourced when executed, otherwise it presents an error.

As for using the -Verbose parameter, I found that it worked fine when using Write-Verbose in the script’s main block and initial functions. It did not work in the functions called by the events. The function would just hang at the point of running the cmdlet. I have not been able to find out why this is the case, so I wrote a function to mimic the output of the Write-Verbose cmdlet. This function is used in the functions called by the events.

To make use of this script in your environment, you will want to modify the first function, called OffHook-Action, by replacing lines 13 and 19 with code to “do” something and “undo” something, respectively. If those lines of code are dependent on other functions, you can put them in the subsequent region that is labeled for your dependent functions.

As a side note, since I don’t have a network-controllable light at work, I needed something else for it to do so I could be sure it was running correctly. I use Winamp, so it was appropriate for me to have it pause when I am on the phone, and resume if I am off the phone (but only if it is already paused). The code in the script, therefore, includes functions for controlling Winamp and reference to a COM object the provides this. I left it in so you can see an example of the custom code that you will need to put in your version. It is worth nothing that the COM object for controlling Winamp is a 32-bit library. If you are running PowerShell on a 64-bit system and reference a library that is only compiled for a 32-bit system, you will need to run the x86 version of PowerShell (there is a Start Menu item for it). (The Lync SDK runtime is accessible in both versions of the shell.)

The download of the full version of the script (and the Lync SDK runtime library) is below, but you can also see the full script by expanding the code block below:

  Monitor-LyncSelfActivityChange.zip (640.5 KiB)