Entra / Microsoft 365 · Compliance & audit
Test compliance holds
Test the Invoke-HoldRemovalAction cmdlet.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-ExchangeOnline -SkipLoadingCmdletHelpConnect-IPPSSession -CommandName Invoke-HoldRemovalAction
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
Connect-ExchangeOnline -SkipLoadingCmdletHelpConnect-IPPSSession -CommandName Invoke-HoldRemovalAction$CSVFileEXO = "c:\temp\EXOMailboxHolds.CSV"$CSVFileSPO = "c:\temp\SPOSiteHolds.CSV"Write-Host "Looking for mailboxes to process..."# Fetch user mailboxes[array]$Mbx = Get-ExoMailbox -RecipientTypeDetails UserMailbox -Properties LitigationHoldEnabled | `Sort-Object DisplayNameIf (!($Mbx)) {Write-Host "Unable to find mailboxes - exiting"; break}# Loop through mailboxes and figure out what kind of hold information is reported[int]$i = 0$MbxReport = [System.Collections.Generic.List[Object]]::new()ForEach ($M in $Mbx) {$i++$HoldOutput = $NullWrite-Host ("Analyzing holds on mailbox {0} ({1}/{2})" -f $M.displayName, $i, $Mbx.count)[array]$HoldData = Invoke-HoldRemovalAction -Action GetHolds -ExchangeLocation $M.ExternalDirectoryObjectId# Throw away any blank entries$HoldData = $HoldData | Where-Object {$_.length -gt 0}If (!($HoldData)) {$HoldInformation = "No holds on mailbox"} Else {[array]$HoldInformation = $NullForEach ($Hold in $HoldData) {If ($Hold -eq 'DelayHold') {Write-Output "Delay hold set"$HoldOutput += "Delay Hold set"}# Litigation holdIf ($Hold -eq '98E9BABD09A04bcf8455A58C2AA74182Unlimit') {$HoldInformation += "Litigation Hold set"}# Hold imposed by eDiscovery caseIf ($Hold.SubString(0,4) -eq 'UniH') {$eDiscoveryHoldId = $Hold.SubString(4, $Hold.Length-4)$eDiscoveryHoldName = $Null$eDiscoveryHoldName = (Get-CaseHoldPolicy -Identity $eDiscoveryHoldId -ErrorAction SilentlyContinue).NameIf ($eDiscoveryHoldName) {$HoldInformation += ("eDiscoveryHold: {0}" -f $eDiscoveryHoldName)} Else {Write-Output ("Unable to identify hold identifier {0}" -f $Hold)$HoldInformation += ("Unidentified eDiscoveryId: {0}" -f $Hold)}}# Anything else} If (($Hold -ne 'DelayHold') -and ($Hold -ne '98E9BABD09A04bcf8455A58C2AA74182Unlimit') `-and ($Hold.SubString(0,4) -ne 'UniH')) {Write-Host "Legacy hold set"$HoldInformation += $Hold}}If ($HoldInformation) {$HoldOutput = $HoldInformation -Join ", "}$Reportline = [PsCustomObject]@{User = $M.DisplayNameUPN = $M.UserPrincipalNameHolds = $HoldOutput}$MbxReport.Add($ReportLine)}$MbxReport | Export-CSV -NoTypeInformation $CSVFileEXOWrite-Host ("Exchange mailbox legacy hold information available in {0}" -f $CSVFileEXO)# SharePointWrite-Host "Finished dealing with Exchange Online - Now moving to SharePoint Online"Import-Module Microsoft.Online.Sharepoint.PowerShellConnect-SPOService -url https://office365itpros-admin.sharepoint.com -Credential (Get-Credential)[array]$Sites = Get-SpoSite -Limit All -Template "Group#0"$SPOReport = [System.Collections.Generic.List[Object]]::new()[Int]$i = 0ForEach ($Site in $Sites) {$i++Write-Host ("Processing site {0} {1}/{2}" -f $Site.Title, $i, $Sites.Count)[array]$HoldData = Invoke-HoldRemovalAction -Action GetHolds -SharePointLocation $Site.UrlIf ($HoldData) {Write-Host ("Found holds on site {0} {1}" -f $Site.Title, $Site.Url) -ForegroundColor RedForEach ($Hold in $HoldData) {$HoldInfo = Get-RetentionCompliancePolicy -Identity $HoldIf ($HoldInfo.IsAdaptivePolicy -eq $True) {$AdaptiveFlag = "Adaptive Policy"} Else {$AdaptiveFlag = "Static Policy"}Write-Host ("Microsoft 365 Retention Policy set is {0} ({1})" -f $HoldInfo.Name, $AdaptiveFlag) -ForegroundColor Yellow$Reportline = [PsCustomObject]@{Site = $Site.UrlTitle = $Site.TitleHoldId = $HoldHold = $HoldInfo.name'Policy Scope' = $AdaptiveFlag}$SPOReport.Add($ReportLine)}}}$SPOReport | Export-CSV -NoTypeInformation $CSVFileSPOWrite-Host ("SharePoint Online site hold information available in {0}" -f $CSVFileSPO)
Attribution
Author
Office365itpros