Private/Compliance/MessageSearch.ps1

function MessageSearch {
    <#
        .SYNOPSIS
        Search for content with Microsoft's compliance search and optionally delete found content. A maximum of 10 mail messages per mailbox (per search)

        .DESCRIPTION
        Search for content with Microsoft's compliance search and optionally delete said content
        Currently supports the Exchange workflow

        .PARAMETER _ExchangeServer
        Add the FQDN to the on-premises Exchange Server. Skip this step if using Exchange Online

        .PARAMETER _ExchangeServerBasicAuth
        Check this checkbox if connecting to Exchange when Basic Authentication required. Skip this step if using Exchange Online

        .PARAMETER _ExchangeOnline
        Check this checkbox to connect to the Office 365 Security and Compliance Center (SCC).
        You will be prompted to connect if you are not already
        If You have connected to Exchange on-premises or have more than one Session, each session will be removed, and a new connection will be initiated
        You will have to type your credentials each new session.

        .PARAMETER RequiredSearchName
        A unique search name for your organization and specific for your search.

        Example:
        Search and Delete all messages with the words pear and apple in the subject

        .PARAMETER __HardDelete
        Allows for Hard Deletion of messages. In other words, they will be unrecoverable
        Otherwise deletions will be soft deleted. That is, unless Single Item Recovery is not enabled
        Use with caution.
        NOTE: Microsoft’s compliance searches will delete a maximum of 10 messages (per mailbox) per search
                Use additional searches to remove more than 10 messages.


        .PARAMETER _From
        Accepts a single email address

        .PARAMETER _To

        Accepts one or more email addresses separated by commas

        Examples:

        1. jane@contoso.com
        - The search will find email with just Jane

        2. jane@contoso.com, joe@contoso.com
        - The search will not find email with just Jane
        - The search will find email with Jane and Joe


        .PARAMETER _SubjectContains
        The search will only find email with this subject

        Use the checkbox "SubjectContainsIsCommaSeparated" to specify a list of words (comma separated), all of which must be found in the email's subject

        Example:
        1. Apple
        2. Apple, Pear, Kiwi

        If not using SubjectContainsIsCommaSeparated checkbox, the search will look for the entire string

        If using SubjectContainsIsCommaSeparated checkbox, the search will look for each word in the comma-separated list of words. Not together as a phrase.


        .PARAMETER _SubjectContainsIsCommaSeparated
        Check this checkbox to look for more than one word in the subject

        Example:
        1. You check this checkbox.
        2. In the "Subject" field you type:
            Apple, Pear, Orange

        - The search will find this subject:
            The Apple, Pear and Orange

        - The search will not find:
            The Apple, Pear and Banana

        .PARAMETER _SubjectDoesNotContain
        This removes from search results, any emails with the word or phrase specified

        Commas are literal, not treated as CSVs
        In this example, you would not find results with the entire phrase, “Apple, Pear” with the space shown.
        Note: you would NOT find results as this is SubjectDoesNotContain.

        Example:
        1. Apple
        2. Apple, Pear

        .PARAMETER _DateStart
        A date (and optionally a time) in the past from when you wish to start the search

        Use the format: YYYY-MM-DDThh:mm:ss

        A few examples:

        2020-06-25
        2020-06-25T14:00
        2020-06-25T14:00:12

        .PARAMETER _DateEnd
        A date (and optionally a time) in the past from when you wish to end the search

        NOTE: Must be more recent that DateStart

        Use the format: YYYY-MM-DDThh:mm:ss

        A few examples:

        2020-06-25
        2020-06-25T15:00
        2020-06-25T15:00:12

        .PARAMETER AttachmentName
        Find any emails with the AttachmentName specified
        Note: Only one attachment name can be specified per search

        Examples:
        1. Attachment Test.txt
        2. AttachmentTest.txt


        .PARAMETER MailboxesToSearch
        The MailboxesToSearch parameter specifies the mailboxes to include

        Valid values are:
        A regular user mailbox. Including other types of mailboxes (for example, inactive mailboxes or Microsoft 365 guest users) is controlled by the AllowNotFoundExchangeLocationsEnabled parameter.
        A distribution group or mail-enabled security group (all mailboxes that are currently members of the group).

        NOTE:
        To specify a mailbox or distribution group, use the email address
        You can specify multiple values separated by commas.

        Leave blank for the default value. The default value is All, for all mailboxes.

        .PARAMETER ExceptionList
        A list of exceptions to ALL, the default value of MailboxesToSearch (when MailboxesToSearch is left blank, ALL is used)

        This parameter specifies the mailboxes to exclude when you use the value All for the MailboxesToSearch parameter. Valid values are:
        - A mailbox(es)
        - A distribution group(s) or mail-enabled security group (all mailboxes that are currently members of the group)
        You can specify multiple values separated by commas.


        .PARAMETER ExceptionFilePath

        Specify a file path to a text file.
        Example: c:\scripts\mailboxes.txt

        1. The file should be a text file
        2. The file should contain a list of emailaddresses separated by commas


        .EXAMPLE

        Use PowerShell 5.1
        Simply type the command in an elevated PowerShell prompt:

        New-MessageSearch

        .NOTES
        Regarding Purging

        SoftDelete: Purged items are recoverable by users until the deleted item retention period expires

        HardDelete (cloud only): Purged items are marked for permanent removal from the mailbox and will be permanently removed the next time the mailbox is processed by the Managed Folder Assistant
        If single item recovery is enabled on the mailbox, purged items will be permanently removed after the deleted item retention period expires.

        Special characters

        Some special characters are not included in the search index and therefore are not searchable
        This also includes the special characters that represent search operators in the search query.
        Here's a list of special characters that are either replaced by a blank space in the actual search query or cause a search error.

        + - = : ! @ # % ^ & ; _ / ? ( ) [ ] { }

        .LINK
        Compliance searches can be found here:
        https://protection.office.com/contentsearch

        the new beta version can be found here:
        https://protection.office.com/contentsearchbeta?ContentOnly=1

        This site allows for you to:
        1. Previewing Results
        2. Exporting Results
        3. Creating report on results

        IMPORTANT: PRIOR TO DELETING, have a look here to determine what you are about to delete
        #>


    [CmdletBinding()]
    param (
        [Parameter()]
        [string]
        $_ExchangeServer,

        [Parameter()]
        [switch]
        $_ExchangeServerBasicAuth,

        [Parameter()]
        [switch]
        $_ExchangeOnline,

        [Parameter(Mandatory)]
        [string]
        $RequiredSearchName,

        [Parameter()]
        [switch]
        $__HardDelete,

        [Parameter()]
        $_From,

        [Parameter()]
        [string[]]
        $_Content,

        [Parameter()]
        [mailaddress[]]
        $_To,

        [Parameter()]
        [mailaddress[]]
        $_CC,

        [Parameter()]
        [switch]
        $_SubjectContainsIsCommaSeparated,

        [Parameter()]
        [string]
        $_SubjectContains,

        [Parameter()]
        [string]
        $_SubjectDoesNotContain,

        [Parameter()]
        [datetime]
        $_DateStart,

        [Parameter()]
        [datetime]
        $_DateEnd = ([datetime]::Now),

        [Parameter()]
        [string]
        $AttachmentName,

        [Parameter()]
        [string]
        $CaseName,

        [Parameter()]
        [mailaddress[]]
        $MailboxesToSearch,

        [Parameter()]
        [mailaddress[]]
        $ExceptionList,

        [Parameter()]
        [string]
        [ValidateScript( { Test-Path $_ })]
        $ExceptionFilePath
    )
    $Script:HardOrSoft = $null
    if ($__HardDelete -and $_ExchangeOnline) { $Script:HardOrSoft = 'HardDelete' }
    else { $Script:HardOrSoft = 'SoftDelete' }

    $Splat = @{ }
    $Splat['Name'] = $RequiredSearchName

    $Query = [System.Collections.Generic.List[string]]::New()
    $ORQuery = [System.Collections.Generic.List[string]]::New()


    if ( $_From ) { $Query.Add('From:{0}' -f $_From) }
    if ( $_Content ) { (@($_Content) -ne '').split(',').trim() | ForEach-Object { $ORQuery.Add('"{0}"' -f $_) } }
    if ( $_CC ) { (@($_CC) -ne '') | ForEach-Object { $Query.Add('CC:{0}' -f $_) } }
    if ( $_To ) { (@($_To) -ne '') | ForEach-Object { $Query.Add('To:{0}' -f $_) } }
    if ( $_SubjectContains ) {
        if ( $_SubjectContainsIsCommaSeparated ) { (@($_SubjectContains) -ne '').split(',').trim() | ForEach-Object { $Query.Add('Subject:"{0}"' -f $_) } }
        else { $Query.Add(('Subject:"{0}"' -f $_SubjectContains)) }
    }
    if ( $_SubjectDoesNotContain ) { (@($_SubjectDoesNotContain) -ne '') | ForEach-Object { $Query.Add('-Subject:"{0}"' -f $_) } }
    if ( $_DateStart ) { $Query.Add(('Received:{0}..{1}' -f $_DateStart.ToUniversalTime().ToString("O") , $_DateEnd.ToUniversalTime().ToString("O"))) }
    if ( $AttachmentName ) { $Query.Add('Attachment:"{0}"' -f $AttachmentName) }
    if ( $Query ) {
        $KQL = '({0})' -f (@($Query) -join ') AND (')
    }
    if ($ORQuery) {
        if ($KQL) {
            $KQL = '{0} AND ({1})' -f $KQL, ('({0})' -f (@($ORQuery) -join ') OR ('))
        }
        else {
            $KQL = '({0})' -f (@($ORQuery) -join ') OR (')
        }

    }
    $Splat['ContentMatchQuery'] = $KQL
    if (-not $MailboxesToSearch) { $Splat['ExchangeLocation'] = 'ALL' }
    if (-not $MailboxesToSearch -and ($ExceptionList -or $ExceptionFilePath)) {
        if ($ExceptionFilePath -or ($ExceptionList -and $ExceptionFilePath)) {
            $Exceptions = (Get-Content $ExceptionFilePath).split(',')
        }
        else { $Exceptions = $ExceptionList }
        $Splat['ExchangeLocationExclusion'] = $Exceptions
    }
    elseif ($MailboxesToSearch) { $Splat['ExchangeLocation'] = $MailboxesToSearch }

    $Session = Get-PSSession
    if ($_ExchangeServer -and ($Session.State -match 'Broken|Disconnected|Closed' -or (-not (Get-Command Get-ComplianceSearch -ErrorAction SilentlyContinue)) -or
            $Session.Count -gt 1 -or $Session.ComputerName -match 'ps.compliance.protection.outlook.com' -and
            (Get-PSSession -Name $_ExchangeServer -ErrorAction SilentlyContinue).State -ne 'Opened')) {
        Get-PSSession | Remove-PSSession
        Connect-OnPremExchange -Server $_ExchangeServer -Basic:$_ExchangeServerBasicAuth
    }
    elseif ($_ExchangeOnline -and ($Session.State -match 'Broken|Disconnected|Closed' -or (-not (Get-Command 'Get-ComplianceSearch' -ErrorAction SilentlyContinue) -or
                $Session.Count -gt 1 -or $Session.ComputerName -notmatch 'ps.compliance.protection.outlook.com'))) {
        Get-PSSession | Remove-PSSession
        Connect-ExchangeOnline -ConnectionUri 'https://ps.compliance.protection.outlook.com/powershell-liveid' -ShowBanner:$false
        Write-Host "You have successfully connected to Security & Compliance Center" -foregroundcolor "magenta" -backgroundcolor "white"
    }
    if (-not $_ExchangeServer -and -not $_ExchangeOnline) { return }

    if ($CaseName) {
        if (-not ($null = Get-ComplianceCase -Identity $CaseName -ErrorAction SilentlyContinue )) {
            $NewCase = New-ComplianceCase -Name $CaseName
            Write-Host "New e-discovery case created: $($NewCase.Name)" -ForegroundColor Cyan
        }
        $Splat['Case'] = $CaseName
    }

    $Splat
}