Get-UserHoldPolicies.ps1

<#PSScriptInfo
 
.VERSION 4.0
 
.GUID ca632a17-1da3-4069-88c1-5aaf2cce3a1b
 
.AUTHOR Aaron Guilmette
 
.COMPANYNAME Microsoft
 
.COPYRIGHT 2020
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI https://www.undocumented-features.com/2018/05/15/update-to-the-get-userholdpolicies-tool/
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
.DESCRIPTION
View hold policies applied to one or more mailboxes.
 
.PRIVATEDATA
 
#>


<#
.SYNOPSIS
List holds applied to a user.
 
This script will return holds applied to users through the mechanisms of:
- Exchange Online in-place holds
- Litigation hold
- MRM policies with retention policy tags
- Security & Compliance Center eDiscovery cases with holds
- Security & Compliance Center retention policies
 
In order to return data for eDiscovery cases, the account used to run
the script must be a member of eDiscovery Administrators or be a member
of every open case.
 
.PARAMETER Credential
Specify a PSCredential object for connecting to Exchange Online and the Security
& Compliane Center.
 
.PARAMETER ExcludeLegacyExchangePolicies
Choose whether to exclude legacy MRM policies for Exchange Online mailboxes. If
not specified, attempt to locate policies that have policy tags with retention
enabled. Legacy Exchange MRM policies don't actually have retention, per se, but
they can be the source of unexpected behavior if content is being moved/deleted
without a user's knowledge.
 
.PARAMETER Identity
Specify an individual user for retreiving hold policies.
 
.EXAMPLE
.\Get-UserHoldPolicies.ps1 -Identity AdeleV
Displays all of the holds applied to user AdeleV.
 
.EXAMPLE
Get-Mailbox -Resultsize Unlimited | .\Get-UserHoldPolicies.ps1
Displays all explicit holds applied to all mailboxes.
 
.EXAMPLE
Get-Mailbox -Resultsize Unlimited | .\Get-UserHoldPolicies.ps1 -IncludeInheritiedPolicies
Displays all holds (explicit and inherited) applied to all mailboxes.
 
.EXAMPLE
Get-Mailbox -Resultsize Unlimited | .\Get-UserHoldPolicies.ps1 -OutputFile C:\Temp\UserHolds.csv
Export all explicit holds applied to all mailboxes to CSV C:\Temp\UserHolds.csv
 
.EXAMPLE
(.\Get-UserHoldPolicies.ps1 -Identity AdeleV) -OutputFile C:\Temp\AdeleVHolds.csv
Export all explicit holds applied to AdeleV to CSV C:\Temp\AdeleVHolds.csv
 
.EXAMPLE
Get-Mailbox AdeleV | .\Get-UserHoldPolicies.ps1 -IncludeInheritedPolicies
Display all (including inherited) holds applied to AdeleV.
 
.EXAMPLE
$Holds = Get-Mailbox A* | .\Get-UserHoldPolicies.ps1 -IncludeInheritedPolicies
Save all holds for users starting with A to $holds variable.
 
.NOTES
2019-06-04 Added information for DelayHoldApplied
2019-01-23 Added display info for excluded legacy policies and disposition after expiration.
            Updated URL in Links.
2018-05-15 Added capability to display global inherited policies.
            Added OutputFile parameter.
            Updated output for MRM policies to display object guid.
2017-10-31 Initial release.
 
.LINK
https://www.undocumented-features.com/2019/01/23/update-to-the-get-userholdpolicies-tool-2/
 
.LINK
https://www.undocumented-features.com/2018/05/15/update-to-the-get-userholdpolicies-tool/
 
.LINK
https://www.undocumented-features.com/2017/10/31/display-or-export-all-user-mailbox-holds/
#>

[CmdletBinding()]
param (
    [System.Management.Automation.PSCredential]$Credential,
    [switch]$ExcludeLegacyExchangePolicies = $True,
    [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName = $True,Position=1)]
    $Identity,
    [switch]$IncludeInheritedPolicies,
    [string]$OutputFile
)

begin
{
    If (!(Get-Command Get-Mailbox -ea silentlycontinue))
    {
        $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Credential -Authentication Basic -AllowRedirection
        Import-PSSession $Session
    }
    
    If (!(Get-Command Get-CaseHoldPolicy -ea silently continue))
    {
        $ComplianceSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid -Credential $Credential -Authentication Basic -AllowRedirection
        Import-PSSession $ComplianceSession -AllowClobber
    }
    
    # Check parameters
    If ($PSBoundParameters.ContainsKey('Identity') -and $MyInvocation.PipelineLength -eq 1)
    {
        [object[]]$Identity = Get-Mailbox $Identity
    }
    [pscustomobject]$ErrorData = @()
    [pscustomobject]$Data = @()
        
    # Exclude processing of legacy Exchange Retention Policies if
    # -ExcludeLegacyExchangePolicies is set. This checks for policies that have
    # policy tags with retention enabled.
    If (!($ExcludeLegacyExchangePolicies))
    {
        $LegacyRetentionPolicies = Get-RetentionPolicy
        $LegacyRetentionPoliciesWithRetentionEnabled = @()
        Get-RetentionPolicy | % {
            foreach ($tag in $_.RetentionPolicyTagLinks)
            {
                If ((Get-RetentionPolicyTag $Tag).RetentionEnabled -eq $True) { $LegacyRetentionPoliciesWithRetentionEnabled += $_.Name }
            }
        }
        $LegacyRetentionPoliciesWithRetentionEnabled = $LegacyRetentionPoliciesWithRetentionEnabled | Sort -Unique
    } # End If !$ExcludeLegacyExchangePolicies
    
    # Retrieve inherited policies created in the Security & Compliance Center.
    # This is determined by looking for policies where the ExchangeLocation is
    # specified as "All", since those policies are not stamped on the mailbox.
    If ($IncludeInheritedPolicies)
    {
        $InheritedPolicies = (Get-RetentionCompliancePolicy -DistributionDetail) | ? { $_.ExchangeLocation -match "All" -and $_.Enabled -eq $True -and $_.DistributionStatus -eq "Success" -and $_.Mode -eq "Enforce"}
    } # End If $IncludeInheritedPolicies
}
process
{
    If ($PSBoundParameters.ContainsKey('Identity') -and $MyInvocation.PipelineLength -eq 1)
    {
        $DisplayName = $Identity.Name
        $PrimarySmtp = $Identity.PrimarySmtpAddress
        $InPlaceHoldPolicies = $Identity.InPlaceHolds
        [bool]$LitigationHold = $Identity.LitigationHoldEnabled
        If ($Identity.RetentionPolicy -iin $LegacyRetentionPoliciesWithRetentionEnabled)
            {
            $ExoRetentionPolicy = $Identity.RetentionPolicy        
            }
    }
        
    Else
    {
        $DisplayName = $_.Name
        $PrimarySmtp = $_.PrimarySmtpAddress
        $InPlaceHoldPolicies = $_.InPlaceHolds
        [bool]$LitigationHold = $_.LitigationHoldEnabled
            If ($_.RetentionPolicy -iin $LegacyRetentionPoliciesWithRetentionEnabled)
            {
                $ExoRetentionPolicy = $_.RetentionPolicy
            }
        }
    
    # Process values that appear in InPlaceHoldPolicies
    Foreach ($pol in $InPlaceHoldPolicies)
    {
        # eDiscovery Cases
        if ($pol -match "UniH")
        {
            $Type = "eDiscoveryCase"
            $policy = $pol.Substring(4)
            try
            {
                $Data += Get-CaseHoldPolicy -Identity $Policy | Select `
                                                                       @{ N = "Username"; E = { $DisplayName } },
                                                                       @{ N = "Mail"; E = { $PrimarySmtp } },
                                                                       @{ N = "Hold Placed By"; E = { $_.Name } },
                                                                       @{ N = "Policy Guid"; E = { $Policy } },
                                                                       @{ N = "Case Name"; E = { (Get-ComplianceCase $_.CaseID).Name } },
                                                                       @{ N = "Case Guid"; E = { $_.CaseID } },
                                                                       @{ N = "Hold Type"; E = { $Type } },
                                                                       @{ N = "Delete Type";  E= { "N/A" } }
            }
            catch
            {
                $ErrorDetail = $_.Exception.Message.ToString()
                $ErrorData += @{
                    'Username'        = $DisplayName;
                    'Mail'            = $PrimarySmtp;
                    'ErrorMesage'   = $ErrorDetail
                }
            }
        }
        
        # Security & Compliance Center Retention Policies. These policies are
        # reflected in the "InPlaceHolds" property of a mailbox.
        if ($pol -match "^mbx")
        {
            $Type = "SecComplianceRetentionPolicy-Mailbox"
            $policy = $pol.Substring(3).Split(":")[0]
            $policyDeleteTypeValue = $pol.Substring(3).Split(":")[1]
            switch ($PolicyDeleteTypeValue)
            {
                1 { $PolicyDeleteType = "DeleteOnly" }
                2 { $PolicyDeleteType = "RetainNoDeleteAtExpiration" }
                3 { $PolicyDeleteType = "RetainAndDeleteAtExpiration"}
            }
            $Data += Get-RetentionCompliancePolicy $policy | select `
                                                                    @{ N = "Username"; E = { $DisplayName } },
                                                                    @{ N = "Mail"; E = { $PrimarySmtp } },
                                                                    @{ N = "Hold Placed By"; E = { $_.Name } },
                                                                    @{ N = "Policy Guid"; E = { $policy } },
                                                                    @{ N = "Case Name"; E = { "Not Applicable" } },
                                                                    @{ N = "Case Guid"; E = { "Not Applicable" } },
                                                                    @{ N = "Hold Type"; E = { $Type } },
                                                                    @{ N = "Delete Type"; E = { $PolicyDeleteType } }
        }
        if ($pol -match "^\-mbx")
        {
            $Type = "ExcludedSecComplianceRetentionPolicy"
            $policy = $pol.Substring(4).Split(":")[0]
            $policyDeleteTypeValue = $pol.Substring(3).Split(":")[1]
            switch ($PolicyDeleteTypeValue)
            {
                1 { $PolicyDeleteType = "DeleteOnly" }
                2 { $PolicyDeleteType = "RetainNoDeleteAtExpiration" }
                3 { $PolicyDeleteType = "RetainAndDeleteAtExpiration" }
            }
            $Data += Get-RetentionCompliancePolicy $policy | select `
                                                                    @{ N = "Username"; E = { $DisplayName } },
                                                                    @{ N = "Mail"; E = { $PrimarySmtp } },
                                                                    @{ N = "Hold Placed By"; E = { $_.Name } },
                                                                    @{ N = "Policy Guid"; E = { $policy } },
                                                                    @{ N = "Case Name"; E = { "Not Applicable" } },
                                                                    @{ N = "Case Guid"; E = { "Not Applicable" } },
                                                                    @{ N = "Hold Type"; E = { $Type } },
                                                                    @{ N = "Delete Type"; E = { $PolicyDeleteType } }
        }
        if ($pol -match "^skp")
        {
            $Type = "SecComplianceRetentionPolicy-Skype"
            $policy = $pol.Substring(3).Split(":")[0]
            $policyDeleteTypeValue = $pol.Substring(3).Split(":")[1]
            switch ($PolicyDeleteTypeValue)
            {
                1 { $PolicyDeleteType = "DeleteOnly" }
                2 { $PolicyDeleteType = "RetainNoDeleteAtExpiration" }
                3 { $PolicyDeleteType = "RetainAndDeleteAtExpiration" }
            }
            $Data += Get-RetentionCompliancePolicy $policy | select `
                                                                    @{ N = "Username"; E = { $DisplayName } },
                                                                    @{ N = "Mail"; E = { $PrimarySmtp } },
                                                                    @{ N = "Hold Placed By"; E = { $_.Name } },
                                                                    @{ N = "Policy Guid"; E = { $policy } },
                                                                    @{ N = "Case Name"; E = { "Not Applicable" } },
                                                                    @{ N = "Case Guid"; E = { "Not Applicable" } },
                                                                    @{ N = "Hold Type"; E = { $Type } },
                                                                    @{ N = "Delete Type"; E = { $PolicyDeleteType } }
        }
    } # End Foreach $pol in $InPlaceHoldPolicies
    
    # Check for Object's LitigationHold property. You can query this property
    # via Get-Mailbox and look for the LitigationHoldEnabled property.
    If ($LitigationHold -eq $True)
    {
        $Type = "LitigationHold"
        $Policy = "Mailbox Litigation Hold"
        $LitigationHoldData = @{
                    'Username' = $DisplayName;
                    'Mail' = $PrimarySmtp;
                    'Hold Placed By' = $Policy;
                    'Policy Guid' = "Not Applicable";
                    'Case Name'    = "Not Applicable";
                    'Case Guid'    = "Not Applicable";
                    'Hold Type'    = $Type;
                    'Delete Type' = "Not Applicable"
        }
        $LitigationHoldRowData = [pscustomobject]$LitigationHoldData
        $Data += $LitigationHoldRowData
    } # End If $LitigationHold
    
    # Include Inherited policies from the Security & Compliance Center. These
    # policies are not stamped on the mailbox.
    If ($IncludeInheritedPolicies)
    {
        foreach ($InheritedPolicy in $InheritedPolicies)
        {
            $Type = "SecComplianceRetentionPolicy (Inherited)"
            $Policy = $InheritedPolicy.Name
            $Guid = $InheritedPolicy.Guid
            $InheritedPolicyData = @{
                        'Username' = $DisplayName;
                        'Mail' = $PrimarySmtp;
                        'Hold Placed By' = $Policy;
                        'Policy Guid' = $Guid;
                        'Case Name'    = "Not Applicable";
                        'Case Guid'    = "Not Applicable";
                        'Hold Type'    = $Type;
                        'Delete Type' = "Undetermined"
            }
            $InheritedPolicyRowData = [pscustomobject]$InheritedPolicyData
            $Data += $InheritedPolicyRowData
        }
    } # End If IncludeInheritedPolicies
    
    # If parameter -ExcludeLegacyExchangePolicies is not set, check the legacy
    # Exchange policies to see what policies with retention are applied to the
    # mailbox.
    If (!($ExcludeLegacyExchangePolicies))
    {
        If ($ExoRetentionPolicy)
        {
            $Type = "LegacyRetentionPolicy"
            $Policy = $ExoRetentionPolicy
            $PolicyGuid = ($LegacyRetentionPolicies | ? { $_.Name -eq $Policy }).Guid
            $ExoRetentionPolicyData = @{
                        'Username' = $DisplayName;
                        'Mail' = $PrimarySmtp;
                        'Hold Placed By' = $Policy;
                        'Policy Guid' = $PolicyGuid;
                        'Case Name' = "Not Applicable";
                        'Case Guid'    = "Not Applicable";
                        'Hold Type'    = $Type;
                        'Delete Type' = "Not Applicable"
            }
            $ExoRetentionPolicyRowData = [pscustomobject]$ExoRetentionPolicyData
            $Data += $ExoRetentionPolicyRowData
        }
    } # If !$ExcludeLegacyExchangePolicies
    
    # Finally, check for DelayHold
    If ($Identity.DelayHoldApplied -eq $True)
    {
        $Type = "Delayed Hold"
        $DelayHoldPolicyData = @{
            'Username'          = $DisplayName;
            'Mail'              = $PrimarySmtp;
            'Hold Placed By'  = "DelayHoldProcess";
            'Policy Guid'      = "Not Applicable";
            'Case Name'          = "Not Applicable";
            'Case Guid'          = "Not Applicable";
            'Hold Type'          = $Type;
            'Delete Type'      = "Not Applicable"
        }
        $DelayHoldRowData = [pscustomobject]$DelayHoldPolicyData
        $Data += $DelayedHoldRowData
    }
}
End
{
    If ($OutputFile)
    {
        $Data | Export-Csv $OutputFile -Force -Confirm:$False -NoTypeInformation
        if ($ErrorData) { $ErrorData | Export-Csv $OutputFile+"_Errors.txt" -Force -Confirm:$false -NoTypeInformation }
    }
    Else
    {
        Write-Output $Data
    }
}