(Faster) script to put mailboxes on hold in Exchange Online

This is hardly something new, but I keep running into examples of scripts that do this in a slooooooow way. Everyone that has dealt with large companies in Office 365 knows the pain of running scripts against huge numbers of objects in EO. As detailed in the recent article on the Exchange team blog, there is a better way to do things. Instead of cycling all users with E3 license applied and then checking them for hold settings, you can just use a filter such as:

Get-Mailbox -RecipientTypeDetails UserMailbox -Filter {PersistedCapabilities -eq "BPOS_S_Enterprise" -and LitigationHoldEnabled -ne $true}

This single line of code will return all mailboxes that have Exchange Online plan 2 license applied and are not currently put on litigation hold. Not only the script will look a lot simpler, the execution time will be reduced by order of several magnitudes!

So how would a fully automated solution look then? An example script below:

$admin = 'user@domain.com'
$password = Get-Content "D:\O365\user@domain.com.txt" | ConvertTo-SecureString
$cred = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $admin,$password

$exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $cred -Authentication Basic –AllowRedirection -ErrorAction Stop
Import-PSSession $exchangeSession -ErrorAction Stop

# Select all users with EO Plan 2 license and NO hold enabled:
$Users = Get-Mailbox -RecipientTypeDetails UserMailbox -Filter {PersistedCapabilities -eq "BPOS_S_Enterprise" -and LitigationHoldEnabled -ne $true}

foreach ($User in $Users) {
Set-Mailbox -Identity $User.UserPrincipalName -LitigationHoldEnabled $true -ea silentlycontinue

Remove-PSSession $exchangeSession

Another point worth mentioning here – note that the script does not use any plain text passwords. If you are still putting credentials in plain text in your scripts, you need some spanking. It’s easy enough to store the credentials securely. Just run the following code:

$credential = Get-Credential user@domain.com
$credential.Password | ConvertFrom-SecureString | Set-Content "user@domain.com.txt"

This will store the encrypted password in the user@domain.com.txt file, which you can then reuse in your scripts via:

$username = "user@domain.com"
$password = Get-Content "user@domain.com.txt" | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PsCredential($username, $password)

The credentials will only be available for the user that run the script, so if you are planning to run the script as a scheduled task with service account credentials, make sure to generate the password file using said credentials.

Speaking of running the script as scheduled tasks, it’s very easy to do. The most painless way is to create a new task that runs a simple .bat file, which in turn executes PowerShell and calls your script. For example, this bat file will start PowerShell and run the C:\O365\EnableLitigationHold.ps1 script:

%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe -file C:\O365\EnableLitigationHold.ps1

The script above is very simplistic and can certainly be expanded with proper error handling, logging capabilities and reporting, etc. You can certainly tune it even further if your company has loads of mailboxes, by filtering for example only the mailboxes created in the last 7 days, etc. But hopefully it can serve you as a great starting example.

8 thoughts on “(Faster) script to put mailboxes on hold in Exchange Online

  1. Chris says:

    That’s great – but where do adjust the litigation hold period?..

    1. Vasil Michev says:

      The example above is for unlimited duration, you can use the -LitigationHoldDuration parameter to specify custom values.

  2. Shane says:

    Great Script! I use one that is almost identical. The issue I have at the moment is that I need to make an except for one specific user as we are doing some work on their corrupted MB. This script applies to all users, is their an exception command I can add in into this script to skip one specific user? otherwise I will need to disable my script up to 2 weeks. Any help would be appreciated as I can’t find much on the net to add in an exception to one specific user. Thank you.

    1. Vasil Michev says:

      You can simply edit the filter on line 9 to exclude the user. For example:

      Get-Mailbox -RecipientTypeDetails UserMailbox -Filter {PersistedCapabilities -eq “BPOS_S_Enterprise” -and LitigationHoldEnabled -ne $true -and Alias -ne “HuKu”}

  3. O365_aDdIcT says:

    Ok, i guess i jumped the gun without reading the entire blog. i do see the steps you had given to store the password on encrypted file.

    Thanks a bunch for the blog. Really Helpful

    1. Vasil Michev says:

      Yup, and there are other methods to do the same if you don’t like the one I’ve used here. Never put a password in a script file!

  4. O365_aDdIcT says:

    Thanks for the script.

    One question – you are still required to save the password on a plain text on the computer where this script is being scheduled from. Is there a way to eliminate that ?


Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.