functions/New-SPCase.ps1
function New-SPCase { <# .SYNOPSIS Create a new eDiscovery case and add searches for the locations identified. .DESCRIPTION Create a new eDiscovery case and add searches for the locations identified. Users the user-associated site permissions returned by Invoke-SPPermissionScan or ConvertTo-UserSitePermission commands to dynamically generate searches. Note: Technically this supports advanced eDiscovery, but the search will not be accessible through the UI. .PARAMETER PermissionData Data generated by Invoke-SPPermissionScan or ConvertTo-UserSitePermission. Technically accepts any objects that contain two properties: - UserPrincipalName - Site With "Site" containing the sharepoint site Url. .PARAMETER Case Name of the case to create or use. Adds searches to an existing case if present. .PARAMETER Search Name of the search to generate. Searches have their case-name prepended to ensure uniqueness. .PARAMETER Type Whether to use basic or advanced eDiscovery. Defaults to basic, which is for now the recommended way to create searches. .PARAMETER CaseMode Create one case in total or one per user? Note: When selecting per user, the same site may be searched multiple times. Defaults to: Bulk .PARAMETER SearchMode Create one search in total or one per user? (or none at all?) Note: When selecting per user, the same site may be searched multiple times. Defaults to: Bulk .PARAMETER HoldMode Create holds for the identified sites. One in total, one per user? Note: When selecting per user, the same site may be put on hold multiple times. Multiple holds do not conflict, but ALL of them must be lifted for the site to be released. Defaults to: None .PARAMETER SitesPerSearch How many sites to add to a single search. If set to higher than 0, each search will have its index number appended to the name. .PARAMETER SitesPerHold How many sites to add to a single hold. Defaults to 100, cannot be larger than 100. .EXAMPLE PS C:\> New-SPCase -PermissionData $permissions -Case contoso_test -Search Test Creates a new case named 'contoso_test' for the permissions scanned in $permissions. .EXAMPLE PS C:\> New-SPCase -PermissionData (Import-Csv .\export-result.csv) -Case contoso_test -Search Test -SearchMode PerUser -SitesPerSearch 40 Creates a new case named 'contoso_test' It uses the results of the permission scan stored in "export-result.csv" to generate searches. For each user, a separate set of searches will be created (which may include some site duplication). For each user, the total set of applicable sites will be split into sets of no more than 40 sites per search. .EXAMPLE PS C:\> New-SPCase -PermissionData (Import-Csv .\export-result.csv) -Case contoso_test -Search Test -SearchMode None -HoldMode Bulk Creates a new case named 'contoso_test' It uses the results of the permission scan stored in "export-result.csv" to generate a hold over all sites found. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $PermissionData, [Parameter(Mandatory = $true)] [string] $Case, [string] $Search = 'Sharepoint', [ValidateSet('Advanced', 'Basic')] [string] $Type = 'Basic', [ValidateSet('Bulk', 'PerUser')] [string] $CaseMode = 'Bulk', [ValidateSet('Bulk', 'PerUser', 'None')] [string] $SearchMode = 'Bulk', [ValidateSet('Bulk', 'PerUser', 'None')] [string] $HoldMode = 'None', [int] $SitesPerSearch = -1, [ValidateRange(1,100)] [int] $SitesPerHold = 100 ) begin { #region Utility Functions function New-Case { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] $PermissionData, [ValidateSet('Bulk', 'PerUser', 'None')] [string] $SearchMode, [ValidateSet('Bulk', 'PerUser', 'None')] [string] $HoldMode, [int] $SitesPerSearch, [int] $SitesPerHold, [string] $Search, [ValidateSet('Advanced', 'Basic')] [string] $Type, [string] $Name ) begin { $typeHash = @{ Advanced = 'AdvancedEdiscovery' Basic = 'eDiscovery' } } process { $caseObject = Get-ComplianceCase -Identity $Name -ErrorAction Ignore if (-not $caseObject) { $caseObject = New-ComplianceCase -Name $Name -CaseType $typeHash[$Type] } switch ($SearchMode) { Bulk { New-Search -Name $Search -PermissionData $PermissionData -SitesPerSearch $SitesPerSearch -CaseObject $caseObject } PerUser { foreach ($group in $PermissionData | Group-Object UserPrincipalName) { New-Search -Name "$($Search)_$($group.Name -replace '@','_')" -PermissionData $group.Group -SitesPerSearch $SitesPerSearch -CaseObject $caseObject } } } switch ($HoldMode) { Bulk { New-Hold -Name $Search -PermissionData $PermissionData -CaseObject $caseObject -SitesPerHold $SitesPerHold } PerUser { foreach ($group in $PermissionData | Group-Object UserPrincipalName) { New-Hold -Name "$($Search)_$($group.Name -replace '@','_')" -PermissionData $group.Group -CaseObject $caseObject -SitesPerHold $SitesPerHold } } } } } function New-Hold { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [string] $Name, $PermissionData, $CaseObject, [int] $SitesPerHold ) $sites = @($PermissionData.Site | Sort-Object -Unique) $index = 0 $count = 0 do { $policy = $null $count++ $holdName = '{0}_{1}' -f $Name, $count Write-PSFMessage -Message 'Creating hold: {0}' -StringValues $holdName $length = $SitesPerHold if (($sites.Count - $index) -lt $length) { $length = $sites.Count - $index } $endIndex = $index + $length - 1 $currentSites = $sites[$index..$endIndex] <# Creating a Hold Policy will fail if the sites no longer exist or are in readonly mode. Even if only one site in the list is affected, it will fail. So we parse the error for the bad site, remove it from the list and try again, until either: - No bad site is in the list - No sites remain - Another, unrelated error occurs #> while ($true) { # Case: All sites were deleted/readonly if (-not $currentSites) { break} try { $policy = New-CaseHoldPolicy -Name $holdName -Case $CaseObject.Identity -SharePointLocation $currentSites -ErrorAction Stop break } catch { $wasNotFound = $_ -match 'No exact match was found' $wasReadOnly = $_ -match 'locked in ReadOnly mode' if (-not ($wasNotFound -or $wasReadOnly)) { Stop-PSFFunction -Message 'Unexpected Error' -ErrorRecord $_ -EnableException $true -Cmdlet $PSCmdlet } $siteLink = @(($_ | Select-String '(https://[^\s"]+)').Matches.Groups)[1].Value if ($wasNotFound) { Write-PSFMessage -Level Warning -Message 'Site no longer exists: {0}' -StringValues $siteLink } if ($wasReadOnly) { Write-PSFMessage -Level Warning -Message 'Site is read only: {0}' -StringValues $siteLink } $currentSites = $currentSites | Where-Object { $_ -ne $siteLink } } } if ($policy) { $null = New-CaseHoldRule -Name $holdName -Policy $policy.Guid } $index = $index + $length } while ($index -lt $sites.Count) } function New-Search { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] [CmdletBinding()] param ( [string] $Name, $PermissionData, [int] $SitesPerSearch, $CaseObject ) $sites = $PermissionData.Site | Sort-Object -Unique if ($SitesPerSearch -lt 1) { New-ComplianceSearch -Name "$($CaseObject.Name)_$($Name)" -Case $CaseObject.Name -SharePointLocation $sites return } $index = @{ Number = 0 } foreach ($set in $sites | Group-Object { $index.Number++; ('{0}' -f (($index.Number - 1) / $SitesPerSearch) -replace '\.\d+$' -as [int]) + 1 }) { New-ComplianceSearch -Name "$($CaseObject.Name)_$($Name)_$($set.Name)" -Case $CaseObject.Name -SharePointLocation $set.Group } } #endregion Utility Functions } process { switch ($CaseMode) { Bulk { $param = $PSBoundParameters | ConvertTo-PSFHashtable -Include PermissionData, SearchMode, SitesPerSearch, SitesPerHold, Search, Type, HoldMode -Inherit New-Case @param -Name $Case } PerUser { foreach ($group in $PermissionData | Group-Object UserPrincipalName) { $param = $PSBoundParameters | ConvertTo-PSFHashtable -Include SearchMode, SitesPerSearch, SitesPerHold, Search, Type, HoldMode -Inherit $param.PermissionData = $group.Group New-Case @param -Name "$($Case)_$($group.Name -replace '@','_')" } } } } } |