.

📌How I Used EWS and PowerShell to Fix Broken Meetings

One of the topics suggested by our community was something about Exchange Web Services API in Exchange Server.

Exchange Web Services (EWS) is a cross-platform API that enables applications / scripts to access mailbox items such as email messages, meetings, and contacts starting with Exchange Server 2007. 

Some time ago, the Exchange product team announced REST API as replacement for EWS in Exchange Hybrid environments. It was expected that EWS would not be developed further. Some features with REST API worked even in pure on-prem environments, at least I know, some customers used it for their internal solutions.

However, in 2022 PG announced the end of preview for REST API in Exchange on-prem \ Hybrid. Thus, this is not supported anymore. And this is not the same for EXO, there upgrade to GRAPH API is required.

I would say, we have a lot of documentation regrading EWS architecture as described in EWS applications and the Exchange architecture, for example. A lot of useful examples are already available, that could be used as is or as a template.

One of useful resources for development I used personally is GLEN’S EXCHANGE AND OFFICE 365 DEV BLOG

Even if you have a lack of PowerShell functionality with its limitations, simple script with EWS could help with it. So, in this blog post I will review a practical case with EWS from my own experience in the past.

⚠️Case story

Changes with Time Zones are happening from time to time in different countries. When it happens, it can affect different OS and applications. As example, Long time ago, in July 2014, several changes were passed in Russia, which took effect on 26 October 2014. Almost all of Russia moved back one hour, so Moscow Time became UTC+03:00 again. Some areas changed offset from Moscow.

At the time I was working with several big customers as Russia, one if them was extensively used Exchange Server 2003 still and Exchange 2010. We expected a huge number of issues with meetings and appointments after these changes took effect.

Despite server-side problems, the latest Outlook clients at that time could convert date timestamps on the fly. But a lot of customers still used older versions. And issues with mobile clients were also expected. Most mobile devices didn’t stamp time zones correctly, using their own definitions, so it wasn’t possible to match such zones to well know zones definitions. And that was another part of the case.

I should notice that it’s not a case to provide some custom decision like this being Microsoft engineer, but as I was dedicated engineer        and a spent quit a lot of time being onsite, we decided to develop some solution that could eliminate those issues, or at least, help to reduce them.

As we’re talking about EWS, you can clearly draw a conclusion, that I decided to use EWS to recalculate and change time stamps (start and end time) for existing meetings.

I won’t go though specific parts we implemented – no need to change meetings, ended in the past, for example. Some optimizations also can be done to run this in parallel for faster processing in large organizations.  

But I will go through the main steps and points to highlight how this idea should work. So, if you’re interested, let’s proceed with next part 😊

🔎Connect to Exchange Server

Of cause, you can write the whole EWS Managed API application on C#, if you need it. In my scenario, I decided to proceed with Exchange Web Services and Windows PowerShell.

EWS managed API resources are available on github here.

Note. Do not install EWS Managed API on your Exchange Server. It will cause EWS webpool to crash periodically.

There are several options to load EWS API into your script. If you installed EWS API, you could use Import-Module to load it. Or you can even copy the corresponding folder to your server and load dll directly.

So, from the first, we need to load assembly and create new object for Exchange Web Services endpoint.

Here, we use default session credentials, but you can also pass credentials directly if you want.

[Reflection.Assembly]::LoadWithPartialName(‘Microsoft.Exchange.WebServices’) | Out-Null

$Service = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ExchangeService

$Service.UseDefaultCredentials = $True

Next, we need to define connection endpoint for EWS. Usually, I recommend to identify this one automatically via Autodiscover ($AutodiscoverEmail can be any appropriate email):

$service.AutodiscoverUrl($AutodiscoverEmail, {$True})

Or you can define url manually:

While connecting to EWS endpoint we expected that certificates should be trusted. Some developers enable their solutions to ignore certificate errors and you can find how to do that easily in different examples.   

Note, that service initialized with some default properties like RequestedServerVersion (2013 for 2013 and higher), TimeZone and etc. Some settings can be changed.  

Note, that you can also enable tracing for EWS for troubleshooting purposes

$Service.TraceEnabled = $true

🔄Impersonation

Before we can start working with specific mailboxes, we need to provide access to them. While working with a bunch of mailboxes or whole environment we can grant the impersonation role to a service account by using the Exchange Management Shell.

Impersonation enables a caller, such as a service application, to impersonate a user account. The caller can perform operations by using the permissions that are associated with the impersonated account instead of the permissions associated with the caller’s account.

In my case, we granted Org-Wide permissions, but you can also configure a scope.

New-ManagementRoleAssignment -name:impersonationAssignmentName –Role:ApplicationImpersonation -User:serviceAccount

For more information on how to do that, visit 🔗How to Configure Impersonation

Next, we need to impersonate a specific user account. You need to do that for each user, you want to process.

$Service.ImpersonatedUserId = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList ([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, ‘azubery@contoso.com’)

🔄Find appointments

Next step, we will bind to a well-know folder in the mailbox, called Calendar, as we’re going to work with appointments.

$CalendarFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar)

First, we will get a list of available appointments, using ItemView object and BasePropertySet.

$ItemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(1000)

$PropSet = New-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

$ItemView.PropertySet = $PropSet

$ItemResult = $CalendarFolder.FindItems($ItemView)

In this case we have 3 items in our result list.

FindItems returns some common item properties, and we can do some additional review and filter objects out based on them.

Next step is to get additional details for meeting we’re interested in and do what we want with this meeting. For that, we will define a new BasePropertySet and add properties we want. Below we add well-known property TimeZone as an example.

$PropSetToLoad = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)

$PropSetToLoad.Add([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::TimeZone)

To add extended properties we will need to define an appropriate commonly used property set. In out case we will use PSETID_Appointment:

$guid = New-Object Guid(“00062002-0000-0000-C000-000000000046”)

Review 🔗Commonly Used Property Sets for a list of sets. Known properties for them a listed and described in different resources, for example, 🔗here

Now we can add 2 properties with 2 different types as example:

$pPidLidTimeZoneDescription = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($guid, 33332, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String) 

$pPidLidAppointmentStartWhole = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition($guid, 33293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::SystemTime) 

$PropSetToLoad.Add($pPidLidTimeZoneDescription)

$PropSetToLoad.Add($pPidLidAppointmentStartWhole)

Load corresponding properties, for second element in our list:

$ItemResult.Items[1].Load($PropSetToLoad)

This meeting was created by user Annelie Zubery (organizer) and sent to Administrator account (attendee). We can see below that it was created in UTC timezone, without subject. Also, we can notice start and end time.

Review extended properties:

🔄Change appointments

In specific case with DST changes it want that simple that time. We needed to identify different properties of meetings and make appropriate changes. It’s important to know, if this item is a meeting or appointment, is it recurrent or not, did it happen already in the past (so we don’t care anymore), is it all-day appointment, is mailbox owner a meeting organizer and so on. Sometimes it’s required to stamp well-known properties or extended properties.

As all this staff is case specific, I will not pay a lot of attention to it. But all this information is available now in $ItemResult.Items[1] object.

Let’s change our meeting’s subject and Time Zone (well-known and extended).

$tz = [System.TimeZoneInfo]::FindSystemTimeZoneById(‘W. Europe Standard Time’)

$ItemResult.Items[1].StartTimeZone = $tz

$ItemResult.Items[1].EndTimeZone = $tz

$ItemResult.Items[1].SetExtendedProperty($pPidLidTimeZoneDescription, $tz.DisplayName)

$ItemResult.Items[1].Subject = “Subject Updated”

Next, we need to save our updates and send update to attendees. It depends on specific properties, what we would like to do.

If item is a meeting we should send updated. Otherwise send to none.

if ($ItemResult.Items[1].IsMeeting) {

   $send = [Microsoft.Exchange.WebServices.Data.SendInvitationsOrCancellationsMode]::SendToAllAndSaveCopy

} else {

  $send = [Microsoft.Exchange.WebServices.Data.SendInvitationsOrCancellationsMode]::SendToNone

}

If appointment is recurrent than auto resolve conflicts, otherwise overwrite.

if ($ItemResult.Items[1].AppointmentType -eq ‘RecurringMaster’)

{

  $resolve = [Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AutoResolve

}    else {

$resolve = [Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite

}

Finally, update item.

$ItemResult.Items[1].Update($resolve, $send)

Because our specific item was a meeting, we will send update to Administrator and with AlwaysOverwrite option.

Timezone, start and end time, subject, changed accordingly.

🔄MFCMAPI

🔗MFCMAPI provides access to MAPI stores to facilitate investigation of Exchange and Outlook issues and to provide developers with a sample for MAPI development.

This useful instrument can ne used for problem resolution and described in official Microsoft articles for specific cases.

However, while working with mailbox on low-level with EWS, it can be really useful to look for specific properties, their LIDS and so on. For example, below we can verify extended property ID and corresponding property set.

Be careful with MFCMAPI, as unwanted changes can harm your mailbox content.

End.


Leave a comment