Src/Private/Get-AbrPurviewEDiscovery.ps1
|
function Get-AbrPurviewEDiscovery { <# .SYNOPSIS Used by As Built Report to retrieve Microsoft Purview eDiscovery information. .DESCRIPTION Collects and reports on eDiscovery Cases, Holds, and Searches configured in Microsoft Purview compliance portal. .NOTES Version: 0.1.0 Author: Pai Wei Sing .EXAMPLE .LINK #> [CmdletBinding()] param ( [Parameter(Position = 0, Mandatory)] [string]$TenantId ) begin { Write-PScriboMessage -Message "Collecting Microsoft Purview eDiscovery information for tenant $TenantId." | Out-Null Show-AbrDebugExecutionTime -Start -TitleMessage 'eDiscovery' } process { # Collect Core and Advanced cases separately try { # CaseType enum changed in REST API: Core -> eDiscovery, Advanced -> AdvancedEdiscovery $CoreCases = @(try { Get-ComplianceCase -CaseType eDiscovery -ErrorAction Stop } catch { @() }) $AdvancedCases = @(try { Get-ComplianceCase -CaseType AdvancedEdiscovery -ErrorAction Stop } catch { @() }) $AllCases = @($CoreCases) + @($AdvancedCases) # Collect holds per-case to avoid permission errors from the parameterless call. # The parameterless Get-CaseHoldPolicy requires elevated roles that may not be present. $AllHolds = [System.Collections.ArrayList]::new() foreach ($c in $AllCases) { try { $holds = Get-CaseHoldPolicy -Case $c.Name -ErrorAction SilentlyContinue foreach ($h in $holds) { $AllHolds.Add($h) | Out-Null } } catch { } } if ($AllCases.Count -gt 0) { Section -Style Heading2 'eDiscovery Cases' { #region Coverage Summary $CovObj = [System.Collections.ArrayList]::new() $_pre_CoreeDiscoveryCases_49 = if ($CoreCases.Count -gt 0) { 'Yes' } else { 'No' } $_pre_AdvancedeDiscoveryCa_50 = if ($AdvancedCases.Count -gt 0) { 'Yes' } else { 'No' } $_pre_ActiveCaseHolds_51 = if ($AllHolds.Count -gt 0) { 'Yes' } else { 'No' } $covInObj = [ordered] @{ 'Core eDiscovery Cases' = $_pre_CoreeDiscoveryCases_49 'Advanced eDiscovery Cases' = $_pre_AdvancedeDiscoveryCa_50 'Active Case Holds' = $_pre_ActiveCaseHolds_51 } $CovObj.Add([pscustomobject]$covInObj) | Out-Null if ($Healthcheck -and $script:HealthCheck.Purview.EDiscovery) { $CovObj | Where-Object { $_.'Active Case Holds' -eq 'No' } | Set-Style -Style Warning | Out-Null } $CovTableParams = @{ Name = "eDiscovery Coverage Summary - $TenantId"; List = $true; ColumnWidths = 55, 45 } if ($script:Report.ShowTableCaptions) { $CovTableParams['Caption'] = "- $($CovTableParams.Name)" } $CovObj | Table @CovTableParams #endregion #region Cases Summary Table $OutObj = [System.Collections.ArrayList]::new() foreach ($Case in $AllCases) { try { $caseClosedTime = if ($Case.ClosedDateTime) { $Case.ClosedDateTime.ToString('yyyy-MM-dd') } else { 'N/A' } $caseCreated = if ($Case.WhenCreated) { $Case.WhenCreated.ToString('yyyy-MM-dd') } else { 'N/A' } $caseTypeDisplay = switch ($Case.CaseType) { 'eDiscovery' { 'Core eDiscovery' } 'AdvancedEdiscovery' { 'Advanced eDiscovery' } default { $Case.CaseType } } $_pre_Members_77 = if ($Case.Members) { ($Case.Members -join ', ') } else { 'N/A' } $_pre_ClosedBy_78 = if ($Case.ClosedBy) { $Case.ClosedBy } else { 'N/A' } $inObj = [ordered] @{ 'Case Name' = $Case.Name 'Status' = $script:TextInfo.ToTitleCase($Case.Status) 'Case Type' = $caseTypeDisplay 'Members' = $_pre_Members_77 'Closed By' = $_pre_ClosedBy_78 'Closed Time' = $caseClosedTime 'Created' = $caseCreated } $OutObj.Add([pscustomobject]$inObj) | Out-Null } catch { Write-PScriboMessage -IsWarning -Message "eDiscovery Case '$($Case.Name)': $($_.Exception.Message)" | Out-Null } } if ($Healthcheck -and $script:HealthCheck.Purview.EDiscovery) { $OutObj | Where-Object { $_.'Status' -eq 'Closed' } | Set-Style -Style Warning | Out-Null } $TableParams = @{ Name = "eDiscovery Cases - $TenantId"; List = $false; ColumnWidths = 20, 10, 12, 20, 14, 12, 12 } if ($script:Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } if ($OutObj.Count -gt 0) { $OutObj | Sort-Object -Property 'Case Name' | Table @TableParams } #endregion #region ACSC Inline Check — eDiscovery Cases if ($script:InfoLevel.EDiscovery -ge 3) { $HasActiveHolds = [bool]($AllHolds | Where-Object { $_.Enabled }) Write-AbrPurviewACSCCheck -TenantId $TenantId -SectionName 'eDiscovery Cases' -Checks @( [pscustomobject]@{ ControlId = 'ISM-0854' E8 = 'N/A' Description = 'Legal hold capability exists to preserve evidence for investigations' Check = 'At least one active eDiscovery case hold configured' Status = if ($HasActiveHolds) { 'Pass' } elseif ($AllCases.Count -gt 0) { 'Partial' } else { 'Manual' } } ) } #endregion #region Case Holds per case (InfoLevel 2+) if ($script:InfoLevel.EDiscovery -ge 2) { foreach ($Case in ($AllCases | Where-Object { $_.Status -ne 'Closed' })) { try { $Holds = Get-CaseHoldPolicy -Case $Case.Name -ErrorAction SilentlyContinue if ($Holds) { Section -ExcludeFromTOC -Style NOTOCHeading3 "Holds: $($Case.Name)" { $HoldObj = [System.Collections.ArrayList]::new() foreach ($Hold in $Holds) { try { $_pre_Enabled_128 = if ($Hold.Enabled) { 'Yes' } else { 'No' } $_pre_ExchangeLocation_130 = if ($Hold.ExchangeLocation.Name) { ($Hold.ExchangeLocation.Name -join ', ') } else { 'N/A' } $_pre_SharePointLocation_131 = if ($Hold.SharePointLocation.Name) { ($Hold.SharePointLocation.Name -join ', ') } else { 'N/A' } $holdInObj = [ordered] @{ 'Hold Name' = $Hold.Name 'Enabled' = $_pre_Enabled_128 'Status' = $script:TextInfo.ToTitleCase($Hold.Status) 'Exchange Location' = $_pre_ExchangeLocation_130 'SharePoint Location' = $_pre_SharePointLocation_131 } $HoldObj.Add([pscustomobject]$holdInObj) | Out-Null } catch { Write-PScriboMessage -IsWarning -Message "Case Hold '$($Hold.Name)': $($_.Exception.Message)" | Out-Null } } if ($Healthcheck -and $script:HealthCheck.Purview.EDiscovery) { $HoldObj | Where-Object { $_.'Enabled' -eq 'No' } | Set-Style -Style Critical | Out-Null } $HoldTableParams = @{ Name = "Case Holds - $($Case.Name)"; List = $false; ColumnWidths = 22, 10, 12, 28, 28 } if ($script:Report.ShowTableCaptions) { $HoldTableParams['Caption'] = "- $($HoldTableParams.Name)" } $HoldObj | Sort-Object -Property 'Hold Name' | Table @HoldTableParams } } } catch { Write-PScriboMessage -IsWarning -Message "Case Holds for '$($Case.Name)': $($_.Exception.Message)" | Out-Null } } } #endregion } } else { Write-PScriboMessage -Message "No eDiscovery Case information found for $TenantId. Disabling section." | Out-Null } } catch { Write-PScriboMessage -IsWarning -Message "eDiscovery Section: $($_.Exception.Message)" | Out-Null } # Content Searches (InfoLevel 2+) if ($script:InfoLevel.EDiscovery -ge 2) { try { $Searches = Get-ComplianceSearch -ErrorAction Stop if ($Searches) { Section -Style Heading2 'Content Searches' { $OutObj = [System.Collections.ArrayList]::new() foreach ($Search in $Searches) { try { $_pre_ContentMatchQuery_179 = if ($Search.ContentMatchQuery) { $Search.ContentMatchQuery } else { '--' } $inObj = [ordered] @{ 'Search Name' = $Search.Name 'Status' = $script:TextInfo.ToTitleCase($Search.Status) 'Items Found' = $Search.Items 'Size (MB)' = [math]::Round($Search.Size / 1MB, 2) 'Content Match Query' = $_pre_ContentMatchQuery_179 'Created' = $Search.WhenCreated.ToString('yyyy-MM-dd') } $OutObj.Add([pscustomobject]$inObj) | Out-Null } catch { Write-PScriboMessage -IsWarning -Message "Content Search '$($Search.Name)': $($_.Exception.Message)" | Out-Null } } $TableParams = @{ Name = "Content Searches - $TenantId"; List = $false; ColumnWidths = 22, 12, 10, 10, 34, 12 } if ($script:Report.ShowTableCaptions) { $TableParams['Caption'] = "- $($TableParams.Name)" } $OutObj | Sort-Object -Property 'Search Name' | Table @TableParams } } else { Write-PScriboMessage -Message "No Content Search information found for $TenantId. Disabling section." | Out-Null } } catch { Write-PScriboMessage -IsWarning -Message "Content Search Section: $($_.Exception.Message)" | Out-Null } } #region Advanced eDiscovery Settings (MCCA check-eDiscovery102) try { $AdvancedCases = Get-ComplianceCase -CaseType AdvancedEdiscovery -ErrorAction SilentlyContinue if ($AdvancedCases) { Section -Style Heading2 'Advanced eDiscovery Case Details' { Paragraph "Advanced eDiscovery provides custodian management, legal hold notifications, and built-in analytics. The following shows member assignments and hold status per case." BlankLine foreach ($Case in $AdvancedCases) { try { Section -Style Heading3 $Case.Name { # Members $Members = Get-ComplianceCaseMember -Case $Case.Name -ErrorAction SilentlyContinue $Holds = Get-CaseHoldPolicy -Case $Case.Name -ErrorAction SilentlyContinue $_pre_Status = $script:TextInfo.ToTitleCase($Case.Status) $_pre_MemberCount = if ($Members) { @($Members).Count } else { 0 } $_pre_HoldCount = if ($Holds) { @($Holds).Count } else { 0 } $_pre_ActiveHolds = if ($Holds) { @($Holds | Where-Object { $_.Enabled }).Count } else { 0 } $_pre_Created = if ($Case.CreatedDateTime) { ([datetime]$Case.CreatedDateTime).ToString('yyyy-MM-dd') } else { 'N/A' } $caseInObj = [ordered] @{ 'Case Name' = $Case.Name 'Status' = $_pre_Status 'Members Assigned' = $_pre_MemberCount 'Total Holds' = $_pre_HoldCount 'Active Holds' = $_pre_ActiveHolds 'Created' = $_pre_Created } $CaseDetailObj = [System.Collections.ArrayList]::new() $CaseDetailObj.Add([pscustomobject]$caseInObj) | Out-Null if ($Healthcheck -and $script:HealthCheck.Purview.EDiscovery) { $CaseDetailObj | Where-Object { $_.'Members Assigned' -eq 0 } | Set-Style -Style Warning | Out-Null $CaseDetailObj | Where-Object { $_.'Active Holds' -eq 0 } | Set-Style -Style Warning | Out-Null } $CaseDetailTableParams = @{ Name = "Advanced eDiscovery - $($Case.Name)"; List = $true; ColumnWidths = 40, 60 } if ($script:Report.ShowTableCaptions) { $CaseDetailTableParams['Caption'] = "- $($CaseDetailTableParams.Name)" } $CaseDetailObj | Table @CaseDetailTableParams # Members table if ($Members) { $MemObj = [System.Collections.ArrayList]::new() foreach ($Member in $Members) { $memInObj = [ordered] @{ 'Display Name' = $Member.DisplayName 'Role' = if ($Member.Role) { $Member.Role } else { 'Member' } 'Windows Live ID' = if ($Member.WindowsLiveID) { $Member.WindowsLiveID } else { '--' } } $MemObj.Add([pscustomobject]$memInObj) | Out-Null } $MemTableParams = @{ Name = "Case Members - $($Case.Name)"; List = $false; ColumnWidths = 35, 25, 40 } if ($script:Report.ShowTableCaptions) { $MemTableParams['Caption'] = "- $($MemTableParams.Name)" } $MemObj | Table @MemTableParams } } } catch { Write-PScriboMessage -IsWarning -Message "Advanced eDiscovery Case '$($Case.Name)': $($_.Exception.Message)" | Out-Null } } } } else { Write-PScriboMessage -Message "No Advanced eDiscovery cases found for $TenantId." | Out-Null } } catch { Write-PScriboMessage -IsWarning -Message "Advanced eDiscovery Settings Section: $($_.Exception.Message)" | Out-Null } #endregion } end { Show-AbrDebugExecutionTime -End -TitleMessage 'eDiscovery' } } |