<#
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE
#>
# Version 25.04.16.1347
<#
.NOTES
Name: Test-ExchAVExclusions.ps1
Requires: Administrator rights
Major Release History:
06/16/2021 - Initial Release
06/26/2023 - Added ability to scan processes
02/10/2025 - Added AMSI detection
.SYNOPSIS
Uses EICAR files to verify that all Exchange paths that should be excluded from AV
scanning are excluded.
Checks Exchange processes for Non-Default modules being loaded into them.
.DESCRIPTION
Writes an EICAR test file https://en.wikipedia.org/wiki/EICAR_test_file to all
paths specified by
https://docs.microsoft.com/en-us/Exchange/antispam-and-antimalware/windows-
antivirus-software?view=exchserver-2019 and
https://docs.microsoft.com/en-us/exchange/anti-virus-software-in-the-operating-
system-on-exchange-servers-exchange-2013-help
If the file is removed then the path is not properly excluded from AV Scanning.
IF the file is not removed then it should be properly excluded.
Once the files are created it will wait 300 seconds for AV to "see" and remove the
file.
Pulls all Exchange processes and their modules.
Excludes known modules and reports all Non-Default modules.
Non-Default modules should be reviewed to ensure they are expected.
AV Modules loaded into Exchange Processes may indicate that AV Process Exclusions
are NOT properly configured.
.PARAMETER Recurse
Will test not just the root folders but all SubFolders.
Generally should not be needed unless all folders pass without -Recuse but AV is
still suspected.
.PARAMETER WaitingTimeForAVAnalysisInMinutes
Set the waiting time for AV to analyze the EICAR files. Default is 5 minutes.
.PARAMETER SkipVersionCheck
Skip script version verification.
.PARAMETER ScriptUpdateOnly
Just update script version to latest one.
.OUTPUTS
Log file:
$PSScriptRoot\Test-ExchAvExclusions-#DataTime#.txt
List of Scanned Folders:
$PSScriptRoot\BadExclusions-#DataTime#.txt
.EXAMPLE
.\Test-ExchAVExclusions.ps1
Puts and removes an EICAR file in all test paths.
.EXAMPLE
.\Test-ExchAVExclusions.ps1 -Recurse
Puts and Remove an EICAR file in all test paths + all SubFolders.
#>
[CmdletBinding(DefaultParameterSetName = 'Test')]
param (
[Parameter(ParameterSetName = "Test")]
[int]$WaitingTimeForAVAnalysisInMinutes = 5,
[Parameter(ParameterSetName = "Test")]
[switch]$Recurse,
[Parameter(ParameterSetName = "Test")]
[switch]$SkipVersionCheck,
[Parameter(Mandatory = $true, ParameterSetName = "ScriptUpdateOnly")]
[switch]$ScriptUpdateOnly
)
function Confirm-Administrator {
$currentPrincipal = New-Object
Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurre
nt() )
return
$currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator
)
}
function Invoke-CatchActionError {
[CmdletBinding()]
param(
[ScriptBlock]$CatchActionFunction
)
if ($null -ne $CatchActionFunction) {
& $CatchActionFunction
}
}
function Invoke-CatchActionErrorLoop {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[int]$CurrentErrors,
[Parameter(Mandatory = $false, Position = 1)]
[ScriptBlock]$CatchActionFunction
)
process {
if ($null -ne $CatchActionFunction -and
$Error.Count -ne $CurrentErrors) {
$i = 0
while ($i -lt ($Error.Count - $currentErrors)) {
& $CatchActionFunction $Error[$i]
$i++
}
}
}
}
# Confirm that either Remote Shell or EMS is loaded from an Edge Server, Exchange
Server, or a Tools box.
# It does this by also initializing the session and running Get-EventLogLevel.
(Server Management RBAC right)
# All script that require Confirm-ExchangeShell should be at least using Server
Management RBAC right for the user running the script.
function Confirm-ExchangeShell {
[CmdletBinding()]
param(
[Parameter(Mandatory = $false)]
[bool]$LoadExchangeShell = $true,
[Parameter(Mandatory = $false)]
[ScriptBlock]$CatchActionFunction
)
begin {
Write-Verbose "Calling: $($MyInvocation.MyCommand)"
Write-Verbose "Passed: LoadExchangeShell: $LoadExchangeShell"
$currentErrors = $Error.Count
$edgeTransportKey = 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\
EdgeTransportRole'
$setupKey = 'HKLM:\SOFTWARE\Microsoft\ExchangeServer\v15\Setup'
$remoteShell = (-not(Test-Path $setupKey))
$toolsServer = (Test-Path $setupKey) -and
(-not(Test-Path $edgeTransportKey)) -and
($null -eq (Get-ItemProperty -Path $setupKey -Name "Services" -ErrorAction
SilentlyContinue))
Invoke-CatchActionErrorLoop $currentErrors $CatchActionFunction
function IsExchangeManagementSession {
[OutputType("System.Boolean")]
param(
[ScriptBlock]$CatchActionFunction
)
$getEventLogLevelCallSuccessful = $false
$isExchangeManagementShell = $false
try {
$currentErrors = $Error.Count
$attempts = 0
do {
$eventLogLevel = Get-EventLogLevel -ErrorAction Stop | Select-
Object -First 1
$attempts++
if ($attempts -ge 5) {
throw "Failed to run Get-EventLogLevel too many times."
}
} while ($null -eq $eventLogLevel)
$getEventLogLevelCallSuccessful = $true
foreach ($e in $eventLogLevel) {
Write-Verbose "Type is: $($e.GetType().Name) BaseType is: $
($e.GetType().BaseType)"
if (($e.GetType().Name -eq "EventCategoryObject") -or
(($e.GetType().Name -eq "PSObject") -and
($null -ne $e.SerializationData))) {
$isExchangeManagementShell = $true
}
}
Invoke-CatchActionErrorLoop $currentErrors $CatchActionFunction
} catch {
Write-Verbose "Failed to run Get-EventLogLevel"
Invoke-CatchActionError $CatchActionFunction
}
return [PSCustomObject]@{
CallWasSuccessful = $getEventLogLevelCallSuccessful
IsManagementShell = $isExchangeManagementShell
}
}
}
process {
$isEMS = IsExchangeManagementSession $CatchActionFunction
if ($isEMS.CallWasSuccessful) {
Write-Verbose "Exchange PowerShell Module already loaded."
} else {
if (-not ($LoadExchangeShell)) { return }
#Test 32 bit process, as we can't see the registry if that is the case.
if (-not ([System.Environment]::Is64BitProcess)) {
Write-Warning "Open a 64 bit PowerShell process to continue"
return
}
if (Test-Path "$setupKey") {
Write-Verbose "We are on Exchange 2013 or newer"
try {
$currentErrors = $Error.Count
if (Test-Path $edgeTransportKey) {
Write-Verbose "We are on Exchange Edge Transport Server"
[xml]$PSSnapIns = Get-Content -Path
"$env:ExchangeInstallPath\Bin\exShell.psc1" -ErrorAction Stop
foreach ($PSSnapIn in
$PSSnapIns.PSConsoleFile.PSSnapIns.PSSnapIn) {
Write-Verbose ("Trying to add PSSnapIn: {0}" -f
$PSSnapIn.Name)
Add-PSSnapin -Name $PSSnapIn.Name -ErrorAction Stop
}
Import-Module $env:ExchangeInstallPath\bin\Exchange.ps1 -
ErrorAction Stop
} else {
Import-Module $env:ExchangeInstallPath\bin\
RemoteExchange.ps1 -ErrorAction Stop
Connect-ExchangeServer -Auto -
ClientApplication:ManagementShell
}
Invoke-CatchActionErrorLoop $currentErrors $CatchActionFunction
Write-Verbose "Imported Module. Trying Get-EventLogLevel Again"
$isEMS = IsExchangeManagementSession $CatchActionFunction
if (($isEMS.CallWasSuccessful) -and
($isEMS.IsManagementShell)) {
Write-Verbose "Successfully loaded Exchange Management
Shell"
} else {
Write-Warning "Something went wrong while loading the
Exchange Management Shell"
}
} catch {
Write-Warning "Failed to Load Exchange PowerShell Module..."
Invoke-CatchActionError $CatchActionFunction
}
} else {
Write-Verbose "Not on an Exchange or Tools server"
}
}
}
end {
$returnObject = [PSCustomObject]@{
ShellLoaded = $isEMS.CallWasSuccessful
Major = ((Get-ItemProperty -Path $setupKey -Name
"MsiProductMajor" -ErrorAction SilentlyContinue).MsiProductMajor)
Minor = ((Get-ItemProperty -Path $setupKey -Name
"MsiProductMinor" -ErrorAction SilentlyContinue).MsiProductMinor)
Build = ((Get-ItemProperty -Path $setupKey -Name "MsiBuildMajor"
-ErrorAction SilentlyContinue).MsiBuildMajor)
Revision = ((Get-ItemProperty -Path $setupKey -Name "MsiBuildMinor"
-ErrorAction SilentlyContinue).MsiBuildMinor)
EdgeServer = $isEMS.CallWasSuccessful -and (Test-Path $setupKey) -and
(Test-Path $edgeTransportKey)
ToolsOnly = $isEMS.CallWasSuccessful -and $toolsServer
RemoteShell = $isEMS.CallWasSuccessful -and $remoteShell
EMS = $isEMS.IsManagementShell
}
return $returnObject
}
}
function Get-ExchAVExclusionsPaths {
[CmdletBinding()]
[OutputType([Collections.Generic.List[string]])]
param (
[Parameter(Mandatory = $true)]
[ValidateScript({
if (Test-Path $_ -PathType Container ) { $true }
else { throw "Path $_ is not valid" }
})]
[string]
$ExchangePath,
[Parameter(Mandatory = $true)]
[ValidateSet(0, 1, 2)]
[byte]
$MsiProductMinor
)
# Create the Array List
$BaseFolders = New-Object Collections.Generic.List[string]
# List of base Folders
if ((Get-ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
if (Get-DatabaseAvailabilityGroup ) {
if (Get-DatabaseAvailabilityGroup | Where-Object { $_.Servers.Name -
contains ($env:COMPUTERNAME) } ) {
$BaseFolders.Add((Join-Path $($env:SystemRoot) '\
Cluster').ToLower())
$dag = $null
$dag = Get-DatabaseAvailabilityGroup | Where-Object
{ $_.Servers.Name -contains ($env:COMPUTERNAME) }
if ( $null -ne $dag ) {
Write-Warning "Remember to add the witness directory $
($dag.WitnessDirectory.PathName) on the server $($dag.WitnessServer.Fqdn)"
}
}
}
$BaseFolders.Add((Join-Path $ExchangePath '\ClientAccess\OAB').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\FIP-FS').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\GroupMetrics').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\Logging').ToLower())
if ($MsiProductMinor -eq 0 ) {
$BaseFolders.Add((Join-Path $ExchangePath '\Mailbox\
MdbTemp').ToLower())
}
$mbxS = Get-MailboxServer -Identity $($env:COMPUTERNAME) | Select-Object
CalendarRepairLogPath, LogPathForManagedFolders, `
DataPath, MigrationLogFilePath, TransportSyncLogFilePath,
TransportSyncMailboxHealthLogFilePath
$mbxS.PSObject.Properties.Value.PathName | ForEach-Object {
if ( $_ ) {
$BaseFolders.Add($_.ToLower())
}
}
# Add all database folder paths
foreach ($Entry in (Get-MailboxDatabase -Server $Env:COMPUTERNAME)) {
$BaseFolders.Add((Split-Path $Entry.EdbFilePath -Parent).ToLower())
$mbDbLogs = $Entry | Select-Object TemporaryDataFolderPath,
LogFolderPath
$mbDbLogs.PSObject.Properties.Value.PathName | ForEach-Object {
if ( $_ ) {
$BaseFolders.Add($_.ToLower())
}
}
}
$mtsLogs = Get-MailboxTransportService $($env:COMPUTERNAME) | Select-Object
ConnectivityLogPath, `
ReceiveProtocolLogPath, SendProtocolLogPath,
MailboxSubmissionAgentLogPath, MailboxDeliveryAgentLogPath, `
DnsLogPath, RoutingTableLogPath, SyncDeliveryLogPath,
MailboxDeliveryHttpDeliveryLogPath, `
MailboxDeliveryThrottlingLogPath, AgentGrayExceptionLogPath,
PipelineTracingPath
$mtsLogs.PSObject.Properties.Value.PathName | ForEach-Object {
if ( $_ ) {
$BaseFolders.Add($_.ToLower())
}
}
$BaseFolders.Add("$($env:SystemRoot)\Temp\
OICE_????????-????-????-????-????????????")
$BaseFolders.Add("$($env:SystemRoot)\Temp\
OICE_????????-????-????-????-????????????.?")
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsUnifiedMessagingServer) {
$BaseFolders.Add((Join-Path $ExchangePath '\UnifiedMessaging\Grammars'))
$BaseFolders.Add((Join-Path $ExchangePath '\UnifiedMessaging\Prompts'))
$BaseFolders.Add((Join-Path $ExchangePath '\UnifiedMessaging\Temp'))
$BaseFolders.Add((Join-Path $ExchangePath '\UnifiedMessaging\Voicemail'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsClientAccessServer) {
$feTsLogs = Get-FrontEndTransportService $($env:COMPUTERNAME) | Select-
Object ConnectivityLogPath, `
ReceiveProtocolLogPath, SendProtocolLogPath, AgentLogPath, DnsLogPath,
ResourceLogPath, `
AttributionLogPath, `
RoutingTableLogPath, ProxyDestinationsLogPath,
TopInboundIpSourcesLogPath
$feTsLogs.PSObject.Properties.Value.PathName | ForEach-Object {
if ( $_) {
$BaseFolders.Add($_.ToLower())
}
}
$BaseFolders.Add((Join-Path $env:SystemDrive '\inetPub\temp\IIS Temporary
Compressed Files').ToLower())
$BaseFolders.Add(($((Get-PopSettings).LogFileLocation)).ToLower())
$BaseFolders.Add(($((Get-ImapSettings).LogFileLocation)).ToLower())
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer) {
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\Data\
Adam').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\Data\
IpFilter').ToLower())
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer -or (Get-ExchangeServer
$env:COMPUTERNAME).IsHubTransportServer) {
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\Data\
Queue').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\Data\
SenderReputation').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\Data\
Temp').ToLower())
$BaseFolders.Add((Join-Path $ExchangePath '\TransportRoles\
Logs').ToLower())
$tsLogs = Get-TransportService $($env:COMPUTERNAME) | Select-Object
ConnectivityLogPath, MessageTrackingLogPath, `
IrmLogPath, ActiveUserStatisticsLogPath, ServerStatisticsLogPath,
ReceiveProtocolLogPath, RoutingTableLogPath, `
SendProtocolLogPath, QueueLogPath, LatencyLogPath, GeneralLogPath,
WlmLogPath, AgentLogPath, FlowControlLogPath, `
ProcessingSchedulerLogPath, ResourceLogPath, DnsLogPath,
JournalLogPath, TransportMaintenanceLogPath, `
RequestBrokerLogPath, StorageRESTLogPath, AgentGrayExceptionLogPath,
TransportHttpLogPath, PipelineTracingPath, `
PickupDirectoryPath, ReplayDirectoryPath, `
RootDropDirectoryPath
$tsLogs.PSObject.Properties.Value.PathName | ForEach-Object {
if ( $_ ) {
$BaseFolders.Add($_.ToLower())
}
}
$BaseFolders.Add((Join-Path $ExchangePath '\Working\
OleConverter').ToLower())
# Get transport database path
[xml]$TransportConfig = Get-Content (Join-Path $ExchangePath "Bin\
EdgeTransport.exe.config")
$BaseFolders.Add(($TransportConfig.configuration.AppSettings.Add | Where-
Object { $_.key -eq "QueueDatabasePath" }).value.ToLower())
$BaseFolders.Add(($TransportConfig.configuration.AppSettings.Add | Where-
Object { $_.key -eq "QueueDatabaseLoggingPath" }).value.ToLower())
if ($MsiProductMinor -eq 0 ) {
#E13MBX By default, content conversions are performed in the Exchange
server's %TMP% folder.
$BaseFolders.Add((Join-Path $env:SystemRoot '\Temp').ToLower())
}
}
if ($MsiProductMinor -eq 0 ) {
#E13 Exchange Server setup temporary files.
$BaseFolders.Add((Join-Path $env:SystemRoot '\Temp\
ExchangeSetup').ToLower())
# it is only in client Access E13 doc--- inetPub\logs\LogFiles\w3svc
Get-Website | Where-Object { $_.name -eq 'Default Web Site' -or $_.name -eq
'Exchange Back End' } | ForEach-Object {
if ($_.LogFile.directory.StartsWith('%')) {
$BaseFolders.Add(("$(Get-Content -Path Env:"$
($_.logFile.directory.Split('%')[1])")$($_.logFile.directory.Split('%')[2])\W3SVC$
($_.id)").ToLower())
} else {
$BaseFolders.Add(("$($_.LogFile.directory)\W3SVC$
($_.id)").ToLower())
}
}
}
# Remove any Duplicates
$BaseFolders = $BaseFolders | Select-Object -Unique
$BaseFolders
}
function Get-ExchAVExclusionsExtensions {
[CmdletBinding()]
[OutputType([Collections.Generic.List[string]])]
param (
[Parameter(Mandatory = $true)]
[ValidateSet(0, 1, 2)]
[byte]
$MsiProductMinor
)
# Create the Array List
$ExtensionsList = New-Object Collections.Generic.List[string]
if ($MsiProductMinor -eq 0 ) {
#Application-related extensions:
$ExtensionsList.Add("config")
$ExtensionsList.Add("dia")
$ExtensionsList.Add("wsb")
#Database-related extensions:
$ExtensionsList.Add("chk")
$ExtensionsList.Add("edb")
$ExtensionsList.Add("jrs")
$ExtensionsList.Add("jsl")
$ExtensionsList.Add("log")
$ExtensionsList.Add("que")
#Offline address book-related extensions:
$ExtensionsList.Add("lzx")
#Content Index-related extensions:
$ExtensionsList.Add("ci")
$ExtensionsList.Add("dir")
$ExtensionsList.Add("wid")
$ExtensionsList.Add("000")
$ExtensionsList.Add("001")
$ExtensionsList.Add("002")
#Unified Messaging-related extensions:
$ExtensionsList.Add("cfg")
$ExtensionsList.Add("grXml")
#Group Metrics-related extensions:
$ExtensionsList.Add("dsc")
$ExtensionsList.Add("txt")
}
if ($MsiProductMinor -eq 1 -or $MsiProductMinor -eq 2 ) {
if ((Get-ExchangeServer $env:COMPUTERNAME).IsMailboxServer -or (Get-
ExchangeServer $env:COMPUTERNAME).IsEdgeServer) {
#Application-related extensions
$ExtensionsList.Add("config")
#Database-related extensions
$ExtensionsList.Add("chk")
$ExtensionsList.Add("edb")
$ExtensionsList.Add("jfm")
$ExtensionsList.Add("jrs")
$ExtensionsList.Add("log")
$ExtensionsList.Add("que")
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
#Group Metrics-related extensions
$ExtensionsList.Add("dsc")
$ExtensionsList.Add("txt")
#Offline address book-related extensions
$ExtensionsList.Add("lzx")
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsUnifiedMessagingServer) {
#Unified Messaging-related extensions
$ExtensionsList.Add("cfg")
$ExtensionsList.Add("grXml")
}
}
$ExtensionsList.ToLower()
}
function Get-ExchAVExclusionsProcess {
[CmdletBinding()]
[OutputType([Collections.Generic.List[string]])]
param (
[ValidateScript({
if (Test-Path $_ -PathType Container ) { $true }
else { throw "Path $_ is not valid" }
})]
[string]
$ExchangePath,
[Parameter(Mandatory = $true)]
[ValidateSet(0, 1, 2)]
[byte]
$MsiProductMinor
)
# Create the Array List
$ProcessList = New-Object Collections.Generic.List[string]
if ( $MsiProductMinor -eq 0) {
if ((Get-ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\fms.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.EdgeSyncSvc.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'ClientAccess\PopImap\
Microsoft.Exchange.Imap4service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'ClientAccess\PopImap\
Microsoft.Exchange.Pop3service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.RPCClientAccess.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Search.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Store.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Store.Worker.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeDagMgmt.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeDelivery.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeMailboxAssistants.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeMailboxReplication.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeMigrationWorkflow.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeRepl.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeSubmission.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeThrottling.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\Runtime\
1.0\Noderunner.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\OleConverter.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\
ParserServer\ParserServer.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
ScanEngineTest.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
ScanningProcess.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'ClientAccess\Owa\Bin\
DocumentViewing\TranscodingService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\UmService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\UmWorkerProcess.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
UpdateService.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer) {
$ProcessList.Add((Join-Path $env:SystemRoot '\System32\Dsamain.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.EdgeCredentialSvc.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsClientAccessServer) {
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\PopImap\
Microsoft.Exchange.Imap4.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\PopImap\
Microsoft.Exchange.Pop3.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\CallRouter\
Microsoft.Exchange.UM.CallRouter.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeFrontendTransport.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsClientAccessServer -or (Get-
ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\
HostController\hostcontrollerservice.exe'))
$ProcessList.Add((Join-Path $env:SystemRoot '\System32\inetSrv\
inetInfo.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Directory.TopologyService.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsClientAccessServer -or (Get-
ExchangeServer $env:COMPUTERNAME).IsMailboxServer -or (Get-ExchangeServer
$env:COMPUTERNAME).IsEdgeServer) {
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Diagnostics.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.ProtectedServiceHost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Servicehost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeHMHost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeHMWorker.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer -or (Get-
ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
$ProcessList.Add((Join-Path $ExchangePath 'Bin\EdgeTransport.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.AntispamUpdateSvc.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'TransportRoles\agents\
Hygiene\Microsoft.Exchange.ContentFilter.Wrapper.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeTransport.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeTransportLogSearch.exe'))
}
} else {
if ((Get-ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
ComplianceAuditService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\fms.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\
HostController\hostcontrollerservice.exe'))
$ProcessList.Add((Join-Path $env:SystemRoot '\System32\inetSrv\
inetInfo.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Directory.TopologyService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.EdgeSyncSvc.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\PopImap\
Microsoft.Exchange.Imap4.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'ClientAccess\PopImap\
Microsoft.Exchange.Imap4service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Notifications.Broker.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\PopImap\
Microsoft.Exchange.Pop3.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'ClientAccess\PopImap\
Microsoft.Exchange.Pop3service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.RPCClientAccess.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Search.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Store.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Store.Worker.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeCompliance.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeDagMgmt.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeDelivery.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeFrontendTransport.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeMailboxAssistants.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeMailboxReplication.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeRepl.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeSubmission.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeThrottling.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\Runtime\
1.0\Noderunner.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\OleConverter.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\Search\Ceres\
ParserServer\ParserServer.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
ScanEngineTest.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
ScanningProcess.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'FIP-FS\Bin\
UpdateService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\wsbExchange.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer) {
$ProcessList.Add((Join-Path $env:SystemRoot '\System32\Dsamain.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.EdgeCredentialSvc.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsEdgeServer -or (Get-
ExchangeServer $env:COMPUTERNAME).IsMailboxServer) {
$ProcessList.Add((Join-Path $ExchangePath 'Bin\EdgeTransport.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.AntispamUpdateSvc.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'TransportRoles\agents\
Hygiene\Microsoft.Exchange.ContentFilter.Wrapper.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Diagnostics.Service.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.ProtectedServiceHost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
Microsoft.Exchange.Servicehost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\MSExchangeHMHost.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeHMWorker.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeTransport.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\
MSExchangeTransportLogSearch.exe'))
}
if ((Get-ExchangeServer $env:COMPUTERNAME).IsUnifiedMessagingServer) {
$ProcessList.Add((Join-Path $ExchangePath 'FrontEnd\CallRouter\
Microsoft.Exchange.UM.CallRouter.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\UmService.exe'))
$ProcessList.Add((Join-Path $ExchangePath 'Bin\UmWorkerProcess.exe'))
}
}
$ProcessList
}
function Confirm-ProxyServer {
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[string]
$TargetUri
)
Write-Verbose "Calling $($MyInvocation.MyCommand)"
try {
$proxyObject =
([System.Net.WebRequest]::GetSystemWebProxy()).GetProxy($TargetUri)
if ($TargetUri -ne $proxyObject.OriginalString) {
Write-Verbose "Proxy server configuration detected"
Write-Verbose $proxyObject.OriginalString
return $true
} else {
Write-Verbose "No proxy server configuration detected"
return $false
}
} catch {
Write-Verbose "Unable to check for proxy server configuration"
return $false
}
}
function WriteErrorInformationBase {
[CmdletBinding()]
param(
[object]$CurrentError = $Error[0],
[ValidateSet("Write-Host", "Write-Verbose")]
[string]$Cmdlet
)
if ($null -ne $CurrentError.OriginInfo) {
& $Cmdlet "Error Origin Info: $($CurrentError.OriginInfo.ToString())"
}
& $Cmdlet "$($CurrentError.CategoryInfo.Activity) : $
($CurrentError.ToString())"
if ($null -ne $CurrentError.Exception -and
$null -ne $CurrentError.Exception.StackTrace) {
& $Cmdlet "Inner Exception: $($CurrentError.Exception.StackTrace)"
} elseif ($null -ne $CurrentError.Exception) {
& $Cmdlet "Inner Exception: $($CurrentError.Exception)"
}
if ($null -ne $CurrentError.InvocationInfo.PositionMessage) {
& $Cmdlet "Position Message: $
($CurrentError.InvocationInfo.PositionMessage)"
}
if ($null -ne
$CurrentError.Exception.SerializedRemoteInvocationInfo.PositionMessage) {
& $Cmdlet "Remote Position Message: $
($CurrentError.Exception.SerializedRemoteInvocationInfo.PositionMessage)"
}
if ($null -ne $CurrentError.ScriptStackTrace) {
& $Cmdlet "Script Stack: $($CurrentError.ScriptStackTrace)"
}
}
function Write-VerboseErrorInformation {
[CmdletBinding()]
param(
[object]$CurrentError = $Error[0]
)
WriteErrorInformationBase $CurrentError "Write-Verbose"
}
function Write-HostErrorInformation {
[CmdletBinding()]
param(
[object]$CurrentError = $Error[0]
)
WriteErrorInformationBase $CurrentError "Write-Host"
}
function Invoke-WebRequestWithProxyDetection {
[CmdletBinding(DefaultParameterSetName = "Default")]
param (
[Parameter(Mandatory = $true, ParameterSetName = "Default")]
[string]
$Uri,
[Parameter(Mandatory = $false, ParameterSetName = "Default")]
[switch]
$UseBasicParsing,
[Parameter(Mandatory = $true, ParameterSetName = "ParametersObject")]
[hashtable]
$ParametersObject,
[Parameter(Mandatory = $false, ParameterSetName = "Default")]
[string]
$OutFile
)
Write-Verbose "Calling $($MyInvocation.MyCommand)"
if ([System.String]::IsNullOrEmpty($Uri)) {
$Uri = $ParametersObject.Uri
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
if (Confirm-ProxyServer -TargetUri $Uri) {
$webClient = New-Object System.Net.WebClient
$webClient.Headers.Add("User-Agent", "PowerShell")
$webClient.Proxy.Credentials =
[System.Net.CredentialCache]::DefaultNetworkCredentials
}
if ($null -eq $ParametersObject) {
$params = @{
Uri = $Uri
OutFile = $OutFile
}
if ($UseBasicParsing) {
$params.UseBasicParsing = $true
}
} else {
$params = $ParametersObject
}
try {
Invoke-WebRequest @params
} catch {
Write-VerboseErrorInformation
}
}
<#
Determines if the script has an update available.
#>
function Get-ScriptUpdateAvailable {
[CmdletBinding()]
[OutputType([PSCustomObject])]
param (
[Parameter(Mandatory = $false)]
[string]
$VersionsUrl =
"https://github.com/microsoft/CSS-Exchange/releases/latest/download/
ScriptVersions.csv"
)
$BuildVersion = "25.04.16.1347"
$scriptName = $script:MyInvocation.MyCommand.Name
$scriptPath = [IO.Path]::GetDirectoryName($script:MyInvocation.MyCommand.Path)
$scriptFullName = (Join-Path $scriptPath $scriptName)
$result = [PSCustomObject]@{
ScriptName = $scriptName
CurrentVersion = $BuildVersion
LatestVersion = ""
UpdateFound = $false
Error = $null
}
if ((Get-AuthenticodeSignature -FilePath $scriptFullName).Status -eq
"NotSigned") {
Write-Warning "This script appears to be an unsigned test build. Skipping
version check."
} else {
try {
$versionData = [Text.Encoding]::UTF8.GetString((Invoke-
WebRequestWithProxyDetection -Uri $VersionsUrl -UseBasicParsing).Content) |
ConvertFrom-Csv
$latestVersion = ($versionData | Where-Object { $_.File -eq $scriptName
}).Version
$result.LatestVersion = $latestVersion
if ($null -ne $latestVersion) {
$result.UpdateFound = ($latestVersion -ne $BuildVersion)
} else {
Write-Warning ("Unable to check for a script update as no script
with the same name was found." +
"`r`nThis can happen if the script has been renamed. Please
check manually if there is a newer version of the script.")
}
Write-Verbose "Current version: $($result.CurrentVersion) Latest
version: $($result.LatestVersion) Update found: $($result.UpdateFound)"
} catch {
Write-Verbose "Unable to check for updates: $($_.Exception)"
$result.Error = $_
}
}
return $result
}
function Confirm-Signature {
[CmdletBinding()]
[OutputType([bool])]
param (
[Parameter(Mandatory = $true)]
[string]
$File
)
$IsValid = $false
$MicrosoftSigningRoot2010 = 'CN=Microsoft Root Certificate Authority 2010,
O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
$MicrosoftSigningRoot2011 = 'CN=Microsoft Root Certificate Authority 2011,
O=Microsoft Corporation, L=Redmond, S=Washington, C=US'
try {
$sig = Get-AuthenticodeSignature -FilePath $File
if ($sig.Status -ne 'Valid') {
Write-Warning "Signature is not trusted by machine as Valid, status: $
($sig.Status)."
throw
}
$chain = New-Object -TypeName
System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.VerificationFlags = "IgnoreNotTimeValid"
if (-not $chain.Build($sig.SignerCertificate)) {
Write-Warning "Signer certificate doesn't chain correctly."
throw
}
if ($chain.ChainElements.Count -le 1) {
Write-Warning "Certificate Chain shorter than expected."
throw
}
$rootCert = $chain.ChainElements[$chain.ChainElements.Count - 1]
if ($rootCert.Certificate.Subject -ne $rootCert.Certificate.Issuer) {
Write-Warning "Top-level certificate in chain is not a root
certificate."
throw
}
if ($rootCert.Certificate.Subject -ne $MicrosoftSigningRoot2010 -and
$rootCert.Certificate.Subject -ne $MicrosoftSigningRoot2011) {
Write-Warning "Unexpected root cert. Expected $MicrosoftSigningRoot2010
or $MicrosoftSigningRoot2011, but found $($rootCert.Certificate.Subject)."
throw
}
Write-Host "File signed by $($sig.SignerCertificate.Subject)"
$IsValid = $true
} catch {
$IsValid = $false
}
$IsValid
}
<#
.SYNOPSIS
Overwrites the current running script file with the latest version from the
repository.
.NOTES
This function always overwrites the current file with the latest file, which
might be
the same. Get-ScriptUpdateAvailable should be called first to determine if an
update is
needed.
In many situations, updates are expected to fail, because the server running
the script
does not have internet access. This function writes out failures as warnings,
because we
expect that Get-ScriptUpdateAvailable was already called and it successfully
reached out
to the internet.
#>
function Invoke-ScriptUpdate {
[CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')]
[OutputType([boolean])]
param ()
$scriptName = $script:MyInvocation.MyCommand.Name
$scriptPath = [IO.Path]::GetDirectoryName($script:MyInvocation.MyCommand.Path)
$scriptFullName = (Join-Path $scriptPath $scriptName)
$oldName = [IO.Path]::GetFileNameWithoutExtension($scriptName) + ".old"
$oldFullName = (Join-Path $scriptPath $oldName)
$tempFullName = (Join-Path ((Get-Item $env:TEMP).FullName) $scriptName)
if ($PSCmdlet.ShouldProcess("$scriptName", "Update script to latest version"))
{
try {
Invoke-WebRequestWithProxyDetection -Uri
"https://github.com/microsoft/CSS-Exchange/releases/latest/download/$scriptName" -
OutFile $tempFullName
} catch {
Write-Warning "AutoUpdate: Failed to download update: $
($_.Exception.Message)"
return $false
}
try {
if (Confirm-Signature -File $tempFullName) {
Write-Host "AutoUpdate: Signature validated."
if (Test-Path $oldFullName) {
Remove-Item $oldFullName -Force -Confirm:$false -ErrorAction
Stop
}
Move-Item $scriptFullName $oldFullName
Move-Item $tempFullName $scriptFullName
Remove-Item $oldFullName -Force -Confirm:$false -ErrorAction Stop
Write-Host "AutoUpdate: Succeeded."
return $true
} else {
Write-Warning "AutoUpdate: Signature could not be verified:
$tempFullName."
Write-Warning "AutoUpdate: Update was not applied."
}
} catch {
Write-Warning "AutoUpdate: Failed to apply update: $
($_.Exception.Message)"
}
}
return $false
}
<#
Determines if the script has an update available. Use the optional
-AutoUpdate switch to make it update itself. Pass -Confirm:$false
to update without prompting the user. Pass -Verbose for additional
diagnostic output.
Returns $true if an update was downloaded, $false otherwise. The
result will always be $false if the -AutoUpdate switch is not used.
#>
function Test-ScriptVersion {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '',
Justification = 'Need to pass through ShouldProcess settings to Invoke-
ScriptUpdate')]
[CmdletBinding(SupportsShouldProcess)]
[OutputType([bool])]
param (
[Parameter(Mandatory = $false)]
[switch]
$AutoUpdate,
[Parameter(Mandatory = $false)]
[string]
$VersionsUrl =
"https://github.com/microsoft/CSS-Exchange/releases/latest/download/
ScriptVersions.csv"
)
$updateInfo = Get-ScriptUpdateAvailable $VersionsUrl
if ($updateInfo.UpdateFound) {
if ($AutoUpdate) {
return Invoke-ScriptUpdate
} else {
Write-Warning "$($updateInfo.ScriptName) $BuildVersion is outdated.
Please download the latest, version $($updateInfo.LatestVersion)."
}
}
return $false
}
function Get-NewLoggerInstance {
[CmdletBinding()]
param(
[string]$LogDirectory = (Get-Location).Path,
[ValidateNotNullOrEmpty()]
[string]$LogName = "Script_Logging",
[bool]$AppendDateTime = $true,
[bool]$AppendDateTimeToFileName = $true,
[int]$MaxFileSizeMB = 10,
[int]$CheckSizeIntervalMinutes = 10,
[int]$NumberOfLogsToKeep = 10
)
$fileName = if ($AppendDateTimeToFileName) { "{0}_{1}.txt" -f $LogName, ((Get-
Date).ToString('yyyyMMddHHmmss')) } else { "$LogName.txt" }
$fullFilePath = [System.IO.Path]::Combine($LogDirectory, $fileName)
if (-not (Test-Path $LogDirectory)) {
try {
New-Item -ItemType Directory -Path $LogDirectory -ErrorAction Stop |
Out-Null
} catch {
throw "Failed to create Log Directory: $LogDirectory. Inner Exception:
$_"
}
}
return [PSCustomObject]@{
FullPath = $fullFilePath
AppendDateTime = $AppendDateTime
MaxFileSizeMB = $MaxFileSizeMB
CheckSizeIntervalMinutes = $CheckSizeIntervalMinutes
NumberOfLogsToKeep = $NumberOfLogsToKeep
BaseInstanceFileName = $fileName.Replace(".txt", "")
Instance = 1
NextFileCheckTime = ((Get-
Date).AddMinutes($CheckSizeIntervalMinutes))
PreventLogCleanup = $false
LoggerDisabled = $false
} | Write-LoggerInstance -Object "Starting Logger Instance $(Get-Date)"
}
function Write-LoggerInstance {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[object]$LoggerInstance,
[Parameter(Mandatory = $true, Position = 1)]
[object]$Object
)
process {
if ($LoggerInstance.LoggerDisabled) { return }
if ($LoggerInstance.AppendDateTime -and
$Object.GetType().Name -eq "string") {
$Object = "[$([System.DateTime]::Now)] : $Object"
}
# Doing WhatIf:$false to support -WhatIf in main scripts but still log the
information
$Object | Out-File $LoggerInstance.FullPath -Append -WhatIf:$false
#Upkeep of the logger information
if ($LoggerInstance.NextFileCheckTime -gt [System.DateTime]::Now) {
return
}
#Set next update time to avoid issues so we can log things
$LoggerInstance.NextFileCheckTime =
([System.DateTime]::Now).AddMinutes($LoggerInstance.CheckSizeIntervalMinutes)
$item = Get-ChildItem $LoggerInstance.FullPath
if (($item.Length / 1MB) -gt $LoggerInstance.MaxFileSizeMB) {
$LoggerInstance | Write-LoggerInstance -Object "Max file size reached
rolling over" | Out-Null
$directory =
[System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath)
$fileName = "$($LoggerInstance.BaseInstanceFileName)-$
($LoggerInstance.Instance).txt"
$LoggerInstance.Instance++
$LoggerInstance.FullPath = [System.IO.Path]::Combine($directory,
$fileName)
$items = Get-ChildItem -Path
([System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath)) -Filter "*$
($LoggerInstance.BaseInstanceFileName)*"
if ($items.Count -gt $LoggerInstance.NumberOfLogsToKeep) {
$item = $items | Sort-Object LastWriteTime | Select-Object -First 1
$LoggerInstance | Write-LoggerInstance "Removing Log File $
($item.FullName)" | Out-Null
$item | Remove-Item -Force
}
}
}
end {
return $LoggerInstance
}
}
function Invoke-LoggerInstanceCleanup {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[object]$LoggerInstance
)
process {
if ($LoggerInstance.LoggerDisabled -or
$LoggerInstance.PreventLogCleanup) {
return
}
Get-ChildItem -Path
([System.IO.Path]::GetDirectoryName($LoggerInstance.FullPath)) -Filter "*$
($LoggerInstance.BaseInstanceFileName)*" |
Remove-Item -Force
}
}
function Write-Host {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlet
s', '', Justification = 'Proper handling of write host with colors')]
[CmdletBinding()]
param(
[Parameter(Position = 1, ValueFromPipeline)]
[object]$Object,
[switch]$NoNewLine,
[string]$ForegroundColor
)
process {
$consoleHost = $host.Name -eq "ConsoleHost"
if ($null -ne $Script:WriteHostManipulateObjectAction) {
$Object = & $Script:WriteHostManipulateObjectAction $Object
}
$params = @{
Object = $Object
NoNewLine = $NoNewLine
}
if ([string]::IsNullOrEmpty($ForegroundColor)) {
if ($null -ne $host.UI.RawUI.ForegroundColor -and
$consoleHost) {
$params.Add("ForegroundColor", $host.UI.RawUI.ForegroundColor)
}
} elseif ($ForegroundColor -eq "Yellow" -and
$consoleHost -and
$null -ne $host.PrivateData.WarningForegroundColor) {
$params.Add("ForegroundColor",
$host.PrivateData.WarningForegroundColor)
} elseif ($ForegroundColor -eq "Red" -and
$consoleHost -and
$null -ne $host.PrivateData.ErrorForegroundColor) {
$params.Add("ForegroundColor", $host.PrivateData.ErrorForegroundColor)
} else {
$params.Add("ForegroundColor", $ForegroundColor)
}
Microsoft.PowerShell.Utility\Write-Host @params
if ($null -ne $Script:WriteHostDebugAction -and
$null -ne $Object) {
&$Script:WriteHostDebugAction $Object
}
}
}
function SetProperForegroundColor {
$Script:OriginalConsoleForegroundColor = $host.UI.RawUI.ForegroundColor
if ($Host.UI.RawUI.ForegroundColor -eq
$Host.PrivateData.WarningForegroundColor) {
Write-Verbose "Foreground Color matches warning's color"
if ($Host.UI.RawUI.ForegroundColor -ne "Gray") {
$Host.UI.RawUI.ForegroundColor = "Gray"
}
}
if ($Host.UI.RawUI.ForegroundColor -eq $Host.PrivateData.ErrorForegroundColor)
{
Write-Verbose "Foreground Color matches error's color"
if ($Host.UI.RawUI.ForegroundColor -ne "Gray") {
$Host.UI.RawUI.ForegroundColor = "Gray"
}
}
}
function RevertProperForegroundColor {
$Host.UI.RawUI.ForegroundColor = $Script:OriginalConsoleForegroundColor
}
function SetWriteHostAction ($DebugAction) {
$Script:WriteHostDebugAction = $DebugAction
}
function SetWriteHostManipulateObjectAction ($ManipulateObject) {
$Script:WriteHostManipulateObjectAction = $ManipulateObject
}
function Write-Warning {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlet
s', '', Justification = 'In order to log Write-Warning from Shared functions')]
[CmdletBinding()]
param(
[Parameter(Position = 1, ValueFromPipeline)]
[string]$Message
)
process {
if ($null -ne $Script:WriteWarningManipulateMessageAction) {
$Message = & $Script:WriteWarningManipulateMessageAction $Message
}
Microsoft.PowerShell.Utility\Write-Warning $Message
# Add WARNING to beginning of the message by default.
$Message = "WARNING: $Message"
if ($null -ne $Script:WriteWarningDebugAction) {
& $Script:WriteWarningDebugAction $Message
}
# $PSSenderInfo is set when in a remote context
if ($PSSenderInfo -and
$null -ne $Script:WriteRemoteWarningDebugAction) {
& $Script:WriteRemoteWarningDebugAction $Message
}
}
}
function SetWriteWarningAction ($DebugAction) {
$Script:WriteWarningDebugAction = $DebugAction
}
function SetWriteRemoteWarningAction ($DebugAction) {
$Script:WriteRemoteWarningDebugAction = $DebugAction
}
function SetWriteWarningManipulateMessageAction ($DebugAction) {
$Script:WriteWarningManipulateMessageAction = $DebugAction
}
function Write-Verbose {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlet
s', '', Justification = 'In order to log Write-Verbose from Shared functions')]
[CmdletBinding()]
param(
[Parameter(Position = 1, ValueFromPipeline)]
[string]$Message
)
process {
if ($null -ne $Script:WriteVerboseManipulateMessageAction) {
$Message = & $Script:WriteVerboseManipulateMessageAction $Message
}
if ($PSSenderInfo -and
$null -ne $Script:WriteVerboseRemoteManipulateMessageAction) {
$Message = & $Script:WriteVerboseRemoteManipulateMessageAction $Message
}
Microsoft.PowerShell.Utility\Write-Verbose $Message
if ($null -ne $Script:WriteVerboseDebugAction) {
& $Script:WriteVerboseDebugAction $Message
}
# $PSSenderInfo is set when in a remote context
if ($PSSenderInfo -and
$null -ne $Script:WriteRemoteVerboseDebugAction) {
& $Script:WriteRemoteVerboseDebugAction $Message
}
}
}
function SetWriteVerboseAction ($DebugAction) {
$Script:WriteVerboseDebugAction = $DebugAction
}
function SetWriteRemoteVerboseAction ($DebugAction) {
$Script:WriteRemoteVerboseDebugAction = $DebugAction
}
function SetWriteVerboseManipulateMessageAction ($DebugAction) {
$Script:WriteVerboseManipulateMessageAction = $DebugAction
}
function SetWriteVerboseRemoteManipulateMessageAction ($DebugAction) {
$Script:WriteVerboseRemoteManipulateMessageAction = $DebugAction
}
function Write-Progress {
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlet
s', '', Justification = 'In order to log Write-Warning from Shared functions')]
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[string]$Activity = "",
[switch]$Completed,
[string]$CurrentOperation,
[Parameter(Position = 2)]
[int]$Id,
[int]$ParentId = -1,
[int]$PercentComplete,
[int]$SecondsRemaining = -1,
[int]$SourceId,
[Parameter(Position = 1)]
[string]$Status
)
process {
$params = @{
Activity = $Activity
Completed = $Completed
CurrentOperation = $CurrentOperation
Id = $Id
ParentId = $ParentId
PercentComplete = $PercentComplete
SecondsRemaining = $SecondsRemaining
SourceId = $SourceId
}
if (-not([string]::IsNullOrEmpty($Status))) {
$params.Add("Status", $Status)
}
Microsoft.PowerShell.Utility\Write-Progress @params
$message = "Write-Progress Activity: '$Activity' Completed: $Completed
CurrentOperation: '$CurrentOperation' Id: $Id" +
" ParentId: $ParentId PercentComplete: $PercentComplete SecondsRemaining:
$SecondsRemaining SourceId: $SourceId Status: '$Status'"
if ($null -ne $Script:WriteProgressDebugAction) {
& $Script:WriteProgressDebugAction $message
}
if ($PSSenderInfo -and
$null -ne $Script:WriteRemoteProgressDebugAction) {
& $Script:WriteRemoteProgressDebugAction $message
}
}
}
function SetWriteProgressAction ($DebugAction) {
$Script:WriteProgressDebugAction = $DebugAction
}
function SetWriteRemoteProgressAction ($DebugAction) {
$Script:WriteRemoteProgressDebugAction = $DebugAction
}
function Write-DebugLog ($message) {
if (![string]::IsNullOrEmpty($message)) {
$Script:DebugLogger = $Script:DebugLogger | Write-LoggerInstance $message
}
}
function Write-HostLog ($message) {
if (![string]::IsNullOrEmpty($message)) {
$Script:DebugLogger = $Script:DebugLogger | Write-LoggerInstance $message
$Script:HostLogger = $Script:HostLogger | Write-LoggerInstance $message
}
}
$LogFileName = "Test-ExchAvExclusions"
$StartDate = Get-Date
$StartDateFormatted = ($StartDate).ToString("yyyyMMddhhmmss")
$Script:DebugLogger = Get-NewLoggerInstance -LogName "$LogFileName-Debug-
$StartDateFormatted" -LogDirectory $PSScriptRoot -AppendDateTimeToFileName $false -
ErrorAction SilentlyContinue
$Script:HostLogger = Get-NewLoggerInstance -LogName "$LogFileName-Results-
$StartDateFormatted" -LogDirectory $PSScriptRoot -AppendDateTimeToFileName $false -
ErrorAction SilentlyContinue
SetWriteHostAction ${Function:Write-HostLog}
SetWriteProgressAction ${Function:Write-DebugLog}
SetWriteVerboseAction ${Function:Write-DebugLog}
SetWriteWarningAction ${Function:Write-HostLog}
$BuildVersion = "25.04.16.1347"
Write-Host ("Test-ExchAVExclusions.ps1 script version $($BuildVersion)") -
ForegroundColor Green
if ($ScriptUpdateOnly) {
switch (Test-ScriptVersion -AutoUpdate -VersionsUrl "https://aka.ms/Test-
ExchAVExclusions-VersionsURL" -Confirm:$false) {
($true) { Write-Host ("Script was successfully updated") -ForegroundColor
Green }
($false) { Write-Host ("No update of the script performed") -
ForegroundColor Yellow }
default { Write-Host ("Unable to perform ScriptUpdateOnly operation") -
ForegroundColor Red }
}
return
}
if ((-not($SkipVersionCheck)) -and
(Test-ScriptVersion -AutoUpdate -VersionsUrl "https://aka.ms/Test-
ExchAVExclusions-VersionsURL" -Confirm:$false)) {
Write-Host ("Script was updated. Please re-run the command") -ForegroundColor
Yellow
return
}
# Confirm that we are an administrator
if (-not (Confirm-Administrator)) {
Write-Host "[ERROR]: Please run as Administrator" -ForegroundColor Red
exit
}
$serverExchangeInstallDirectory = Get-ItemProperty HKLM:\SOFTWARE\Microsoft\
ExchangeServer\v15\Setup -ErrorAction SilentlyContinue
# Check Exchange registry key
if (-not $serverExchangeInstallDirectory ) {
Write-Host "[ERROR]: Failed to find the Exchange installation Path registry
key" -ForegroundColor Red
exit
}
# Check the installation path
if (-not ( Test-Path $($serverExchangeInstallDirectory.MsiInstallPath) -PathType
Container) ) {
Write-Host "[ERROR]: Failed to find the Exchange installation Path" -
ForegroundColor Red
exit
}
# Check Exchange is 2013, 2016 or 2019
if ( -not ( $($serverExchangeInstallDirectory.MsiProductMajor) -eq 15 -and `
($($serverExchangeInstallDirectory.MsiProductMinor) -eq 0 -or $
($serverExchangeInstallDirectory.MsiProductMinor) -eq 1 -or $
($serverExchangeInstallDirectory.MsiProductMinor) -eq 2 ) ) ) {
Write-Host "[ERROR]: This script is designed for Exchange 2013, 2016 or 2019" -
ForegroundColor Red
exit
}
$ExchangePath = $serverExchangeInstallDirectory.MsiInstallPath
# Check Exchange Shell and Exchange installation
$exchangeShell = Confirm-ExchangeShell
if (-not($exchangeShell.ShellLoaded)) {
Write-Host "Failed to load Exchange Shell Module..." -ForegroundColor Red
exit
}
Write-Host
"##################################################################################
#########"
Write-Host "Starting AV Exclusions analysis at $(($StartDate).ToString())"
Write-Host
"##################################################################################
#########"
Write-Host "You can find a simple log on: $LogFileName-Results-
$StartDateFormatted.txt"
Write-Host "And a detailed log on: $LogFileName-Debug-$StartDateFormatted.txt"
# Create the Array List
$BaseFolders = Get-ExchAVExclusionsPaths -ExchangePath $ExchangePath -
MsiProductMinor ([byte]$serverExchangeInstallDirectory.MsiProductMinor)
if ( $BaseFolders.count -eq 0 ) {
Write-Host "We do not detect folders to analyze" -ForegroundColor Red
exit
}
# Create list object to hold all Folders we are going to test
$FolderList = New-Object Collections.Generic.List[string]
$randomCharForWildCard = (Get-Random -Maximum 16).ToString('x')
$nonExistentFolder = New-Object Collections.Generic.List[string]
foreach ($path in $BaseFolders) {
try {
if ($path -match '\?') {
$path = $path -replace '\?', $randomCharForWildCard
$FolderList.Add($path.ToLower())
$nonExistentFolder.Add($path.ToLower())
New-Item -Path (Split-Path $path) -Name $path.split('\')[-1] -ItemType
Directory -Force | Out-Null
Write-Verbose "Created folder: $path"
}
# Resolve path only returns a bool so we have to manually throw to catch
if (!(Resolve-Path -Path $path -ErrorAction SilentlyContinue)) {
$nonExistentFolder.Add($path.ToLower())
New-Item -Path (Split-Path $path) -Name $path.split('\')[-1] -ItemType
Directory -Force | Out-Null
Write-Verbose "Created folder: $path"
}
# If -recurse then we need to find all SubFolders and Add them to the list
to be tested
if ($Recurse) {
# Add the root folder
$FolderList.Add($path.ToLower())
# Get the Folder and all subFolders and just return the fullName value
as a string
Get-ChildItem $path -Recurse -Directory -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty FullName | ForEach-Object
{ $FolderList.Add($_.ToLower()) }
}
# Just Add the root folder
$FolderList.Add($path.ToLower())
} catch { Write-Verbose "[ERROR] - Failed to resolve folder $path" -
ForegroundColor Red }
}
# Remove any Duplicates
$FolderList = $FolderList | Select-Object -Unique
Write-Host "Creating EICAR Files"
# Create the EICAR file in each path
$eicarFileName = "eicar"
$eicarFileExt = "com"
$eicarFullFileName = "$eicarFileName.$eicarFileExt"
#Base64 of eicar string
[string] $EncodedEicar =
'WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTE
UhJEgrSCo='
foreach ($Folder in $FolderList) {
[string] $FilePath = (Join-Path $Folder $eicarFullFileName)
Write-Verbose "Creating $eicarFullFileName file $FilePath"
if (!(Test-Path -Path $FilePath)) {
# Try writing the encoded string to a the file
try {
[byte[]] $eicarBytes =
[System.Convert]::FromBase64String($EncodedEicar)
[string] $eicar = [System.Text.Encoding]::UTF8.GetString($eicarBytes)
[IO.File]::WriteAllText($FilePath, $eicar)
}
catch {
Write-Warning "$Folder $eicarFullFileName file couldn't be created.
Either permissions or AV prevented file creation."
}
} else {
Write-Warning "$eicarFullFileName already exists!: $FilePath"
}
}
# Create a random folder in root path
$randomString = -join ((65..90) + (97..122) | Get-Random -Count 10 | ForEach-Object
{ [char]$_ })
$randomFolder = New-Item -Path (Join-Path (Join-Path $env:SystemDrive '\')
"TestExchAVExclusions-$randomString") -ItemType Directory
$extensionsList = New-Object Collections.Generic.List[string]
$extensionsList = Get-ExchAVExclusionsExtensions -MsiProductMinor ([byte]
$serverExchangeInstallDirectory.MsiProductMinor)
if ($randomFolder) {
foreach ($extension in $extensionsList) {
$filepath = Join-Path $randomFolder "$eicarFileName.$extension"
Write-Verbose "Creating $eicarFileName.$extension file $FilePath"
if (!(Test-Path -Path $FilePath)) {
# Try writing the encoded string to a the file
try {
[byte[]] $eicarBytes =
[System.Convert]::FromBase64String($EncodedEicar)
[string] $eicar =
[System.Text.Encoding]::UTF8.GetString($eicarBytes)
[IO.File]::WriteAllText($FilePath, $eicar)
} catch {
Write-Warning "$randomFolder $eicarFileName.$extension file
couldn't be created. Either permissions or AV prevented file creation."
}
} else {
Write-Warning "$randomFolder $eicarFileName.$extension already
exists!: "
}
}
} else {
Write-Warning "We cannot create a folder in root path to test extension
exclusions."
}
Write-Host "EICAR Files Created"
Write-Host "Accessing EICAR Files"
# Try to open each EICAR file to force detection in paths
$i = 0
foreach ($Folder in $FolderList) {
$FilePath = (Join-Path $Folder $eicarFullFileName)
if (Test-Path $FilePath -PathType Leaf) {
Write-Verbose "Opening $eicarFullFileName file $FilePath"
Start-Process -FilePath more -ArgumentList """$FilePath""" -ErrorAction
SilentlyContinue -WindowStyle Hidden | Out-Null
}
$i++
}
# Try to open extensions:
$i = 0
foreach ($extension in $extensionsList) {
$FilePath = Join-Path $randomFolder "$eicarFileName.$extension"
if (Test-Path $FilePath -PathType Leaf) {
Write-Verbose "Opening $eicarFileName.$extension file $FilePath"
Start-Process -FilePath more -ArgumentList """$FilePath""" -ErrorAction
SilentlyContinue -WindowStyle Hidden | Out-Null
}
$i++
}
Write-Host "Access EICAR Files Finished"
[int]$initialDiff = (New-TimeSpan -End
$StartDate.AddMinutes($WaitingTimeForAVAnalysisInMinutes) -Start
$StartDate).TotalSeconds
$currentDiff = $initialDiff
$firstExecution = $true
$SuspiciousProcessList = New-Object Collections.Generic.List[string]
$SuspiciousW3wpProcessList = New-Object Collections.Generic.List[string]
$SuspiciousAMSIinW3wpProcessList = New-Object Collections.Generic.List[string]
# Get AMSI Dlls registered
# Define the AMSI providers registry path
$registryPath = "HKLM:\SOFTWARE\Microsoft\AMSI\Providers"
$subKeys = $null
$AMSIDll = New-Object Collections.Generic.List[string]
# Get all subKeys in the specified registry path
$subKeys = Get-ChildItem -Path $registryPath -ErrorAction SilentlyContinue
if ($subKeys) {
foreach ($subKey in $subKeys) {
$matchingSubKeys = $null
# Filter the subKeys that match the regular expression and get only their
names
$matchingSubKeys = $subKey -match '[0-9A-Fa-f\-]{36}'
if ($matchingSubKeys) {
foreach ($m in $Matches.Values) {
$foundDll = (Get-Item "HKLM:\SOFTWARE\Classes\ClSid\{$m}\
InprocServer32" -ErrorAction SilentlyContinue).GetValue("").trim('"')
if ($null -eq $foundDll) {
Write-Host "No AMSI Dlls was found for $m, possible AMSI
misconfiguration" -ForegroundColor Red
} else {
Write-Verbose "AMSI $m was found"
$AMSIDll.Add($foundDll)
}
}
} else {
Write-Host 'No AMSI configuration was found, possible AMSI
misconfiguration"' -ForegroundColor Red
}
}
} else {
Write-Host '"No AMSI Providers was found"'
}
Write-Host "Analyzing Exchange Processes"
while ($currentDiff -gt 0) {
if ($firstExecution) {
# Test Exchange Processes for unexpected modules
$ExchangeProcessList = Get-ExchAVExclusionsProcess -ExchangePath
$ExchangePath -MsiProductMinor ([byte]
$serverExchangeInstallDirectory.MsiProductMinor)
# Include w3wp process in the analysis
$ExchangeProcessList += (Join-Path $env:SystemRoot '\System32\inetSrv\
W3wp.exe')
# Gather all processes on the computer and filter by the Exchange Process
List
$ServerProcess = Get-Process | Where-Object { $ExchangeProcessList -
contains $_.path } | Sort-Object -Property ProcessName
# Module allow list
$ModuleAllowList = New-Object Collections.Generic.List[string]
# cSpell:disable
# No company name
#Exchange 2013
# Bin\Search\Ceres\HostController\Data\Repository\Journal\
$ModuleAllowList.Add("Microsoft.Exchange.TransportFlow.50.dll")
$ModuleAllowList.Add("Microsoft.ClientResourceView.FlowService.dll")
$ModuleAllowList.Add("Microsoft.Exchange.TransportFlowMdm.50.dll")
$ModuleAllowList.Add("Microsoft.Exchange.Search.Writer.50.dll")
$ModuleAllowList.Add("FUSE.Paxos.Network.dll")
$ModuleAllowList.Add("FUSE.Weld.Base.Portable.dll")
$ModuleAllowList.Add("ParallelExtensionsExtras.dll")
$ModuleAllowList.Add("Google.ProtocolBuffers.dll")
#Exchange 2016
# Bin\Search\Ceres\HostController\Data\Repository\Journal\
$ModuleAllowList.Add("Microsoft.Exchange.TransportFlowMdm.105.dll")
$ModuleAllowList.Add("Microsoft.Exchange.TransportFlow.105.dll")
$ModuleAllowList.Add("Microsoft.Exchange.Search.Writer.109.dll")
$ModuleAllowList.Add("Microsoft.Exchange.WatermarkCtsFlow.100.dll")
$ModuleAllowList.Add("Bond.Precompiler.dll")
$ModuleAllowList.Add("Microsoft.Applications.Telemetry.dll")
$ModuleAllowList.Add("Microsoft.Applications.Telemetry.Server.dll")
$ModuleAllowList.Add("Microsoft.RightsManagementServices.Core.dll")
$ModuleAllowList.Add("Microsoft.Search.ObjectStore.Client.dll")
$ModuleAllowList.Add("ParallelExtensionsExtras.dll")
$ModuleAllowList.Add("System.IdentityModel.Tokens.Jwt.dll")
$ModuleAllowList.Add("Owin.dll")
$ModuleAllowList.Add("Google.ProtocolBuffers.dll")
$ModuleAllowList.Add("DiskLockerApi.dll")
$ModuleAllowList.Add("ExDbFailureItemApi.dll")
$ModuleAllowList.Add("ManagedBlingSigned.dll")
$ModuleAllowList.Add("Microsoft.DSSMNativeSSELib.dll")
#Exchange 2019
$ModuleAllowList.Add("Microsoft.Exchange.BigFunnelFlow.28.dll")
$ModuleAllowList.Add("BigFunnel.NeuralTree.dll")
$ModuleAllowList.Add("DocParserWrapper.dll")
#.NET Foundation
$ModuleAllowList.Add("Microsoft.AspNet.SignalR.Core.dll")
$ModuleAllowList.Add("Microsoft.AspNet.SignalR.SystemWeb.dll")
#Microsoft Research Limited
$ModuleAllowList.Add("Infer.Compiler.dll")
$ModuleAllowList.Add("Infer.Runtime.dll")
#The Legion of the Bouncy Castle
$ModuleAllowList.Add("BouncyCastle.Crypto.dll")
#Google Inc.
$ModuleAllowList.Add("Google.Protobuf.dll")
#Newtonsoft
$ModuleAllowList.Add("Newtonsoft.Json.dll")
$ModuleAllowList.Add("Newtonsoft.Json.Bson.dll")
#Marc Gravell
$ModuleAllowList.Add("protobuf-net.dll")
$ModuleAllowList.Add("protobuf-net.Core.dll")
#Matthew Manela
$ModuleAllowList.Add("DiffPlex.dll")
#The Apache Software Foundation
$ModuleAllowList.Add("log4net.dll")
#http://system.data.sqlite.org/
$ModuleAllowList.Add("System.Data.SQLite.dll")
#Robert Simpson, et al.
$ModuleAllowList.Add("SQLite.Interop.dll")
#Microsoft.Cloud.InstrumentationFramework.*
$ModuleAllowList.Add("Microsoft.Cloud.InstrumentationFramework.Events.dll")
$ModuleAllowList.Add("Microsoft.Cloud.InstrumentationFramework.Health.dll")
$ModuleAllowList.Add("Microsoft.Cloud.InstrumentationFramework.Metrics.dll")
#Windows
$ModuleAllowList.Add("prxyqry.DLL")
$ModuleAllowList.Add("icu.dll")
$ModuleAllowList.Add("TextShaping.dll")
#Windows Fraunhofer IIS MPEG Audio Layer-3 ACM codec - MPEG Audio Layer-3
Codec for MSACM
$ModuleAllowList.Add("l3codecp.acm")
# CompanyName allow list
$CompanyNameAllowList = New-Object Collections.Generic.List[string]
$CompanyNameAllowList.Add("Microsoft Corporation")
$CompanyNameAllowList.Add("Microsoft Corporation.")
$CompanyNameAllowList.Add("Microsoft")
$CompanyNameAllowList.Add("Microsoft Corp.")
$CompanyNameAllowList.Add("Microsoft CoreXT")
#$CompanyNameAllowList.Add("Microsoft Research Limited") #Only 2 modules
$CompanyNameAllowList.Add("Корпорация Майкрософт")
$CompanyNameAllowList.Add("Корпорація Майкрософт")
$CompanyNameAllowList.Add("Корпорація Майкрософт (Microsoft Corporation)")
$CompanyNameAllowList.Add("Корпорація Майкрософт (Microsoft Corporation)")
$CompanyNameAllowList.Add("Microsoft корпорациясы")
$CompanyNameAllowList.Add("Корпорация Майкрософт.")
# CompanyName allow list
$FIPCompanyNameAllowList = New-Object Collections.Generic.List[string]
$FIPCompanyNameAllowList.Add("Oracle Corporation")
$FIPCompanyNameAllowList.Add("Oracle Corp.")
# cSpell:enable
Write-Verbose "Allow List Module Count: $($ModuleAllowList.count)"
# Gather each process and work thru their module list to remove any known
modules.
foreach ($process in $ServerProcess) {
Write-Verbose "Checking $($process.path) - PID: $($process.Id)"
Write-Progress -Activity "Checking Exchange Processes" -
CurrentOperation "$currentDiff More Seconds" -PercentComplete ((($initialDiff -
$currentDiff) / $initialDiff) * 100) -Status " "
[int]$currentDiff = (New-TimeSpan -End
$StartDate.AddMinutes($WaitingTimeForAVAnalysisInMinutes) -Start (Get-
Date)).TotalSeconds
# Gather all modules
[array]$ProcessModules = $process.modules
# Remove Microsoft modules
Write-Verbose "Removing Microsoft Modules"
$ProcessModules = $ProcessModules | Where-Object
{ $_.FileVersionInfo.CompanyName -notin $CompanyNameAllowList }
# Remove Oracle modules on FIPS
Write-Verbose "Removing Oracle Modules"
$ProcessModules = $ProcessModules | Where-Object { (-not($_.FileName -
like "*\FIP-FS\Bin\*" -and ($_.FileVersionInfo.CompanyName -in
$FIPCompanyNameAllowList))) }
# Clear out modules from the allow list
Write-Verbose "Removing Allow Modules"
foreach ($module in $ModuleAllowList) {
$ProcessModules = $ProcessModules | Where-Object { $_.ModuleName -
ne $module -and $_.ModuleName -ne $($module.Replace(".dll", ".ni.dll")) }
}
if ($ProcessModules.count -gt 0) {
foreach ($module in $ProcessModules) {
$OutString = ("PROCESS: $($process.ProcessName) PID($
($process.Id)) UNEXPECTED MODULE: $($module.ModuleName) COMPANY: $
($module.Company)`n`tPATH: $($module.FileName)`n`tFileVersion: $
($module.FileVersion)")
if ($process.MainModule.ModuleName -eq "W3wp.exe") {
if ($AMSIDll -contains $module.FileName) {
$OutString = ("PROCESS: $($process.ProcessName) PID($
($process.Id)) MODULE: $($module.ModuleName) COMPANY: $($module.Company)`n`tPATH: $
($module.FileName)`n`tFileVersion: $($module.FileVersion)")
Write-Host "[WARNING] - AMSI DLL Detected: $OutString"
-ForegroundColor Yellow
$SuspiciousAMSIinW3wpProcessList += $OutString
} else {
Write-Host "[FAIL] - $OutString" -ForegroundColor Red
$SuspiciousW3wpProcessList += $OutString
}
} else {
Write-Host "[FAIL] - $OutString" -ForegroundColor Red
$SuspiciousProcessList += $OutString
}
}
}
}
$firstExecution = $false
} else {
Start-Sleep -Seconds 1
Write-Progress -Activity "Waiting for AV" -CurrentOperation "$currentDiff
More Seconds" -PercentComplete ((($initialDiff - $currentDiff) / $initialDiff) *
100) -Status " "
[int]$currentDiff = (New-TimeSpan -End
$StartDate.AddMinutes($WaitingTimeForAVAnalysisInMinutes) -Start (Get-
Date)).TotalSeconds
}
}
Write-Host "Analyzed Exchange Processes"
# Create a list of folders that are probably being scanned by AV
$BadFolderList = New-Object Collections.Generic.List[string]
Write-Host "Testing for EICAR files"
# Test each location for the EICAR file
foreach ($Folder in $FolderList) {
$FilePath = (Join-Path $Folder $eicarFullFileName)
# If the file exists delete it -- this means the folder is not being scanned
if (Test-Path $FilePath ) {
#Get content to confirm that the file is not blocked by AV
$output = Get-Content $FilePath -ErrorAction SilentlyContinue
if ($output -eq $eicar) {
Write-Verbose "Removing $FilePath"
Remove-Item $FilePath -Confirm:$false -Force
} else {
Write-Host "[FAIL] - Possible AV Scanning on Path: $Folder" -
ForegroundColor Red
$BadFolderList.Add($Folder)
}
}
# If the file doesn't exist Add that to the bad folder list -- means the folder
is being scanned
else {
Write-Host "[FAIL] - Possible AV Scanning on Path: $Folder" -
ForegroundColor Red
$BadFolderList.Add($Folder)
}
if ($nonExistentFolder -contains $Folder) {
Remove-Item $Folder -Confirm:$false -Force -Recurse
Write-Verbose "Removed folder: $Folder"
}
}
$BadExtensionList = New-Object Collections.Generic.List[string]
# Test each extension for the EICAR file
foreach ($extension in $extensionsList) {
$filepath = Join-Path $randomFolder "$eicarFileName.$extension"
# If the file exists delete it -- this means the extension is not being scanned
if (Test-Path $filepath ) {
#Get content to confirm that the file is not blocked by AV
$output = Get-Content $FilePath -ErrorAction SilentlyContinue
if ($output -eq $eicar) {
Write-Verbose "Removing $FilePath"
Remove-Item $FilePath -Confirm:$false -Force
} else {
Write-Host "[FAIL] - Possible AV Scanning on Extension: $extension" -
ForegroundColor Red
$BadExtensionList.Add($extension)
}
}
# If the file doesn't exist Add that to the bad extension list -- means the
extension is being scanned
else {
Write-Host "[FAIL] - Possible AV Scanning on Extension: $extension" -
ForegroundColor Red
$BadExtensionList.Add($extension)
}
}
#Delete Random Folder
Remove-Item $randomFolder -Confirm:$false -Force -Recurse
$OutputPath = Join-Path $PSScriptRoot BadExclusions-$StartDateFormatted.txt
"##################################################################################
#########" | Out-File $OutputPath
"Exclusions analysis at $((Get-Date).ToString())" | Out-File $OutputPath -Append
"##################################################################################
#########" | Out-File $OutputPath -Append
# Report what we found
if ($BadFolderList.count -gt 0 -or $BadExtensionList.Count -gt 0 -or
$SuspiciousProcessList.count -gt 0 -or $SuspiciousW3wpProcessList.count -gt 0 -or
$SuspiciousAMSIinW3wpProcessList.count -gt 0) {
Write-Host "Possible AV Scanning found" -ForegroundColor Red
if ($BadFolderList.count -gt 0 ) {
"`n[Missing Folder Exclusions]" | Out-File $OutputPath -Append
$BadFolderList | Out-File $OutputPath -Append
Write-Warning ("Found $($BadFolderList.count) of $($FolderList.Count)
folders that are possibly being scanned! ")
}
if ($BadExtensionList.count -gt 0 ) {
"`n[Missing Extension Exclusions]" | Out-File $OutputPath -Append
$BadExtensionList | Out-File $OutputPath -Append
Write-Warning ("Found $($BadExtensionList.count) of $
($extensionsList.Count) extensions that are possibly being scanned! ")
}
if ($SuspiciousProcessList.count -gt 0 ) {
"`n[Non-Default Modules Loaded]" | Out-File $OutputPath -Append
$SuspiciousProcessList | Out-File $OutputPath -Append
Write-Warning ("Found $($SuspiciousProcessList.count) UnExpected modules
loaded into Exchange Processes ")
}
if ($SuspiciousW3wpProcessList.count -gt 0 ) {
$SuspiciousW3wpProcessListString = "`nW3wp.exe is not present in the
recommended Exclusion list but we found 3rd Party modules on it and could affect
Exchange performance or functionality."
$SuspiciousW3wpProcessListString | Out-File $OutputPath -Append
Write-Warning $SuspiciousW3wpProcessListString
"`n[Non-Default Modules Loaded on W3wp.exe]" | Out-File $OutputPath -Append
$SuspiciousW3wpProcessList | Out-File $OutputPath -Append
Write-Warning ("Found $($SuspiciousW3wpProcessList.count) UnExpected
modules loaded into W3wp.exe ")
}
if ($SuspiciousAMSIinW3wpProcessList.count -gt 0) {
$SuspiciousAMSIinW3wpProcessListString = "`nFound AMSI modules in w3wp
processes`nThat may impact Exchange performance and Outlook connectivity in some
scenarios.`nThese modules are not necessarily anomalies, but we recommend checking
the following articles: `n`thttps://learn.microsoft.com/en-us/exchange/antispam-
and-antimalware/amsi-integration-with-exchange `n`thttps://aka.ms/Test-AMSI"
$SuspiciousAMSIinW3wpProcessListString | Out-File $OutputPath -Append
Write-Warning $SuspiciousAMSIinW3wpProcessListString
"`n[AMSI Modules Loaded on W3wp.exe]" | Out-File $OutputPath -Append
$SuspiciousAMSIinW3wpProcessList | Out-File $OutputPath -Append
Write-Warning ("Found $($SuspiciousAMSIinW3wpProcessList.count) AMSI
modules loaded into W3wp.exe ")
}
Write-Warning ("Review " + $OutputPath + " For the full list.")
} else {
$CorrectExclusionsString = "`nAll EICAR files found; File Exclusions,
Extensions Exclusions and Processes Exclusions (Did not find Non-Default modules
loaded) appear to be set properly"
$CorrectExclusionsString | Out-File $OutputPath -Append
Write-Host $CorrectExclusionsString
}
# SIG # Begin signature block
# MIIoQwYJKoZIhvcNAQcCoIIoNDCCKDACAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBgAREo/Sg5W0Y4
# BUCICahNLqE2ctQ6Gb/ax6zqUCMyDKCCDXYwggX0MIID3KADAgECAhMzAAAEBGx0
# Bv9XKydyAAAAAAQEMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjQwOTEyMjAxMTE0WhcNMjUwOTExMjAxMTE0WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC0KDfaY50MDqsEGdlIzDHBd6CqIMRQWW9Af1LHDDTuFjfDsvna0nEuDSYJmNyz
# NB10jpbg0lhvkT1AzfX2TLITSXwS8D+mBzGCWMM/wTpciWBV/pbjSazbzoKvRrNo
# DV/u9omOM2Eawyo5JJJdNkM2d8qzkQ0bRuRd4HarmGunSouyb9NY7egWN5E5lUc3
# a2AROzAdHdYpObpCOdeAY2P5XqtJkk79aROpzw16wCjdSn8qMzCBzR7rvH2WVkvF
# HLIxZQET1yhPb6lRmpgBQNnzidHV2Ocxjc8wNiIDzgbDkmlx54QPfw7RwQi8p1fy
# 4byhBrTjv568x8NGv3gwb0RbAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQU8huhNbETDU+ZWllL4DNMPCijEU4w
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMjkyMzAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAIjmD9IpQVvfB1QehvpC
# Ge7QeTQkKQ7j3bmDMjwSqFL4ri6ae9IFTdpywn5smmtSIyKYDn3/nHtaEn0X1NBj
# L5oP0BjAy1sqxD+uy35B+V8wv5GrxhMDJP8l2QjLtH/UglSTIhLqyt8bUAqVfyfp
# h4COMRvwwjTvChtCnUXXACuCXYHWalOoc0OU2oGN+mPJIJJxaNQc1sjBsMbGIWv3
# cmgSHkCEmrMv7yaidpePt6V+yPMik+eXw3IfZ5eNOiNgL1rZzgSJfTnvUqiaEQ0X
# dG1HbkDv9fv6CTq6m4Ty3IzLiwGSXYxRIXTxT4TYs5VxHy2uFjFXWVSL0J2ARTYL
# E4Oyl1wXDF1PX4bxg1yDMfKPHcE1Ijic5lx1KdK1SkaEJdto4hd++05J9Bf9TAmi
# u6EK6C9Oe5vRadroJCK26uCUI4zIjL/qG7mswW+qT0CW0gnR9JHkXCWNbo8ccMk1
# sJatmRoSAifbgzaYbUz8+lv+IXy5GFuAmLnNbGjacB3IMGpa+lbFgih57/fIhamq
# 5VhxgaEmn/UjWyr+cPiAFWuTVIpfsOjbEAww75wURNM1Imp9NJKye1O24EspEHmb
# DmqCUcq7NqkOKIG4PVm3hDDED/WQpzJDkvu4FrIbvyTGVU01vKsg4UfcdiZ0fQ+/
# V0hf8yrtq9CkB8iIuk5bBxuPMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGiMwghofAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAQEbHQG/1crJ3IAAAAABAQwDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMC3zH7JQqx6VBFYONfbkZKA
# 3DfOz9sBpqvnGbihVssuMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEALs+SZGl/e1SyCi9PfTMUvyBX85cVc4Y/BLxmyvh5Vipcon6Fezk5RQPa
# c1iUvZOvAJlps9ZTiqhjVJ7iN1gnvCBU5H37EYTpMv0GqX/ip62SAHt5UyFVV8nu
# JFqgUPrr4O9ptxTwgM5H6G9Kn2wnWXWI6/dBt8hRot42iSzZ66ts1oaDRP3yA42o
# Ja78FYeT4S4+61OKTOAy3eLmsf9wr043dV2Svn2kOyet86Wvo+wUD6Vt/7yiQ6cS
# gLXNLcl+PZvRODoKLOMHNKnbOeuZ9BejOLOkCn4LLgPctn84K/1cQa1L1hhx3AL/
# LooC5UphfrH5zQkhkyAmzYlMCqIP5aGCF60wghepBgorBgEEAYI3AwMBMYIXmTCC
# F5UGCSqGSIb3DQEHAqCCF4YwgheCAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsq
# hkiG9w0BCRABBKCCAUkEggFFMIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCAZ6P/rgcwlDb85a2REwvZ6dAImohAWLb2Gy7zdmCfHMwIGaFMyI122
# GBMyMDI1MDcwMTE5NDkyMy4yMTVaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo2RjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaCCEfswggcoMIIFEKADAgECAhMzAAAB/Bigr8xpWoc6AAEAAAH8MA0G
# CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI0
# MDcyNTE4MzExNFoXDTI1MTAyMjE4MzExNFowgdMxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9w
# ZXJhdGlvbnMgTGltaXRlZDEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjZGMUEt
# MDVFMC1EOTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl
# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp1DAKLxpbQcPVYPHlJHy
# W7W5lBZjJWWDjMfl5WyhuAylP/LDm2hb4ymUmSymV0EFRQcmM8BypwjhWP8F7x4i
# O88d+9GZ9MQmNh3jSDohhXXgf8rONEAyfCPVmJzM7ytsurZ9xocbuEL7+P7EkIwo
# OuMFlTF2G/zuqx1E+wANslpPqPpb8PC56BQxgJCI1LOF5lk3AePJ78OL3aw/Ndlk
# vdVl3VgBSPX4Nawt3UgUofuPn/cp9vwKKBwuIWQEFZ837GXXITshd2Mfs6oYfxXE
# tmj2SBGEhxVs7xERuWGb0cK6afy7naKkbZI2v1UqsxuZt94rn/ey2ynvunlx0R6/
# b6nNkC1rOTAfWlpsAj/QlzyM6uYTSxYZC2YWzLbbRl0lRtSz+4TdpUU/oAZSB+Y+
# s12Rqmgzi7RVxNcI2lm//sCEm6A63nCJCgYtM+LLe9pTshl/Wf8OOuPQRiA+stTs
# g89BOG9tblaz2kfeOkYf5hdH8phAbuOuDQfr6s5Ya6W+vZz6E0Zsenzi0OtMf5RC
# a2hADYVgUxD+grC8EptfWeVAWgYCaQFheNN/ZGNQMkk78V63yoPBffJEAu+B5xlT
# PYoijUdo9NXovJmoGXj6R8Tgso+QPaAGHKxCbHa1QL9ASMF3Os1jrogCHGiykfp1
# dKGnmA5wJT6Nx7BedlSDsAkCAwEAAaOCAUkwggFFMB0GA1UdDgQWBBSY8aUrsUaz
# hxByH79dhiQCL/7QdjAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBf
# BgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2NybC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmww
# bAYIKwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0El
# MjAyMDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUF
# BwMIMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAgEAT7ss/ZAZ0bTa
# FsrsiJYd//LQ6ImKb9JZSKiRw9xs8hwk5Y/7zign9gGtweRChC2lJ8GVRHgrFkBx
# ACjuuPprSz/UYX7n522JKcudnWuIeE1p30BZrqPTOnscD98DZi6WNTAymnaS7it5
# qAgNInreAJbTU2cAosJoeXAHr50YgSGlmJM+cN6mYLAL6TTFMtFYJrpK9TM5Ryh5
# eZmm6UTJnGg0jt1pF/2u8PSdz3dDy7DF7KDJad2qHxZORvM3k9V8Yn3JI5YLPuLs
# o2J5s3fpXyCVgR/hq86g5zjd9bRRyyiC8iLIm/N95q6HWVsCeySetrqfsDyYWStw
# L96hy7DIyLL5ih8YFMd0AdmvTRoylmADuKwE2TQCTvPnjnLk7ypJW29t17Yya4V+
# Jlz54sBnPU7kIeYZsvUT+YKgykP1QB+p+uUdRH6e79Vaiz+iewWrIJZ4tXkDMmL2
# 1nh0j+58E1ecAYDvT6B4yFIeonxA/6Gl9Xs7JLciPCIC6hGdliiEBpyYeUF0ohZF
# n7NKQu80IZ0jd511WA2bq6x9aUq/zFyf8Egw+dunUj1KtNoWpq7VuJqapckYsmvm
# mYHZXCjK1Eus7V1I+aXjrBYuqyM9QpeFZU4U01YG15uWwUCaj0uZlah/RGSYMd84
# y9DCqOpfeKE6PLMk7hLnhvcOQrnxP6kwggdxMIIFWaADAgECAhMzAAAAFcXna54C
# m0mZAAAAAAAVMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UE
# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z
# b2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZp
# Y2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMy
# MjVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH
# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV
# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0B
# AQEFAAOCAg8AMIICCgKCAgEA5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51
# yMo1V/YBf2xK4OK9uT4XYDP/XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY
# 6GB9alKDRLemjkZrBxTzxXb1hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9
# cmmvHaus9ja+NSZk2pg7uhp7M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN
# 7928jaTjkY+yOSxRnOlwaQ3KNi1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDua
# Rr3tpK56KTesy+uDRedGbsoy1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74
# kpEeHT39IM9zfUGaRnXNxF803RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2
# K26oElHovwUDo9Fzpk03dJQcNIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5
# TI4CvEJoLhDqhFFG4tG9ahhaYQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZk
# i1ugpoMhXV8wdJGUlNi5UPkLiWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9Q
# BXpsxREdcu+N+VLEhReTwDwV2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3Pmri
# Lq0CAwEAAaOCAd0wggHZMBIGCSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUC
# BBYEFCqnUv5kxJq+gpE8RjUpzxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJl
# pxtTNRnpcjBcBgNVHSAEVTBTMFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9y
# eS5odG0wEwYDVR0lBAwwCgYIKwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUA
# YgBDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# 1fZWy4/oolxiaNE9lJBb186aGMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIw
# MTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0w
# Ni0yMy5jcnQwDQYJKoZIhvcNAQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/yp
# b+pcFLY+TkdkeLEGk5c9MTO1OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulm
# ZzpTTd2YurYeeNg2LpypglYAA7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM
# 9W0jVOR4U3UkV7ndn/OOPcbzaN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECW
# OKz3+SmJw7wXsFSFQrP8DJ6LGYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4
# FOmRsqlb30mjdAy87JGA0j3mSj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3Uw
# xTSwethQ/gpY3UA8x1RtnWN0SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPX
# fx5bRAGOWhmRaw2fpCjcZxkoJLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVX
# VAmxaQFEfnyhYWxz/gq77EFmPWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGC
# onsXHRWJjXD+57XQKBqJC4822rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU
# 5nR0W2rRnj7tfqAxM328y+l7vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEG
# ahC0HVUzWLOhcGbyoYIDVjCCAj4CAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJl
# bGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT
# Tjo2RjFBLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIjCgEBMAcGBSsOAwIaAxUATkEpJXOaqI2wfqBsw4NLVwqYqqqggYMw
# gYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsF
# AAIFAOwOKoAwIhgPMjAyNTA3MDEwOTM2MDBaGA8yMDI1MDcwMjA5MzYwMFowdDA6
# BgorBgEEAYRZCgQBMSwwKjAKAgUA7A4qgAIBADAHAgEAAgIxcTAHAgEAAgITSDAK
# AgUA7A98AAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBCwUAA4IBAQBzM9JHopsJW+9E
# Ix0WwLbPmHjdBU5SeaGurWHkY/knwzJmZ+8KxIh2A7BFfFD3JADqbQuArirtMkTR
# NlOsyQ6O2pa/WsZky5+t+6m29Vd6Ue858BVK57pjP27Tk8hNJQqAqovbDOmpBquT
# 9xlpg3mmMd2U3QXnl4eCQrgIXcCYr/uXZiwQYO5CqfeoNtWFv8yV4MfTvE/fyGi6
# lDHoYp3MTETa0rRty7Jj4MTlbR1+DpOJ9v75s3F/fbGOyHm5m3sSfXFRyC7xVDUD
# nzmBapmtUtVGbrfNtNABDzmsnQfrmNcTTjIzvJJ8RUMujiVLPtB3CArd1Rg06ETf
# bDW1dcdnMYIEDTCCBAkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAH8GKCvzGlahzoAAQAAAfwwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqG
# SIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQg5ofPhqKfHGsc
# 6E2g+zEUjomc5yvCoY7X38/9Y2NNQ5EwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHk
# MIG9BCCVQq+Qu+/h/BOVP4wweUwbHuCUhh+T7hq3d5MCaNEtYjCBmDCBgKR+MHwx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1p
# Y3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB/Bigr8xpWoc6AAEAAAH8
# MCIEIIjOtSF+eVNJwJyC9kCUkdc4vkGND2xxoXjH8CdsDNZpMA0GCSqGSIb3DQEB
# CwUABIICAIFfGWNhNz8ftsUvja0Nog1iuALGDo3OjXeXZsEosfO3+H4tXcE76Fzn
# rNwjSRJt4gLOpEPkXrdFYXg7tCdei7P+sU62aIQQVC5pgzQNvGBOKNfuJ2/Rtkye
# CsG1eCWcQB/p9Q1VnEaUz9T9VE555iht/MoKwF1l0FWdjlLksWuamdMGxgKEVg1D
# SlxQ+HnD1F2T8UzOBeMcm0ToRRy6bzYlPjSTJrUxQD6HtgErdU+lYabxx6fuk1MD
# grnh9TwbrTiUFus/eWT8g21FmMOoXYhmDcOaSX7irZQzCkxRWPDB2jBRak9mduJV
# OLohilgg+hAxtgcdtib0mUO3CZbAjJgNV/3xqj5GCjxnHlS/ABxNiKnUxyu9XNgF
# 7BwCEqzynkxiuGMCu3vBABQKKT3NmDMHbeV6K3Lb3vUrNoPILwoItNIkTDjv1HSP
# kf0QfQWxgviYHXk6KdtoRXYG2R3b9YGq/o9Qz1y9i9qHx+6c61Ti4Z0Zyo0ZyMmy
# Yq/Jwf0ydMNmFFPS1BEcSlfruXwMwhvZMeO6YAg6kAI49pm0GFZHPkd1MOKDMO2u
# faH5NfEMJO9JN1aM9NQQ/uWwLYNqXIbY0RuGfy4iXyD3QnfD9W8J7au3sfuhGFR4
# wT2+tIRbqlU3SVNzUhIKqBEXAoyGqKsie7UjZAs15MhTyb06+a+v
# SIG # End signature block