ATP Safe Attachment Scanning Delay

Scenario: We noticed that messages were taking longer to deliver when ATP Safe Attachments was being called for message processing. Users in the Dynamic action policy noticed that the attachment would be reinserted hours after the message delivery. Other users in a Replace action policy noticed that the entire message was delayed delivery for hours.

Script: We found a script online that we modified to fit our needs. Here is our modified version of the script:

ATP Safe Attachment Scanning Delay Scriptlets


#Declare Variables
$Outfile = “C:\temp\ATPSafeAttachmentScan-$(Get-Date -Format “MMddyyyy_hhmmss”).csv”
$MessageList = $null 
$CurrMessages = $null
$Page = 1
$PageSize = 5000
$start = (Get-Date).AddHours(-4)
$end = Get-Date

$MessgaeListCount = 0


#Loop for Message Trace
do 
{   
Write-Host “Collecting Message Trace – Current Count: $MessageListCount – Page $Page…” 

$CurrMessages = Get-MessageTrace -StartDate $start -EndDate $end -PageSize $pagesize -Page $Page -Status Delivered | Where {$_.Size -gt 1MB}

$Page++ 
$MessageList += $CurrMessages 
$MessageListCount = $messageList.count
} until ($CurrMessages -eq $null)



#Loop Message Trace Results for ATP Events
$Row = 0
$TotalRow = $MessageList.count

$MessageList | Select-Object -Skip $Row | % {
    $ID = ($_.MessageTraceId).Guid
    $Sender = $_.SenderAddress
    $Recipient = $_.RecipientAddress
    $Size = $_.Size
    $MessageDetails = $_ | Get-MessageTraceDetail | where { $_.Event -eq “Advanced Threat Protection” -or $_.Event -eq “Deliver”} | sort Date
    $First = ($MessageDetails | select -First 1).Date
    $Last = ($MessageDetails | select -Last 1).Date


    # Only if ATP was used
    If ($First -ne $null -and $Last -ne $null) {
        If ($First -eq $Last) {$Delay = 0}
        Else { $Delay =  [math]::Round((New-TimeSpan –Start $First –End $Last).TotalSeconds,0) }
        $Item = New-Object System.Object;
        $Item | Add-Member -Type NoteProperty -Name “ID” -Value $ID;
       $Item | Add-Member -Type NoteProperty -Name “Sender” -Value $Sender;
        $Item | Add-Member -Type NoteProperty -Name “Recipient” -Value $Recipient;
        $Item | Add-Member -Type NoteProperty -Name “Size” -Value $Size;        $Item | Add-Member -Type NoteProperty -Name “Start” -Value $First;
        $Item | Add-Member -Type NoteProperty -Name “End” -Value $Last;
        $Item | Add-Member -Type NoteProperty -Name “Delay” -Value $Delay;
        #$results += $Item
        $Item | Export-Csv $Outfile  -NoTypeInformation -Append    }

    $Row++
    Write-Host “$(Get-Date -Format “HH:mm”): Processed row $Row out of $TotalRow” }
}






#REF: Original Script that we modified: https://jocha.se/blog/tech/exchange-atp-attachment-delay

Exchange Online: Place a hold on a Soft-Deleted mailbox

Scenario: Recently a user has left the organization and you need to place a hold on the Exchange Online mailbox. Currently the mailbox is SoftDeleted (get-mailbox -softdeletedmailbox).

Solution/Scriptlets:
If a Microsoft Security and Compliance case is not already configured, create a one via PowerShell of course! The scriptlets below will create the Compliance Case, Case Hold Policy, and the Case Hold Rule. The Case Hold Policy will group all specified mailboxes to be placed on Hold and the Case Hold Rule will set the entire mailbox on hold.

New-ComplianceCase “InactiveMailboxes_Hold” -Description “This is a hold for mailboxes that are no longer active”

New-CaseHoldPolicy -Case InactiveMailboxes_hold -Name InactiveMailboxes_Hold -ExchangeLocation Steve,Bob -Enabled $true


Note: If the mailboxes are Inactive/SoftDeleted, you may have to place a period in front of the name similar to this command: New-CaseHoldPolicy -Case InactiveMailboxes_hold -Name InactiveMailboxes_Hold -ExchangeLocation .Steve,.Bob -Enabled $true

New-CaseHoldRule -Name InactiveMailboxes_Hold -Policy InactiveMailboxes_hold


If the eDiscovery case is already created and you need to add users, run the following:

set-caseholdpolicy InactiveMailboxes_hold -AddExchangeLocation Jim

Complex Search criteria for New-ComplianceSearch -ContentMatchQuery

Scenario: You need to search the entire Exchange Online organization for emails with specific criteria:
-Received: 5/1/2020 — 5/15/2020
-Subject: “This is a test”
-Keywords: Trophy OR Lose OR Award

Scriptlets:

$name = “Search_11102020”

New-ComplianceSearch -Name $name -Description “This is a compliance search made from PowerShell” -ExchangeLocation ALL -ContentMatchQuery “Trophy (c:s) Lose(c:s) Award(c:c)(subject:’This is a test’)(received=2020-05-01..2020-05-15)”

Start-ComplianceSearch $name

Notes:
(c:c) = AND
(c:s) = OR


Put a hold on a Soft-Deleted Mailbox in Exchange Online

LEGACY: The instruction below with the steps to create a Mailbox Search is no longer relevant and has been deprecated by Microsoft. Go to this post for an updated version on how to perform this via the Microsoft Security and Compliance Center:
https://ex-shell.com/2020/11/11/exchange-online-place-a-hold-on-a-soft-deleted-mailbox/

Scenario: A person is no longer with the organization and their mailbox is currently retained in Exchange Online as a Soft-Deleted mailbox (get-mailbox -softdeletedmailbox). It was discovered that we need to place the mailbox on a hold to keep the email data preserved.

Scriptlets:
We are going to create a InPlace Hold on the inactive mailbox by running the following commands:

$mbx = get-mailbox -identity <user> -softdeletedmailbox

New-MailboxSearch -Name “InactiveMailboxHold” -SourceMailboxes $mbx.DistinguishedName -InPlaceHoldEnabled $true



Run Get-ADUser by pulling the WindowsLiveID from the Exchange Online Mailbox

Scenario: You need to pull in additional AD properties for users with Exchange Online mailboxes that are only available when running the Get-ADUser command because they are not included in the AD Sync to Microsoft Online/Azure.

Scriptlet:

Declare Variables:

$mbx = Get-mailbox -resultsize unlimited

$ADUserData = @()

$c=0 #Just for a counter

Run the Loop:

$mbx | Where WindowsLiveID -ne “” | Sort | %{

$c++ #Increase the Counter

$upn = $_.windowsliveid #Create the UPN based off windowsliveid

$f = “Userprincipalname -eq ‘$upn'” #Create a Filter for get-aduser

“$c – $f ” #Display on PS Screen

$ADuserData += get-aduser -filter $f -properties * #Fill in $userData with Get-Aduser Data

}

Timeout and Errors when running ‘Get-Mailbox -resultsize Unlimited’ within Exchange Online?

Scearnio: With a large number of mailboxes in our Exchange Online environment, anytime we run a command where it is get-mailbox -resultsize unlmited, we also run into errors/timeouts due to the amount of time and size it takes.

WorkAround: Use filtering within the get-mailbox command to split the data pull and join them into a Master Variable:

Create Variables

$All = @()
$d = (Get-date).adddays(-730)

Set Filter and Run

$f = “{WhenMailboxCreated -ge ‘$d’}”
$1 = get-recipient -filter $f -resultsize unlimited | select name, windowsliveid,primarysmtpaddress

Change Filter and Run

$f = “{WhenMailboxCreated -lt ‘$d’}”
$2 = get-recipient -filter $f -resultsize unlimited| select name, windowsliveid,primarysmtpaddress

Build the Master Variable to contain both datasets

$All += $1
$All += $2

Selecting specific rows from a CSV file to perform Powershell functions/commands against

Scenario: You have a large CSV file that you need to import and perform specific PowerShell commands against. You need the results in a hurry and you want to split up the CSV so you can run multiple PowerShell sessions at once to pull the results in parallel. Instead of creating separate CSV files to import for each PowerShell session, you can tell PowerShell which rows of the import you want to target.

Code Example:   In each PowerShell you can import the CSV file. When calling the variable for the loop you can specify the rows you want to target.  In this example we are going to set customattribute1 to the string “MigrateME” for the first 1000 (well 1001) entries in the csv file in one PowerShell session.  In a second PowerShell session I would import the code but change [0..1000] to [1001..2000],  and repeat the increment for each additional PowerShell session.

$users = Import-csv C:TempAll_Mailboxes.csv

$users[0..1000] | Select -expandproperty alias | %{ set-mailbox $_ -customattribute1 “MigrateMe”}

PowerShell Scriptlets to check if NLA is enabled for Remote Desktop

Scenario: You want to see if  NLA (Network Level Authentication) is enabled on all of your servers for Remote Desktop.

The PowerShell code below is not a .ps1, although you can make a .ps1 out of it if you would like.  I like to teach working  “within” PowerShell, not just being a script monkey.

In order to run the code, you will need to open Powershell OR Powershell ISE with an account with elevated permissions to access your servers (Run-AS).  The purpose of working within PowerShell is so that you can  reuse/recycle the code below to do other things too.
Once PowerShell is opened, follow the steps:
  1. Build your $Servers Variable — Select a method to populate $Servers
  2. Run the Query to build the $Results table
  3. Display the $Results — Select a method

 

 

#1. Build your $Servers Variable — Select one of the methods below to populate $Servers

  $Servers = “ExSrv1″,”ExSrv2”   #Feel free to list the servers by name

    #-or-
        $Servers = Import-csv C:tempservers.csv | Select -expandproperty Name   #Create a CSV with a NAME column header

    #-or-
        $Servers =  get-adcomputer -searchbase “OU=Servers,DC=steve,DC=com” -properties Name -Filter *|  Select -ExpandProperty Name  #Pull a list of AD Computers via AD PowerShell

#2. Run the query to build the $Results variable 

$Results = @()
$Servers | %{
    #Collect NLA Data
    $name = $_
    $NLA = Get-WmiObject -class “Win32_TSGeneralSetting” -Namespace rootcimv2terminalservices -ComputerName $name -Filter “TerminalName=’RDP-tcp'” | Select -ExpandProperty UserAuthenticationRequired
   
    #Display in PowerShell Screen
    “$name : NLA_Enabled:$(If($NLA -eq 1){“Yes”}else{“No”})”

    #Add the data to the $results variable
    $ServerObj = New-Object PSObject
    $ServerObj | Add-Member NoteProperty -Name “Server” -value $name
    $ServerObj | Add-Member NoteProperty -Name “NLA_Enabled” -value $(If($NLA -eq 1){“Yes”}else{“No”})
    $Results += $ServerObj
   
    #Reset Variable
    $Name = $null
    $NLA = 0
}

#3. Display the $Results –  Select a method

$Results

    #-or Export the Results –
        $Results | Export-CSV C:tempresults.csv

    #-or Email the Results –
        $Header = @”
        <style>
        TABLE {border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;}
        TH {border-width: 1px; padding: 3px; border-style: solid; border-color: black; background-color: #6495ED;}
        TD {border-width: 1px; padding: 3px; border-style: solid; border-color: black; text-align: center;}
        tr:nth-child(odd) { background-color:#F2F2F2;}
        tr:nth-child(even) { background-color:white;}
        </style>
“@
        $body =”$($results | ConvertTo-Html -Head $header)”
        $smtp = “smtp.steve.com”
        $to = “steve@steve.com”
        $from = “SteveDoingSomething@steve.com”
        $subject = “Results of NLA”
        send-MailMessage -SmtpServer $smtp -To $to -From $from -Subject $subject -Body $body -BodyAsHtml -Priority high

Analyzing the Get-MobileDeviceStatistics -GetMailboxLog to determine Sync Errors

Scenario:  You have already enabled ActiveSync Debug Logging  (Set-CASMailbox steve -ActiveSyncDebugLogging:$true) and now you want to collect and analyze the logs for errors.  ActiveSync is chatty so it creates a TON of logging data to sift through. So how do we know which log entries are good and which are bad? Here is the dirty way we sifted through it.

Pull the ActiveSync Debug Log:

$1 = Get-MobileDeviceStatistics -Mailbox steve -GetMailboxLog

Lets take a quick glance at the status provided for each ActiveSync transaction provided in the MailboxLogReport

$1.MailboxLogReport | Out-file C:tempmobiledevicedebuglog.txt
$Text = “<status>”
$statuscheck = Get-Content C:tempmobiledevicedebuglog.txt | Select-String -Pattern $Text
$statuscheck

This should provide a long list of StatusCodes.  Usually a status of 1 (<status>1</status>) down the list is great!

BUT if you receive other statuses, then we may have to dive deeper into figuring out what the status means related to the ActiveSync command which caused it.  To do this, you will need to do the following:

  1. Navigate to the Microsoft document which provides a list of each ActiveSync Command and their status codes.  It can be found here.

2. Open the C:tempmobiledevicedebuglog.txt  which was created in previous steps and perform a find on that status code. In this example, we will use:  <status>2</status> as our questionable status.

3. Perform a search for that status and in the responsebody: found in the text file, that section should list the ActiveSync command that was attempted that produced a result.  For example our <status>2</status> was for the PING command.

Digging deeper, it appears the <status>2</status> for PING is fine because it just lets me know that: “Changes occurred in at least one of the monitored folders. The response specifies the changed folders.”  which is fine — no big deal. I found that status code  here.

BUT lets pretend I found a status 8 (<status>8</status>). This would be an issue that was caused/recognized by the Exchange  server.  Something like this could help in troubleshooting a server issue versus a client issue.