Microsoft Office 365

Synchronize Shifts from Microsoft Teams to Outlook Calendars

Have you ever wanted to synchronize shifts in Microsoft Teams to evens in Outlook calendars? This is how you can do that!

Shifts

There is an updated version of this post available here: Synchronise Microsoft Shifts with Outlook Calendars

Today, one of my colleagues asked me if it was possible to forward my shifts schedule to my outlook calendar, so that when my work is scheduled in Shifts I receive alerts like for any other calendar event.

Shifts is an app in Microsoft Teams that can be used to schedule work for your team members. But most users sit within their Outlook apps working on emails.

Synchronize Shifts from Microsoft Teams to Outlook Calendars

My response was of course, Yes! Power Automate can do all of that!

So where do we get started?

First of all it is important to understand how Shifts is used before we look at synchronizing shifts. My colleague stores the task details in the notes of a shift. Sets the start and end time and then that is it.

Now he would manually create a diary entry in the calendar and share that with the persons expected to complete the shift. The events that I will create in this flow are owned by the service account and then the assignee is invited to the event.

Just imaging if he could synchronize shifts to outlook in an automated way!

The Trigger

All flows in Power Automate start with a trigger and this synchronize shifts flow example is no different. I’m running this flow on a schedule of every 30 minutes.

Recurrence trigger

During the development I used a manual starting flow, but in the long run I want to run this flow on a regular basis.

New Line action

Later on in the flow we will need to handle new lines. For this I’m adding a compose action and rename this to New Line.

The new Line action may look empty but it is not. It has a new line in it. Just hit that enter key once and you will have the job done!

Getting the shifts

Then I want get all the shifts for everybody starting from today. I will not synchronize shifts that are older.

In the below example of List all shifts, I’ve limited the number of shifts to 50. But this can of course be increased if you need to synchronize shifts beyond the first 50. It will just depend on how much you have to schedule and how many shifts you want to synchronize.

I’m also not interested in anything in the past. So I will restrict the action by setting the From start time using the utcNow function.

The odd thing however is that this will now only give me up to 50 shifts somewhere in the future. Most likely the most recently updated shifts. This is not what I want.

By setting the end time as well this problem goes away. So I set the end time to 4 weeks from now using the following formatDateTime expression that then I managed to get a lot more shifts.

formatDateTime(adddays(utcNow(),28), 'yyyy-MM-ddT00:00:00.000Z')

Get the calendars

Then i’m getting my calendars in Outlook for the service account that runs my flow.

Get Calendars action

This is a bit of a clumsy action as I can’t filter it to the one that I want. But later in my flow I will filter out the right one.

The Get Calendars will give us an array of calendars, even if we only have one calendar that we are interested in. It’s possible to process all of these calendars and use a condition but why not filter the unwanted ones out.

Filter Calendar

The compose action after the filter is there to turn the array into a single item. That way we avoid one of those unwanted apply to each steps making the overall Synchronize Shifts to Outlook Calendar events process that bit easier to understand.

All I have to do is use the following expression:

first(body('Filter_Calendar'))

Process the shifts

Now that I’ve got hold of the calendars and the Shifts I can step through my data.

Apply to each going through each calendar

For ease of use I’m now adding my shift by setting a compose action to

items('Apply_to_each_Shift')

This especially helps to debug the flow as the compose action displays my data for the shift that I’m processing.

Start time and end time

I’m now going to collect the start and end time from the shift.

This is where trouble starts. In Shifts, you can have draft shifts and shared shifts. Once a shift has been shared the format of the data changes. this means that sometimes I find my data for a shift in a json property sharedShift and sometimes in draftShift.

To get my start time I’m using the following expressions that will pickup the start time form the shared shift or draft shift.

if(equals(items('Apply_to_each_Shift')?['sharedShift/startDateTime'],null), items('Apply_to_each_Shift')?['draftShift/startDateTime'], items('Apply_to_each_Shift')?['sharedShift/startDateTime'])
Shift Compose action followed by Start time compose

If that wasn’t bad enough. The time is also 1 hour out. My current time zone is British Summer time and this will need to be corrected.

Time and correcting time

Time in this flow is going to be a bit of a pain. As we will see later on not all actions use the same time zone method.

The time that I get for my shifts are 1 hours out. to correct this time I used the following code in a compose action.

addHours(outputs('Start_Time'),1)
Corrected start time to move to right time zone

This is the wrong way of doing things of course. But converting time zones is something that I will address in a separate post.

The expression I used here is:

addHours(outputs('Start_Time'),1)

Then the same needs to be repeated for the end time

End time

Notes in Shifts

And finally I’m going to take the notes from the shift

notes
if(equals(items('Apply_to_each_Shift')?['sharedShift/notes'],null), items('Apply_to_each_Shift')?['draftShift/notes'], items('Apply_to_each_Shift')?['sharedShift/notes'])

Now to make sure that the notes don’t have nay quotes and other characters causing problems I’m adding an compose action with the following expression:

replace(replace(replace(replace(outputs('notes'),'\/','\\\/'),'&','and'),'''','\'''), outputs('New_Line'), ' ')
escaped notes

Create the calendar events

I will need to know who is assigned to a shift first. All I have is a user id

items('Apply_to_each_Shift')?['userId']

This user id can be used in the get user profile action to get a user’s email address.

Get user profile

We are now ready to synchronize shifts.

Checking if a Calendar event already exists

I’m going to need 3 actions to check if my event already exists in the calendar.

I will try to find all the events by checking their subject. The subject will be set to the notes that I collected earlier in this flow.

The notes however are not unique. Therefore I will use the shift id which is stored in the body of my event to filter down to a single event in the calendar.

Now all we have to do is count the number of items that the filter array action is returning and we will know if the Outlook calendar contains an event already or not.

The way to count the number of calendar events found is:

length(body('Filter_array'))

Do we need to create or update an event? Well that depends on the number of events found. If we find one then we do an update and otherwise we will do a creation of a new event.

We could use a condition for this as shown below, however I’m preferring the Switch action here!

create event

Why use a switch?

Well I actually might have 3 situations.

  1. The event doesn’t exist
  2. The item already exists
  3. Multiple items are found ( this really shouldn’t happen but just in case…)
using a switch on the events

Create an event

If there are none found then the create event is easy. Just set the Subject to the notes. and the start and end time with a bit of date formatting magic will get the times sorted out.

formatDateTime(outputs('Corrected_Start_Time'),'yyyy-MM-ddTHH:mm')

Note that the corrected date is used here.

Event does not exist

Update an event

When there is already an Outlook calendar event available and update is done. But the update should only be done if there is a change to be made.

update event

The conditions in the no updates needed will compare the values of the existing item with the new values. The can be done with the following 4 expressions:

convertToUtc(items('Apply_to_each_2')?['start'],'Central Europe Standard Time','yyyy-MM-ddTHH:mm:00.0000000')

formatDateTime(outputs('Start_Time'),'yyyy-MM-ddTHH:mm:00.0000000')

convertToUtc(items('Apply_to_each_2')?['end'],'Central Europe Standard Time','yyyy-MM-ddTHH:mm:00.0000000')

formatDateTime(outputs('End_Time'),'yyyy-MM-ddTHH:mm:00.0000000')

So we start with a condition checking the values of the existing event with the values of the updates that we want to make. If this results in the flow finding updates to be required an update event action will be the final step.

update event details

Some more thoughts

In this post I ignored the try catch pattern, just so that I can keep the post simple. But of course you should implement this with the Try Catch pattern in place so that errors are handled.

Share
Pieter Veenstra

Business Applications Microsoft MVP working as the Head of Power Platform at Vantage 365. You can contact me using contact@sharepains.com

View Comments

  • But where do I start on the Power Automate page? My Flows? New Flow? You skipped all the wizard questions?

  • Could you post the full flow? Im a little confused on how it all works together when its broken up like this.
    When do I start creating actions out side of “apply to each shift?”

    • Hi Luka,

      for the first image:
      outputs('escaped_notes')
      formatDateTime(outputs('Start_Time'),'yyyy-MM-ddTHH:mm')
      formatDateTime(outputs('End_Time'),'yyyy-MM-ddTHH:mm')

      For the length expressions:
      length(body('Filter_array'))

  • Hi, and thanks for this huge work.
    I tried to implement it, but I encountered an issue, when I tried to save the flow:
    Flow save failed with code 'InvalidTemplate' and message 'The template validation failed: 'The action(s) 'escaped_notes' referenced by 'inputs' in action 'No_update_needed' are not defined in the template.'.'.

    I check the expression outputs('escaped_notes'), but no errors seen...

    Do you have any idea ?

    Thanks.

    • Hi David,

      It sounds, like the action "escaped notes" doesn't exist. Can you check the compose action that is called escaped notes and check if things are spelled exactly like that ( case sensitive? ). Power Automate will replace all spaces form the actions with an underscore ( _ )

  • Hi,
    I have a working flow running now, but one issue remains for me for it to perform well. Do you have any tips on how you would go forward to make the flow delete the shifts that are already synced with the outlook calendar if they are removed from the Shifts calendar?

    • Do you know which part is slow?

      I wouldblook at using Microsoft Graph and remove any nested apply to each steps. Can you share any details on the amount of tems that you are syncing?

  • Thank you for the response first of all. I don't think it's is running slow, so that's not a problem. The flow synchronizes the shifts of one team (~10 people) for the next 90 days, and recur every afternoon. It synchronizes from UtcNow() and 90 days forward, and it doesn't add duplicates if there already is a shift there in outlook. The only problem is that if changes are made in the teams shift plan, the flow will not delete the shift that someone else took over. I haven't tried microsoft graph before, but will check it out.

    • So far I haven't looked at deleting. My client didn't have a need for that. But if any future client wants that then Ibam sure it would be possible to implement with a flow.

1 2 3 5

Recent Posts

Introducing 8 AI Functions for Dataverse in Power Apps

Recently Microsoft added AI Functions to Dataverse that can be used in Power Apps. In…

1 day ago

Copy and paste Scope steps in the new Power Automate Designer

One of the outstanding issues with the new Power Automate Designer is Copy and Paste…

1 week ago

Receive the available storage within your SharePoint Online tenant

Within the SharePoint admin centre there is that little detailed overview, telling you the available…

1 month ago

Options for Documenting Your Power Apps: Comments, Code, and Controls

Within Power Apps there are various ways to document your app. In this post I'm…

1 month ago

2 ways to duplicate SharePoint Lists to support your Power Apps

Recently I've been asked quite a few times to duplicate SharePoint lists as part of…

1 month ago

Update a Hyperlink Column in SharePoint with Power Automate

Today, I was asked about how to create a lookup to a document or item…

1 month ago