├── .gitignore ├── .gitattributes ├── README.md └── WinDefAntiVirus.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore config file 2 | /ignore -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hMailServer WinDefAntiVirus 2 | Use Windows Defender Antivirus as external scanner for hMailServer 3 | 4 | Discussion thread: https://hmailserver.com/forum/viewtopic.php?f=9&t=40635 5 | 6 | This script will mitigate false positives caused by using Windows Defender as external scanner by re-scanning files that test positive. It also logs scan activity that does not immediately come back clean. 7 | 8 | # Instructions 9 | Fill in the variables at the top of the script 10 | 11 | Enter into hMailServer Admin Console > Settings > Anti-virus > External virus scanner > Scanner executable: 12 | 13 | ```Powershell -File "C:\path\to\script\WinDefAntiVirus.ps1" "%FILE%"``` 14 | 15 | Enter `2` for Return value 16 | 17 | -------------------------------------------------------------------------------- /WinDefAntiVirus.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | 3 | .SYNOPSIS 4 | WinDefAntiVirus for hMailServer 5 | 6 | .DESCRIPTION 7 | Powershell script to use Windows Defender as custom virus scanner in hMailServer. 8 | 9 | Script runs Windows Defender single-file scan on messages passed to it from hMailServer. 10 | 11 | Script mitigates false positives and offers detailed logging. 12 | 13 | .FUNCTIONALITY 14 | Enter into hMailServer Admin Console > Settings > Anti-virus > External virus scanner > Scanner executable: 15 | Powershell -File "C:\path\to\script\WinDefAntiVirus.ps1" "%FILE%" 16 | 17 | Enter "2" for Return value 18 | 19 | .PARAMETER FileToScan 20 | The file to scan passed from hMailServer while calling this script. 21 | 22 | .NOTES 23 | Discussion topic at hMailServer forum: 24 | https://hmailserver.com/forum/viewtopic.php?f=9&t=40635 25 | 26 | #> 27 | 28 | Param( 29 | [String]$FileToScan 30 | ) 31 | 32 | <### VARIABLES ###> 33 | 34 | $LogFolder = "C:\hMailServer\Logs" # Location of hMailServer Log folder 35 | $ScanTries = 3 # Number of times to try scanning file before giving up 36 | 37 | <### FUNCTIONS ###> 38 | 39 | Function Log($Msg) { 40 | $LogFile = "$LogFolder\WinDefAntiVirus.log" 41 | If (-Not(Test-Path $LogFile)) {New-Item $LogFile -Force} 42 | Write-Output "$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss.fff')) : $Msg" | Out-File $LogFile -Append -Encoding ASCII 43 | } 44 | 45 | Function Execute-Command ($CommandPath, $CommandArguments) { 46 | # https://stackoverflow.com/questions/8761888/capturing-standard-out-and-error-with-start-process 47 | $pinfo = New-Object System.Diagnostics.ProcessStartInfo 48 | $pinfo.FileName = $CommandPath 49 | $pinfo.RedirectStandardError = $True 50 | $pinfo.RedirectStandardOutput = $True 51 | $pinfo.UseShellExecute = $False 52 | $pinfo.Arguments = $CommandArguments 53 | $p = New-Object System.Diagnostics.Process 54 | $p.StartInfo = $pinfo 55 | $p.Start() | Out-Null 56 | $p.WaitForExit() 57 | [pscustomobject]@{ 58 | StdOut = $p.StandardOutput.ReadToEnd() 59 | StdErr = $p.StandardError.ReadToEnd() 60 | ExitCode = $p.ExitCode 61 | } 62 | } 63 | 64 | Function Ordinal ($Integer) { 65 | $Return = Switch ($Integer) { 66 | 1 {"first"; Break} 67 | 2 {"second"; Break} 68 | 3 {"third"; Break} 69 | 4 {"fourth"; Break} 70 | Default {$Integer; Break} 71 | } 72 | Return $Return 73 | } 74 | 75 | <### START SCRIPT ###> 76 | 77 | If ([String]::IsNullOrEmpty($FileToScan)) { 78 | Log "[ERROR] : No file argument presented : Quitting" 79 | Exit 0 80 | } 81 | 82 | $IterateScan = 0 83 | 84 | If (Test-Path $FileToScan) { 85 | Do { 86 | Try { 87 | $WinDef = Execute-Command -CommandPath "$Env:ProgramW6432\Windows Defender\MpCmdRun.exe" -CommandArguments "-Scan -ScanType 3 -File ""$FileToScan"" -DisableRemediation" 88 | } 89 | Catch { 90 | Log "[ERROR] : $FileToScan : Error running Windows Defender command : $($Error[0])" 91 | Exit 0 92 | } 93 | 94 | If ($WinDef.ExitCode -eq 0) { 95 | If ($IterateScan -gt 0) { 96 | Log "[CLEAN] : $FileToScan : Clean scan on $(Ordinal ($IterateScan + 1)) scan : Exit code $($WinDef.ExitCode)" 97 | } 98 | Exit 0 99 | } 100 | 101 | If (-not([String]::IsNullOrEmpty($WinDef.StdErr))) { 102 | Log "[ERROR] : $FileToScan : Error on $(Ordinal ($IterateScan + 1)) scan : $($WinDef.StdErr)" 103 | } 104 | 105 | If (-not([String]::IsNullOrEmpty($WinDef.ExitCode))) { 106 | $VirusName = ($WinDef.StdOut | Select-String -Pattern "(?<=\sVirus:).*").Matches.Value 107 | $VirusName = $VirusName -Replace "[\n\r]+","" 108 | If (-not([String]::IsNullOrEmpty($VirusName))) { 109 | Log "[VIRUS] : $FileToScan : VIRUS FOUND! $VirusName : Found on $(Ordinal ($IterateScan + 1)) scan : Exit code $($WinDef.ExitCode)" 110 | Exit $WinDef.ExitCode 111 | } Else { 112 | If ($IterateScan -lt ($ScanTries - 1)) { 113 | Log "[VIRUS] : $FileToScan : Probable error on $(Ordinal ($IterateScan + 1)) scan : Exit code $($WinDef.ExitCode) : Trying again" 114 | } Else { 115 | Log "[VIRUS] : $FileToScan : Probable error on $(Ordinal ($IterateScan + 1)) scan : Exit code $($WinDef.ExitCode) : Giving up : Exit as clean" 116 | Exit 0 117 | } 118 | } 119 | } Else { 120 | Log "[ERROR] : $FileToScan : No exit code on $(Ordinal ($IterateScan + 1)) scan : Quitting" 121 | Exit 0 122 | } 123 | 124 | Start-Sleep -Seconds 1 125 | $IterateScan++ 126 | } Until ($IterateScan -eq $ScanTries) 127 | 128 | } Else { 129 | Log "[NOFND] : $FileToScan : File could not be found : Quitting" 130 | Exit 0 131 | } 132 | 133 | Log "[ERROR] : $FileToScan : You should never see this message : Notify administrator" 134 | Exit 0 135 | --------------------------------------------------------------------------------