Scripting a Mailbox recovery

I have been helping a large number of customers to recover mailboxes that were deleted, in the Office 365 service (by mistake or not) and although the service provides a couple of methods to achieve this, I have found that many of the Office 365 (Exchange online) administrators are struggling to recover the mailboxes, so I have written a script the will facilitate this.

This small script is the first bit from a larger project. The final goal is to have a script that can enable administrators to recover soft deleted mailboxes while being in a hybrid environment.

As you can imagine, a hybrid environment is a very complex one and recovering a mailbox living in such a complex configuration can become difficult to put into a script, so this first part will only recover mailboxes for Cloud only Customers.

Additionally the script is not intended to be used for archive mailboxes as well.

This post will explain in details the script logic and the prerequisites needed for it to run.

Before running the script please read the below section:

DISCLAIMER: This application is a sample application. The sample is provided “as is” without warranty of any kind. Microsoft further disclaims all implied warranties including without limitation any implied warranties of merchantability or of fitness for a particular purpose. The entire risk arising out of the use or performance of the samples remains with you. In no event shall Microsoft or its suppliers be liable for any damages whatsoever (including, without limitation, damages for loss of business profits, business interruption, loss of business information, data loss or other pecuniary loss arising out of the use of or inability to use the samples, even if Microsoft has been advised of the possibility of such damages. Because some states do not allow the exclusion or limitation of liability for consequential or incidental damages, the above limitation may not apply to you.

 

Prerequisites: Before you will be able to run the script you will need to install on the computer the prerequisites described in the following article Connect to Office 365 PowerShell

The logic:

Before taking any actions, the script is using the below paragraph to ensure that the working surface is clean and that the errors have been cleared and no lingering PowerShell sessions are still living:

Cls
$error.Clear()
Get-Pssession | Remove-PSSession
$FormatEnumerationLimit =-1

I have used Functions to achieve various tasks and I'll explain each function below:

Function Check-ExecutionPolicy -> The purpose of this function is to make sure that the execution policy on the computer used to run the script, is at least Remote Signed if the value is lower than this then the script will attempt to change it to Remote Signed.

Function Connect-O365 -> Like the name implies, this function will attempt to connect to the Office 365 services (Exchange Online and to MsolService). The Administrator will be prompted on the screen to provide the credentials for the global administrator and it will check if any error occurred during the process. If the process ended with no error then the script will continue, otherwise the script will exit.

Function get-choice -> The function is used in the script, in various places and it's purpose is to create the framework for a question and provides two choices (Yes/No)

Function Choose-Mailbox -> This function is intended to display in a user friendly view the list of mailboxes that can be recovered by this script. Behind the scenes this portion of the code will retrieve the mailboxes that are present in a soft deleted state. The Administrator can select the mailbox he/she wants to recover and once selected the Admin needs to press on the OK button to retain the selection.

The function returns the mailbox object selected above containing all the attributes.

Function Get-Onmicrosoft -> Like the name implies, this function is used to search among the accepted domains on the tenant and to return only the initial domain (i.e. tenant_name.onmicrosoft.com)

Function Recover_CloudOnlyMailbox -> This is the main function of this script and is the one that's doing the heavy lifting.

At the beginning of the function the user will get a confirmation on the screen that the mailbox he has previously selected is actually the one that this script will attempt to recover. On the screen the Primary SMTP email address of the selected mailbox will be displayed.

Second information displayed is the date and time when this mailbox was deleted. I have included this information to make sure that the Administrator has all the data ever which mailbox is actually recovered.

If by mistake the administrator selected the wrong mailbox then he will have the option to select another mailbox by replying no to the following question:

Is this the mailbox you want to recover?

If the Administrator selects NO then the program will go back and display on the screen the soft deleted mailboxes again and asks the administrator to select another one.

If the Administrator selects Yes then the program continues with its logic focusing it's actions on the mailbox the Administrator has selected.

Before attempting any recovery actions on the selected mailbox we need to check to see if there is any MsolUser still associated with the mailbox and the script will check to see if the property "ExternalDirectoryObjectId" is still populated with a GUID or not. If the property has any value then this means that there is still a user associated with this mailbox so the script will then search among the active users and soft deleted users to see if this GUID represents an ObjectID of a valid user.

 

If a user is found among the active users then the script will share this info on the screen. This situation should not be encountered but if the mailbox is in this situation then this script will not address it.

Try to wait 15 minutes and then re-run the script again as if you delete the mailbox and not wait until all the properties are being synchronized across the service, unexpected results might be encountered.

Next the script will check to see if there is a soft deleted MsolUser using the ObjectID and if it is found then the script will restore that MsolUser to active, this bringing the mailbox to it's active state.

 

If there is no user found that is still associated to this mailbox then the script will try to see if there are any other active mailboxes that are using the SMTPs on this mailbox.

If any active mailbox is found then the script will try to merge the soft deleted mailbox with this active one otherwise the script will use the new-mailbox cmdlet to create a new MsolUser and to associate the deleted mailbox to it.

The new user will have the following UserPrincipalName: alias_backup@initialdomain.onmicrosoft.com

The script will display the credentials the administrator can use to connect to the newly recovered mailbox.

 

Script Body

Cls
$error.Clear()
Get-Pssession | Remove-PSSession
$FormatEnumerationLimit =-1

#Check the current Execution Policy and if it is not RemoteSigned/Unrestricted the below Function will change it to RemoteSigned
Function Check-ExecutionPolicy
{
Write-Host -ForegroundColor DarkGreen  "Checking the local ExecutionPolicy...."
$ExecutionPolicy = Get-ExecutionPolicy
If ($ExecutionPolicy -notmatch "RemoteSigned")
{
Write-host -ForegroundColor DarkGreen "Currently the execution policy for the current user is "$ExecutionPolicy", the script will change it to RemoteSigned"
Set-ExecutionPolicy RemoteSigned -Force CurrentUser
}
Else
{
Wtite-Host -ForegroundColor DarkGreen "Currently the exection policy has this value "$ExecutionPolicy" and this is compliant with running remote PowerShell connections"
}
}

#EXO & MSODS connect Function - Used to connect to Office 356 workflows: Exchange Online and Azure Active Directory
Function Connect-O365
{
$EXOConnectionError = $null
Write-Host -ForegroundColor DarkGreen "Please enter the Username for the Global Admin (Tenant Administrator)"
$ExoUsername = read-host
Write-Host -ForegroundColor DarkGreen "Please enter the Password for the Global Admin (Tenant Administrator)"
$PassExo= read-host

$secureStringPwd = $PassExo | ConvertTo-SecureString -AsPlainText -Force
$EXOCred = new-object -typename System.Management.Automation.PSCredential -argumentlist $ExoUsername,$secureStringPwd
$EXOSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $EXOcred -Authentication Basic -AllowRedirection
If(!$Error)
{
Import-PSSession $EXOSession -Prefix EXO -DisableNameChecking -AllowClobber -erroraction silentlycontinue
Connect-MsolService -credential $EXOCred
Write-Host -ForegroundColor DarkGreen "Connection to Exchange Online was successfull."
}
Else
{
Write-Host -ForegroundColor DarkGreen "Importing the PS session encountered an error. The script will now exit"
Read-Host
Exit
}
$Error.Clear()
}

#The below function is used to prompt the user for various choices
function get-choice {
# Function for getting user choice
param
(
[Parameter(Mandatory = $true, HelpMessage = 'Pop-up Display Name')]
[String]$Title,
[Parameter(Mandatory = $true, HelpMessage = 'Prompt')]
[String]$Prompt,
[Parameter(Mandatory = $true, HelpMessage = 'Message for Yes')]
[String]$Ymessage,
[Parameter(Mandatory = $true, HelpMessage = 'Message for No')]
[String]$Nmessage

)

$Yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes',$Ymessage
$No = New-Object System.Management.Automation.Host.ChoiceDescription '&No',$Nmessage
$options = [System.Management.Automation.Host.ChoiceDescription[]] ($Yes,$No)
$eprompt = $Prompt + "[Y]es or [N]o"
$choice = $host.ui.PromptForChoice($title,$eprompt,$options,0)
return $choice
}

#The function below is used to display the mailboxes that this script is able to recover
Function Choose-Mailbox
{
Write-Host -ForegroundColor DarkGreen "Please select a mailbox from the grid view that you want to Recover or to Delete"
$Global:ExoDeltedMailbox = Get-EXOMailbox -SoftDeletedMailbox | Out-GridView -PassThru
return $Global:ExoDeltedMailbox
}

#The function is used to collect the initial domain on the tenant
Function Get-Onmicrosoft
{
$Onmicrosoft = Get-EXOAcceptedDomain | ?{$_.Name -like "*onmicrosoft.com*" -and $_.Name -notlike "*mail*" }
return $Onmicrosoft.Name
}

#Function used to recover the mailbox.
Function Recover_CloudOnlyMailbox
{
Write-Host -ForegroundColor DarkGreen "You have selected the following mailbox to recover:"$Global:ExoDeltedMailbox.PrimarySmtpAddress""
Write-Host -ForegroundColor DarkGreen "The above mailbox was delted on : "$Global:ExoDeltedMailbox.WhenSoftDeleted"UTC"
$CorrectMailbox = get-choice -Title "Confirm Mailbox" -Prompt "Is this the mailbox you want to recover?" -Ymessage "Attempting to recover the mailbox." -Nmessage "Please choose another one!"

if($CorrectMailbox)
{
Choose-Mailbox
Recover_CloudOnlyMailbox
}
Else
{
Write-Host -ForegroundColor DarkGreen "Checking if there is a MsolUser associated with this mailbox:"
If ($Global:ExoDeltedMailbox.ExternalDirectoryObjectId -ne "")
{
$MSolUser = Get-MsolUser -ObjectId $ExoDeltedMailbox.ExternalDirectoryObjectId -ErrorAction SilentlyContinue
$MsolUserDeleted = Get-MsolUser -ObjectId $ExoDeltedMailbox.ExternalDirectoryObjectId -ReturnDeletedUsers -ErrorAction SilentlyContinue
If ($MSolUser -ne $null)
{
Write-host -ForegroundColor DarkRed "There is an active user that is associated to this mailbox:"$MSolUser.UserPrincipalName""
Write-Host -ForegroundColor DarkRed "This is an unhandled situation and the mailbox recovery cannot continue."
Write-Host -ForegroundColor DarkRed "The script will now exit. Press any key to continue"
Read-Host
exit
}

ElseIf($MsolUserDeleted -ne $null)
{
Write-Host -ForegroundColor DarkGreen "The mailbox "$ExoDeltedMailbox.PrimarySmtpAddress" is associated with a soft delted user"
Write-Host -ForegroundColor DarkGreen "The script will attempt to restore the soft deleted user "$MsolUserDeleted.UserPrincipalName""
Restore-MsolUser -UserPrincipalName $MsolUserDeleted.UserPrincipalName -AutoReconcileProxyConflicts
Write-Host -ForegroundColor DarkGreen "The SoftDeleted Mailbox "$ExoDeltedMailbox.DisplayName" was recovered and can be accessed through "$MsolUserDeleted.UserPrincipalName" UPN"
}
Else
{
Write-host -ForegroundColor DarkRed "The mailbox "$ExoDeltedMailbox.Identity" is an Orphan mailbox and this represents an unhandled situation"
Write-host -ForegroundColor DarkRed "The script will now exit. Please press any key to exit"
Read-Host
Exit
}
}
Else
{
Write-Host -ForegroundColor DarkGreen "The selected soft deleted mailbox "$ExoDeltedMailbox.PrimarySmtpAddress" has no MsolUser associated with it"
Write-Host -ForegroundColor DarkGreen "Checking to see if there is an active mailbox using the properties of the deleted mailbox"
$EmailAddreses = $ExoDeltedMailbox.EmailAddresses
$Recipients = Get-EXOrecipient
$collection = @()
foreach ($mbx in $Recipients)
{
Foreach($addr in $EmailAddreses)
{
If ($addr -in $mbx.EmailAddresses)
{
$Mailbox_to_Merge_to = $mbx
Write-Host -ForegroundColor DarkRed "address $addr exist in "$mbx.DisplayName" with the following GUID "$mbx.ExchangeGuid""
$collection += $mbx
}
Else
{
Write-host -ForegroundColor DarkGreen "Mailbox "$mbx.DisplayName" is not using "$addr""
}
}
}
If ($collection -eq $null)
{
$alias = ($ExoDeltedMailbox.PrimarySmtpAddress -split "@")[0].Substring(0)
$New_alias = $alias+"_Backup"
$c = Get-Onmicrosoft
$New_MicrosoftOnlineServicesID = $New_alias + "@" +$c
New-EXOMailbox -InactiveMailbox $ExoDeltedMailbox.ExchangeGuid.Guid -Alias $New_alias -Name $New_alias -MicrosoftOnlineServicesID $New_MicrosoftOnlineServicesID -Password(ConvertTo-SecureString -String P@ssw0rd -AsPlainText -Force)
Write-Host -ForegroundColor Cyan "The Mailbox "$ExoDeltedMailbox.Identity" was recovered and associated with the following MsolUser $New_MicrosoftOnlineServicesID and the password is: P@ssw0rd"
}
Else
{
$collection | %{Write-host -ForegroundColor DarkGreen "The SMTPs of "$ExoDeltedMailbox.DisplayName" is used in the "$_.DisplayName", and has this tupe "$_.RecipientTypeDetails""}
Write-Host -ForegroundColor DarkGreen "Do you whish to merge the soft deleted mailbox with the active one?"
foreach ($item in $collection)
{
$ItemType = $collection.RecipientTypeDetails
If ($ItemType -ne "UserMailbox")
{
Write-host -ForegroundColor DarkRed "The object $item is not an active mailbox and it is conflicting with the soft deleted mailbox $ExoDeltedMailbox. Press any key to exit..."
Read-Host
exit
}
}
$Merge = get-choice -Title "Continue with Merge" -Prompt "Are you sure you want to merge the two mailboxes?" -Ymessage "Attempting to merge the mailboxes." -Nmessage "The program will now exit"
If (!$Merge)
{
Write-Host -ForegroundColor DarkGreen "Attempting to merge "$ExoDeltedMailbox.DisplayName": Guid "$ExoDeltedMailbox.Guid", with "$Mailbox_to_Merge_to.DisplayName": Guid "$Mailbox_to_Merge_to.Guid""
New-EXOMailboxRestoreRequest -Name $ExoDeltedMailbox.Alias -SourceMailbox $ExoDeltedMailbox.Guid.Guid -TargetMailbox $Mailbox_to_Merge_to.Guid.Guid -AllowLegacyDNMismatch
$Merge_Status = (Get-EXOMailboxRestoreRequest -Name $ExoDeltedMailbox.Alias).Status
Write-host -ForegroundColor DarkGreen "The Merge Job might take a while so this program will exit. To check the status of the mailbox merge please initiate a new RemotePowershell conection"
Write-Host -ForegroundColor DarkGreen "to Exchange Online and run the Get-MailboxRestoreRequest"
(Get-EXOMailboxRestoreRequest -Name $ExoDeltedMailbox.Alias).Status
Read-Host
Exit
}
}

}

}
Write-host -ForegroundColor DarkMagenta "The program will now exit!!! Please press any key to continue..."
Read-Host
}

#Program Body!!! The script will attempt to recover a mailbox only if one was selected when prompted, otherwize the script will exit!!!

Check-ExecutionPolicy
Connect-O365
Choose-Mailbox
If($ExoDeltedMailbox -ne $null)
{
Recover_CloudOnlyMailbox
}
Else
{
Write-host -ForegroundColor DarkRed "No Mailbox was selected"
Write-host -ForegroundColor DarkRed "The script will now exit. Please press any key to exit"
Read-Host
Exit
}