Outlook 2016 GUI creates an appointment with extraneous attendee, causing Outlook.com and Outlook for iOS to show it as a meeting

Update: It seems that the resolution of the problem also involves Exchange Server (or whatever) for Outlook.com.

Update on 12 September 2018: The latest consumer channel of Outlook 2016 (version 1808), Outlook for iOS (2.94.0), Calendar app (version 16005.10730.20084.1000) and Outlook.com (September 2018) works well now. Appointments created by Outlook 2016 still has oneself as a recipient, but Outlook for iOS and Outlook.com handles this situation well. I would like to express my thanks to the Microsoft employee who helped escalating this issue, though the reason behind this fix might be some other steamingly angry corporate customer.

Updated on 23 April 2018: I contacted Microsoft Support and submitted a ticket on the issue (related product set to Outlook 2016). Today, suggested by a Microsoft employee, I submitted another ticket in Outlook for iOS, mentioned my work and asked them to assign the issue to him. Outlook for iOS immediately recognised I was affiliated with Microsoft (actually, currently an intern at MSRA). I got a response within 2 minutes and I guess my identity expedited this process somehow. Let’s see how long it will take Microsoft before the fix is rolled out.

Story

Outlook 2016 GUI always creates an event with at least one attendee, which makes Outlook.com and Outlook for iOS have difficulty correctly interpreting the appointment. Instead, they think it is a meeting. However, if you create the event with Outlook object model, there is a good chance the event is created neatly. In addition, events created on Outlook.com or in Outlook for iOS are always neat.

I found this issue when I was checking my agenda on Outlook for iOS and found many of my appointments (status Busy) displayed as Tentative. If I tap the event, Outlook for iOS will show that /o=first organization/ou=exchange administrative group(fydibohf23spdlt)/cn=recipients/cn=<Microsoft account unique ID>(legacy Exchange distinguished name or legacyExchangeDN) is the organiser, and that your primary Microsoft account alias is the (other) attendee.

Here, <Microsoft account unique ID> is the unique ID displayed on Additional security options page for your Microsoft account.

Outlook 2016 recognises this event as an appointment, one that does not involve other people, whereas Outlook for iOS and Outlook.com regard it as a meeting, one that involves other people.

Issue

Condition

  1. You use your Microsoft account with Outlook 2016, Outlook.com and Outlook for iOS.
  2. You create an appointment in Outlook 2016.
  3. You remove the email address of yourself from the attendee list on Outlook.com.

Symptoms

  • After step 2, the event displays as an appointment in Outlook 2016 and Calendar app.
  • After step 2, the event displays as a meeting in Outlook for iOS and on Outlook.com.
  • After step 2, if you look at the event in Outlook for iOS, it shows as tentative and requires you responding to yourself. Moreover, the organiser displays a strange long string that is not an SMTP email address, and the only (other) attendee is the primary alias of your Microsoft account.
  • After step 2, if you look at the event on Outlook.com, the primary alias of your Microsoft account is shown as an attendee.
  • After step 3, the event displays as a meeting in Outlook 2016 and Calendar app.
  • After step 3, the event displays as an appointment in Outlook for iOS and on Outlook.com.

Expected behaviours

After step 2, the event displays as an appointment in Outlook for iOS and on Outlook.com.

Workaround

Currently, if you create an appointment with Outlook for iOS, the appointment displays correctly on all ends. Moreover, if you create an appointment with Outlook object model, the result is likely to be correct. To learn more, go to Solution.

Details

Environment

  • Exchange-based Outlook.com inbox, which is also your Microsoft account. Note that Office 365 doesn’t reproduce this issue, and that you must use Microsoft account.
  • Windows 10 (version 1709, build 16299.371).
  • Calendar app (version 17.9126.21535.0) that comes with Windows 10.
  • Outlook 2016 (version 1803, build 9126.2152).
  • Outlook for iOS (version 2.73.0).

Reproduction

  1. You sign in Outlook 2016 with your Outlook.com inbox with Exchange (or auto-discover).
  2. You sign in Calendar app with your Microsoft account.
  3. You sign in Outlook.com Calendar with your Microsoft account.
  4. You sign in Outlook for iOS with your Microsoft account (configured as Outlook.com account).
  5. In Outlook 2016, you create an appointment named ‘Example event’ some time today.
  6. In Outlook 2016, the event displays as an appointment as in [DesktopOutside] and [DesktopInside].
  7. In Calendar app, the event displays as an appointment as in [Commapp].
  8. In Outlook for iOS, the event displays as a meeting as in [iOSOutside] and [iOSInside]. If you try to respond to the event, you will see a pop-up for your choice as in [iOSRespond], and after you choose a response, it fails as in [iOSRespondFail].
  9. On Outlook.com, the event displays as a meeting as in [OWA].
  10. On Outlook.com, you edit the event, remove yourself from the attendee list and tap ‘Send’. If a pop-up appears for confirmation, tap ‘Send’.
  11. In Outlook 2016, the event displays as a meeting as in [DesktopOutsideEdited] and [DesktopInsideEdited].
  12. In Calendar app, the event displays as a meeting as in [CommappEdited].
  13. In Outlook for iOS, the event displays as an appointment as in [iOSOutsideEdited] and [iOSInsideEdited].
  14. On Outlook.com, the event displays as an appointment as in [OWAEdited].

Screenshots

[DesktopOutside]
assets/appointment-desktop-outside.png

[DesktopInside]
assets/appointment-desktop-inside.png

[Commapp]
assets/appointment-commapp.png

[iOSOutside]
assets/meeting-ios-inside.png

[iOSInside]
assets/meeting-ios-outside.png

[iOSRespond]
assets/meeting-ios-respond.png

[iOSRespondFail]
assets/meeting-ios-respond-fail.png

[OWA]
assets/meeting-owa.png

[DesktopOutsideEdited]
assets/meeting-desktop-outside.png

[DesktopInsideEdited]
assets/meeting-desktop-inside.png

[CommappEdited]
assets/meeting-commapp.png

[iOSOutsideEdited]
assets/appointment-ios-outside.png

[iOSInsideEdited]
assets/appointment-ios-inside.png

[OWAEdited]
assets/appointment-owa.png

Notes

Upon close investigation, I found one possible explanation for the bug.

Create two events, one titled ‘Example event from Outlook 2016’ on Outlook 2016 and the other titled ‘Example event from Outlook for iOS’ on Outlook for iOS. Now open PowerShell, enter the following commands:

$outlook = New-Object -ComObject Outlook.Application
$mapi = $outlook.GetNamespace('MAPI')
$folder = $mapi.PickFolder()
# Pick the calendar folder

$folder.Items | ForEach-Object -Begin { $i = 1 } -Process {
    New-Object PSObject -Property @{ 'Subject' = $_.Subject; 'Index' = $i++ }
} | Out-GridView
# Find out the indices of the two events

$evtGood = $folder.Items(0)
# Index for Example event from Outlook for iOS

$evtBad = $folder.Items(1)
# Index for Example event from Outlook 2016

$evtGood.OutlookVersion, $evtBad.OutlookVersion
# Produces 15.20Ex (good) and 16.0 (bad)

$evtGood.Recipients
# Produces an empty list

$evtBad.Recipients
# Produces a list with a single element

The last element produces some output similar to the following to the console:

    ...
Address               : /o=First Organization/ou=Exchange Administrative Group(
                        FYDIBOHF23SPDLT)/cn=Recipients/cn=<Unique ID>
    ...
Name                  : <someone>@outlook.com
Resolved              : True
TrackingStatus        : 0
TrackingStatusTime    : 1/1/4501 12:00:00 AM
Type                  : 1
PropertyAccessor      : System.__ComObject
Sendable              : False

Now, let’s try:

$item = $folder.Items.Add()
$item.Subject = 'Example event from Outlook object model'
$item.Save()

The newly created event displays as an appointment on all ends, as good as those created in Outlook for iOS. However, its version $item.OutlookVersion is 16.0. Therefore, the version number isn’t the key to the problem. Now consider the following code:

$item.RequiredAttendees
$item.Recipients
$evtGood.RequiredAttendees
$evtGood.Recipients
$evtBad.RequiredAttendees
$evtBad.Recipients

Only $evtBad produces non-empty lists. Now the following code:

$item.GetOrganizer()
$item.Recipients
# Now the list is no longer empty
$item.Saved
# True, which indicates that though
# the event is modified, it is not
# considered edited.
$item.Subject = $item.Subject
$item.Saved
# False, now modified
$item.Save()

After another round of synchronisation, Outlook for iOS (incorrectly) shows the event as a meeting. Now do the following:

$item.Recipients(1).Delete()
# Delete yourself from the recipients.
$item.Save()

Having been synched, the event displays as an appointment on all ends again.

The conclusion I make here is that Outlook 2016 GUI somehow calls GetOrganizer before saving the event, which makes the attendee list non-empty, and that Outlook.com and Outlook for iOS don’t handle this situation well enough — they should be able to detect that the only attendee is actually the organiser hence the event is an appointment instead of a meeting. It is important to realise that both Outlook.com and Outlook for iOS might be able to recognise legacy Exchange distinguished name, because removing the attendee (instead of whacking the organiser, which is still stored with legacyExchangeDN) will make it appear normal.

Moreover, it is interesting that if one deletes the attendee on Outlook.com (instead of deleting it with Outlook object model), Outlook 2016 and Calendar app will think it is a meeting instead. I cannot develop a sensible explanation for this.

Solution

Microsoft should fix the interpretation and/or the editing procedure. Moreover, careful inspection on desired behaviour for a real meeting (how the organiser is managed in the attendee list) is required. Here’s another workaround: I have written a script that fixes an appointment, which can be redistributed under MIT license. You can download the script here (UTF-8 with BOM; CRLF).

The code is collapsed for brevity. Show the code.

The code can be collapsed for brevity. Hide the code.

# File name: Repair-Appointment.ps1

<#
.SYNOPSIS
    Repairs appointments created or edited by Outlook 2016 via GUI.

.DESCRIPTION
    The advanced function receives a bunch of Outlook object model items
    and checks each appointment item. If the item is not a meeting and
    contains one recipient, the recipient is removed and the item is saved;
    if the item is not a meeting and contains multiple recipients, the
    script asks for confirmation before removing its recipients and saving
    it. Repairing appointments allows them to display as appointments
    in Outlook 2016, Calendar app and Outlook for iOS and on Outlook.com.

    Copyright (c) 2018 by Gee Law.

.PARAMETER Items
    Outlook object model items to be processed. The parameter can receive
    values from the pipeline. Valid types to send to Items are Folder,
    Items (item collection type) and AppointmentItem. Items must be
    Outlook object models, otherwise an error is written.

    If an item is a Folder, its Items are enumerated and processed; or
    if an item is an Items, it is enumerated and processed; or
    if an item is an AppointmentItem, it is processed; or
    otherwise, an error is written.

.PARAMETER Force
    Forces the process to go through without confirmation, even for
    appointments with 2 or more recipients.

    If Force is on, -WhatIf and -Confirm are ignored and applicable
    items are always processed.

.PARAMETER WhatIf
    Instead of processing the items, displays a summary of which items
    are to be processed. This switch is ignored if -Force is on.

.PARAMETER Confirm
    Asks for confirmation before each repairment. By default, only
    appointments with 2 or more recipients are asked for confirmation;
    switching -Confirm on makes the function ask for confirmation even
    for appointments with exactly 1 recipient.

.EXAMPLE
    $f1, $f2.Items, $a1, $a2 | Repair-Appointment -WhatIf

    Suppose $f1, $f2 are calendar folders, and $a1, $a2 are appointment
    items. The command sends all items in $f1, $f2 with $a1, $a2 to be
    repaired. However, the command only check if each item is applicable
    for repairment and outputs a summary of what would have been done
    without -WhatIf. To actually run the processing, execute the line
    with -WhatIf off.

.INPUTS
    You can pipe Folder, Items or AppointmentItem to the command, which
    will be equivalent to supplying them as the Items parameter.

.OUTPUTS
    The command does not produce outputs.

.LINK
    https://geelaw.blog/entries/outlook-calendar-extraneous-attendee/

#>
[CmdletBinding(SupportsShouldProcess = $true)]
Param
(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
    [object[]]$Items,
    [Parameter(Mandatory = $false)]
    [switch]$Force
)
Begin
{
    $local:olObjectClassOlFolder = 2;
    $local:olObjectClassOlItems = 16;
    $local:olObjectClassOlAppointment = 26;
    $local:olMeetingStatusOlNonMeeting = 0;
}
Process
{
    foreach ($item in $Items)
    {
        $local:enumerate = @();
        if ($item.Application -isnot [Microsoft.Office.Interop.Outlook.ApplicationClass])
        {
            Write-Error "Non-Outlook object model object." -Category InvalidArgument -TargetObject $item;
            continue;
        }
        if ($item.Class -eq $olObjectClassOlFolder)
        {
            $enumerate = $item.Items | Write-Output;
        }
        elseif ($item.Class -eq $olObjectClassOlItems)
        {
            $enumerate = $item | Write-Output;
        }
        elseif ($item.Class -eq $olObjectClassOlAppointment)
        {
            $enumerate = @($item);
        }
        foreach ($appointment in $enumerate)
        {
            if ($appointment.Class -ne $olObjectClassOlAppointment)
            {
                Write-Error "Non-appointment item." -Category InvalidArgument -TargetObject $appointment;
                continue;
            }
            $local:appName = "[$($appointment.StartInStartTimeZone) -> $($appointment.EndInEndTimeZone)] $($appointment.Subject)";
            if ($appointment.MeetingStatus -eq $olMeetingStatusOlNonMeeting)
            {
                $rcpcnt = $appointment.Recipients.Count;
                if ($rcpcnt -eq 1)
                {
                    if ($Force -or $PSCmdlet.ShouldProcess($appName, "Remove recipients and save"))
                    {
                        $appointment.Recipients(1).Delete();
                        $appointment.Save();
                    }
                }
                elseif ($rcpcnt -gt 1)
                {
                    if ($Force -or $PSCmdlet.ShouldContinue(
                        "`"$appName`" contains multiple recipients. Do you want to remove its recipients and save it?",
                        "Unusual case"))
                    {
                        $rcpcnt..1 | ForEach-Object {
                            $appointment.Recipients($_).Delete();
                        };
                        $appointment.Save();
                    }
                }
                else
                {
                    Write-Verbose "Skipping good appointment: $appName.";
                }
            }
            else
            {
                Write-Verbose "Skipping meeting: $appName.";
            }
        }
    }
}

The code can be collapsed for brevity. Hide the code.

You can fix appointments by:

$outlook = New-Object -ComObject Outlook.Application;
$mapi = $outlook.GetNamespace('MAPI');

while ($true)
{
    $folder = $mapi.PickFolder();
    # In the folder picker, select Cancel to stop.
    if ($folder -eq $null) { break; }
    .\Repair-Appointment.ps1 $folder -WhatIf;
    .\Repair-Appointment.ps1 $folder -Confirm;
    # Type A to accept all the changes; or 
    #      L to select another folder; or
    #      Y/N to confirm one by one.
}

To prevent this from further happening, attach an event handler to AppointmentItem.Write event and make appropriate modification before the event is saved. You might translate the above-mentioned PowerShell script into C♯ or VB.NET, or even VB6.

Moral

Microsoft should pay me the fee of a support ticket. I called Microsoft China for support on that and have found the solution/workaround out myself before Microsoft calls me back. Last time, I couldn’t give an effective solution as there wasn’t one.

MIT license

Copyright © 2018 by Gee Law, retrievable from https://geelaw.blog/entries/outlook-calendar-extraneous-attendee/.

Permission is hereby granted, free of charge, to any person obtaining a copy of this blog entry, especially the PowerShell code snippets in the blog entry (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Please enable JavaScript to view the comments powered by Disqus.