Get-SCCExportDataSize.ps1

<#PSScriptInfo
 
.VERSION 4.1
 
.GUID 7451e210-6009-47aa-ac9e-ea38113ed0eb
 
.AUTHOR Aaron Guilmette
 
.COMPANYNAME Microsoft
 
.COPYRIGHT 2020
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI https://www.undocumented-features.com/2019/02/21/calculating-your-daily-export-for-the-security-compliance-center/
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
.DESCRIPTION
Review and calculate Security & Compliance Center Export volume.
 
.PRIVATEDATA
 
#>


<#
.SYNOPSIS
Review and calculate Security & Compliance Center Export volume.
 
.PARAMETER CaseName
Review export statistics for a particular case. Default will review 24 hours.
Use the Hours parameter to expand the scope.
 
.PARAMETER Credential
Is what it says it is. The credential specified must have the ability to run
Get-ComplianceCase. If the credential specified does not have eDiscovery Admin
privileges, only eDiscovery cases that the credential has created or has been
made a member of will be tallied.
 
.PARAMETER Hours
Number of hours to go back looking for export statistics.
 
.PARAMETER IncludeContentSearch
Include Content Search queries as part of the export total.
 
.PARAMETER Logfile
Specify the name of the logfile (if Report is specified).
 
.PARAMETER Report
Choose to output a CSV report.
 
.PARAMETER SearchType
Select mechanism for searching. Search can be:
- SearchByCaseLastModifiedDate: Uses the LastModifiedTimeDate on the actual case.
- SearchByRecentOnly: Uses the -RecentOnly switch for Get-ComplianceCase, which
  appears to use access time in last 24 hours.
- SearchAllCases: Search all cases. You may want to use the -Hours parameter to
  scope further back.
 
.EXAMPLE
.\Get-SCCExportDataSize.ps1
Run with defaults for SearchType SearchByRecentOnly and looking for exports within last 24 hours.
 
.EXAMPLE
.\Get-SCCExportDataSize.ps1 -Hours 48 -SearchType SearchAllCases
Search all cases for exports in the last 48 hours.
 
.EXAMPLE
.\Get-SCCExportDataSize.ps1 -Hours 48 -CaseName "My Test Case"
Search case "My Test Case" for exports in the last 48 hours.
 
.LINK
https://www.undocumented-features.com/2019/02/21/calculating-your-daily-export-for-the-security-compliance-center/
 
.NOTES
2020-04-21 - Updated for PowerShell Gallery.
2019-03-07 - Updated to fix a typo.
2019-03-05 - Update to include job start/end times and user ID information in output.
2019-02-27 - Update to include regular content search. Updated Get-ComplianceSearchAction to use -Export filter.
2019-02-21 - Update to report on eDiscovery case Admins membership.
2019-02-21 - Initial release.
#>


param (
    [array]$CaseName,
    $Credential,
    [int]$Hours = "24",
    [switch]$IncludeContentSearch,
    [string]$Logfile = (Get-Date -Format yyyy-MM-dd) + "_SCCExportDataSize.csv",
    [switch]$Report,
    [ValidateSet('SearchByCaseLastModifiedDate','SearchByRecentOnly','SearchAllCases')]$SearchType = "SearchByRecentOnly"
)

# Check for Get-ComplianceCase cmdlet. If the Get-ComplianceCase cmdlet is not
# found, it's most likely becuase no connection to the Security & Compliance Center
# Powershell is not present.
If (!(Get-Command Get-ComplianceCase -ea silentlycontinue))
{
    try { $ComplianceSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.compliance.protection.outlook.com/powershell-liveid -Credential $Credential -Authentication Basic -AllowRedirection }
    catch { Write-Error -Message "Something went wrong."}
    If ($Credential)
    {
        Import-PSSession $ComplianceSession -AllowClobber
    }
}

# Check for Get-ComplianceCase cmdlet again
If (!(Get-Command Get-ComplianceCase -ea silentlycontinue))
{
    Write-Error -Message "The required command `'Get-ComplianceCase`' is not available. Please connect to the Security & Compliance Center powershell using a credential with access to Get-ComplianceCase."
    Exit    
}

# Check for Get-EDiscoveryCaseAdmin cmdlet. Only members of eDiscoveryCaseAdmins
# have this cmdlet available. If you are running this and are NOT a member of
# this group, then you will only see results of cases for which you are owner
# or member.
If (!(Get-Command Get-EDiscoveryCaseAdmin -ea silentlycontinue))
{
    Write-Warning -Message "Current user is not a member of eDiscovery Case Admins. Only cases for which user has created or is a member will be included in output."    
}

# If we just want to look at a few particular cases, use -CaseName parameter
If ($CaseName)
{
    $Cases = @()
    foreach ($obj in $CaseName)
    {
        $Cases += Get-ComplianceCase $obj
    }
}

# Default will search all recent cases using -RecentOnly parameter. RecentOnly
# is an undocumented parameter, but it appears to look at cases accessed in the
# last 24 hours. If SearchByCaseLastModifiedDate param is present,
# select only cases that show they were modified in the last $Hours hours using
# the LastModifiedDateTime property. The LastModifiedDateTime property ONLY
# pertains to the actual ComplianceCase container (adding/removing members,
# updating the description, or other things that impact the case display). The
# LastModifiedDateTime DOES NOT get updated if case searches or holds are
# added/deleted. SearchAllCases should be obvious.
switch ($SearchType)
{
    SearchByCaseLastModifiedDate {
        # Get only cases modified in the last $Hours period
        # Write-Host -ForegroundColor Green "Using SearchByCaseLastModifiedDate."
        $Cases = Get-ComplianceCase | ? { $_.LastModifiedDateTime -gt (Get-Date).AddHours(-$($Hours)) }
    }
    
    SearchByRecentOnly {
        # Get all cases using RecentOnly parameter. RecentOnly appears to look at
        # cases accessed in the last 24 hours, but it is not documented.
        # Write-Host -ForegroundColor Green "Using SearchByRecentOnly."
        If ($PSBoundParameters.Keys -match "Hours")
        {
            Write-Host -ForegroundColor Red "You can't use the Hours parameter with SearchType SearchByRecentOnly. You should use -SearchType SearchAllCases."
            Exit
        }
        Else
        {
            $Cases = Get-ComplianceCase -RecentOnly
        }
    }
    
    SearchAllCases {
        # Write-Host -ForegroundColor Green "Using SearchAllCases."
        $Cases = Get-ComplianceCase
    }
}

$global:Exports = @()

# Process eDiscovery cases
$TotalCases = $Cases.Count
$i = 1

Foreach ($Case in $Cases)
{
    Write-Progress -Activity "Processing eDiscovery cases" -PercentComplete (($i / $TotalCases) * 100) -Status "$($TotalCases - $i) cases remaining" -Id 1
    Write-Progress -Activity "Processing case $($Case.Name)" -Id 2 -ParentId 1
    [array]$Results = (Get-ComplianceSearchAction -Export -Case $Case.Identity | ? { $_.LastModifiedTime -gt (Get-Date).AddHours(-$($Hours))})
    If ($Results.Results)
    {
        $ResultsCount = $Results.Count
        $r = 1
        Foreach ($Result in $Results)
        {
            Write-Progress -Activity "Calculating exports" -PercentComplete (($r / $ResultsCount) * 100) -Id 3 -ParentId 2
            $global:Data = $Result.Results.Split(";")
            [string]$Bytes = ($Data -match "Total transferred bytes")
            If ($Bytes -ne "") { $Bytes = $Bytes.Split(":")[1].Trim() }
            [int]$TotalTransferredBytes = $Bytes
            $global:DataObj = [ordered]@{
                CaseId                = $Case.Identity
                CaseName              = $Case.Name
                ContentSearchName    = $Result.SearchName
                ExportName             = $Result.Name
                ExportedBytes         = $TotalTransferredBytes
                CreatedBy              = $Result.CreatedBy
                RunBy                   = $Result.RunBy
                StartTime              = $Result.JobStartTime
                EndTime                = $Result.JobEndTime
            }
            $global:ResultData = New-Object PSObject -Property $DataObj
            $global:Exports += $ResultData
            $r++
            Remove-Variable Bytes,TotalTransferredBytes
        }
    }
    $i++
}

# Process Content Search
if ($IncludeContentSearch)
{
    switch ($SearchType)
    {
        SearchByCaseLastModifiedDate {
            # Get only content searches modified in the last $Hours period
            [array]$ContentSearches = (Get-ComplianceSearchAction -Export | ? { $_.LastModifiedTime -gt (Get-Date).Subtract(- $Hours) })    
        }
        
        SearchByRecentOnly {
            # Get all content searches by date modified in the last 24 hours.
            # The undocumented -RecentOnly parameter only applies to eDiscovery
            # cases; I have substituted the value '24' to match what appears to
            # be the behavior.
            If ($PSBoundParameters.Keys -match "Hours")
            {
                Write-Host -ForegroundColor Red "You can't use the Hours parameter with SearchType SearchByRecentOnly. You should use -SearchType SearchAllCases."
                Exit
            }
            Else
            {
                [array]$ContentSearches = (Get-ComplianceSearchAction -Export | ? { $_.LastModifiedTime -gt (Get-Date).AddHours(-24) })
            }
        }
        
        SearchAllCases {
            # Write-Host -ForegroundColor Green "Using SearchAllCases."
            $cmd = "[array]`$ContentSearches = Get-ComplianceSearchAction -Export -Resultsize Unlimited"
            [array]$ContentSearches = Get-ComplianceSearchAction -Export -Resultsize Unlimited
            if ($PSBoundParameters.Keys -match "Hours") { $cmd += " | ? { `$_.LastModifiedTime -gt (Get-Date).AddHours(- `$Hours) }" }
            Invoke-Expression $cmd
        }
    }
    
    $TotalContentSearches = $ContentSearches.Count
    If ($TotalContentSearches -eq 0) { Write-Host -ForegroundColor Yellow "Environment has no content searches meeting the filter criteria." }
    $j = 1
    Foreach ($Search in $ContentSearches)
    {
        Write-Progress -Activity "Processing Content Searches" -PercentComplete (($j / $TotalContentSearches) * 100) -Status "$($TotalContentSearches - $j) searches remaining" -Id 3
        Write-Progress -Activity "Processing search $($Search.Name)" -Id 4 -ParentId 3
        [array]$Results = $Search.Results
        If ($Results)
        {
            $ResultsCount = $Results.Count
            $s = 1
            Foreach ($Result in $Results)
            {
                Write-Progress -Activity "Calculating exports" -PercentComplete (($s / $ResultsCount) * 100) -Id 3 -ParentId 2
                $global:Data = $Result.Split(";")
                [string]$Bytes = ($Data -match "Total transferred bytes")
                If ($Bytes -ne "") { $Bytes = $Bytes.Split(":")[1].Trim() }
                [int]$TotalTransferredBytes = $Bytes
                $global:DataObj = [ordered]@{
                    CaseId                = "N/A"
                    CaseName            = "N/A"
                    ContentSearchName    = $Search.SearchName
                    ExportName            = $Search.Name
                    ExportedBytes        = $TotalTransferredBytes
                    CreatedBy            = $Search.CreatedBy
                    RunBy                = $Search.RunBy
                    StartTime            = $Search.JobStartTime
                    EndTime                = $Search.JobEndTime
                }
                $global:ResultData = New-Object PSObject -Property $DataObj
                $global:Exports += $ResultData
                $s++
                Remove-Variable Bytes, TotalTransferredBytes
            }
        }
        $j++
    }
}

# Add it all up
$Sum = [math]::Round((($Exports.ExportedBytes | Measure-Object -Sum).Sum / 1024768))
If ($Report)
{
    $Exports | Export-Csv $Logfile -Force -NoTypeInformation
    Write-Host -ForegroundColor Green "Report has been exported to $($Logfile)."
}
Write-Host "You have exported $($Sum) megabytes in the last $($Hours) hours. View exports stored in the `$Exports object in your current shell."