Category Archives: Exchange Online

Legal hold and attribute conflicts in Exchange Online

Consider a large organisation, where deleted mailboxes are kept for many years.

Consider a new user with the same name as an offboarded user, or a user getting rehired, but policy stating that the deleted mailbox should not be restored. The user should start with a clean mailbox.

The inactive old mailbox still has the standardized primary smtp / alias etc and will not allow you to set these on the new user, causing a conflict.

Of course this should be handled during the offboarding process, where perhaps the email address of the user could be appended with _old123 before being soft-deleted.

Since that wasn’t the case here, I had to write a quick script to retroactively add a random string to all such attributes for all inactive mailboxes, hope it helps someone else with the same legacy 🙂

connect-exchangeonline
$inactiveMailboxes = Get-Mailbox -InactiveMailboxOnly -ResultSize unlimited

foreach($inactiveMailbox in $inactiveMailboxesNovib){
    $rand = Get-Random -Maximum 999
    $primary = $inactiveMailbox.PrimarySmtpAddress
    $newMailbox = New-Mailbox -InactiveMailbox $inactiveMailbox.DistinguishedName -name $inactiveMailbox.Name -FirstName $inactiveMailbox.DisplayName.Split(" ")[0] -LastName $inactiveMailbox.DisplayName.Split(" ")[1] -DisplayName $inactiveMailbox.DisplayName -MicrosoftOnlineServicesID $inactiveMailbox.PrimarySmtpAddress.Replace("@","_old$rand@") -Password (ConvertTo-SecureString -String 'W1pos3wsd03?!' -AsPlainText -Force) -ResetPasswordOnNextLogon $true    

    $newMailbox | Remove-Mailbox -Force -Confirm:$False
}

Using Exchange Web Service with Powershell to remove calendar appointments without notification

While helping out a client recently something in their migration went wrong, I ran into an interesting challenge. Calendars of users were merged, many many times. The resulting duplicates that shouldn’t be in user’s calendars could be identified easily: the mailbox was neither the organiser nor invited to these calendar events.

But how to remove them? Graph won’t allow you to do so without notifying the recipients (leading, potentially, to thousands of confused users).

Luckily, EWS DOES allow us to do so, and if you ever need to work with EWS (Office 365 Exchange Online) using Powershell, this code sample could come in handy 🙂

Add-Type -Path "C:\Users\jos\Desktop\net35\Microsoft.Exchange.WebServices.dll"

$Service = [Microsoft.Exchange.WebServices.Data.ExchangeService]::new()
$Service.Credentials = [System.Net.NetworkCredential]::new("admin@onedrivemapper.onmicrosoft.com" , "yourpassword")
$Service.Url = "https://outlook.office365.com/EWS/Exchange.asmx"

$maxDaysIntoTheFuture = 365

function Remove-ObsoleteCalendarItems{
    Param(
        $primaryEmailAddress #eg: admin@onedrivemapper.onmicrosoft.com
    )

    $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$primaryEmailAddress)   
    $Calendar = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$folderid)
    $Recurring = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition([Microsoft.Exchange.WebServices.Data.DefaultExtendedPropertySet]::Appointment, 0x8223,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Boolean); 
    $psPropset= new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)  
    $psPropset.Add($Recurring)
    $psPropset.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;

    #Define Date to Query 
    $currentDay = 0
    while($True){
        $StartDate = (Get-Date).AddDays($currentDay)
        $EndDate = $StartDate.AddDays(14)  
        $currentDay += 14

        if($currentDay -gt $maxDaysIntoTheFuture){
            break
        }

        $CalendarView = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($StartDate,$EndDate,1000)    
        $fiItems = $service.FindAppointments($Calendar.Id,$CalendarView)
        if($fiItems.Items.Count -gt 0){
            $type = ("System.Collections.Generic.List"+'`'+"1") -as "Type"
            $type = $type.MakeGenericType("Microsoft.Exchange.WebServices.Data.Item" -as "Type")
            $ItemColl = [Activator]::CreateInstance($type)
            foreach($Item in $fiItems.Items){
                $ItemColl.Add($Item)
            } 
            [Void]$service.LoadPropertiesForItems($ItemColl,$psPropset)  
        }

        foreach($Item in $fiItems.Items){  
            if($Item.Organizer.Address -ne $primaryEmailAddress -and $Item.RequiredAttendees.Address -notcontains $primaryEmailAddress -and $Item.OptionalAttendees.Address -notcontains $primaryEmailAddress){
                $Item.RequiredAttendees.Clear() #this also works if no one is invited
                $Item.OptionalAttendees.Clear() #this also works if no one is invited
                $Item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite,[Microsoft.Exchange.WebServices.Data.SendInvitationsOrCancellationsMode]::SendToNone)
                $Item.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::MoveToDeletedItems)
                write-host "deleted item $($Item.Subject) without notifying recipients"
            }
        }
    }
}

The required EWS DLL’s can also be found in my git repository:

More licenses and features

Pivot table of all Microsoft cloud suites and their features

I’ve updated the Microsoft cloud suites feature comparison page with all other suites Microsoft including all their features. I’ve also added all Education sku’s. You can use the pivot table to sort / mix / match according to your exact needs. If you need any assistance with Microsoft 365, don’t be a stranger 🙂