Get DefenderReport
Get DefenderReport
.SYNOPSIS
Creates an HTML table of computer's Windows Defender status and emails it.
.DESCRIPTION
Polls a specified AD OU for computers' Windows Defender status. Generates and
emails an HTML report of computers that may need
attention using the following criteria:
- Windows Defender is disabled
- Virus definitions have not been updated more recently than the set
threshold
- A full system scan has not been performed more recently than the set
threshold
- Threats have been found
.EXAMPLE
Get-DefenderReport.ps1
.NOTES
Written by Jason Dillman on 10-20-2017
Revision Date: 02-27-2019
Rev. 1.8.1
This script has only been tested with Windows 10 Pro at this time. It should
perform correctly on Windows Server 2016 as well, but is not tested.
I believe this will work with Windows 8.1 but do not have any machines to test
with. Windows 7/Server 2008 do not have the Powershell cmdlets
to retrieve Windows Defender status. Service and OS information should still
be able to be retrieved, though this is not tested and will probably
produce unexpected results.
This script requires PSRemoting to be enabled and the user running the script
must have permission to perform Invoke-Command on all remote computers.
Changelog
1.8.1: Date: 02-27-2019: Moved New-HTMLReport function into the Report-
Functions.psm1 module.
1.8.0: Date: 02-06-2019: Changed $reportColorCoding from a script block
executing several if statements to a switch statement.
1.7.2: Date: 06-28-2018: Cleaned up formatting/spacing.
1.7.1: Date: 03-07-2018: Fixed a compatibility bug with PS Version 4 not
handling hash tables the same way as PS Version 5.1.
1.7.0: Date: 03-06-2018: Re-wrote the sorting loop and modified the output to
allow for a more modular HTML table creation function. Re-wrote nearly the
entirety
of the New-HTMLTable function to allow the code to be easily and
flexibly re-used in the future as well as eliminating duplicate code. Re-wrote the
Test-Online function to eliminate features not used in this script and
speed up operation (just over 2x speed increase in testing). The way the function
works is still largely the same as Dale Thompson's original function.
1.6.0: Date: 01-23-2018: Significantly refactored code to make it shorter and
easier to follow. Squashed a few bugs. Re-named variables and properties to
make their purpose clearer. Removed code to re-try retrieving
information from remote computers as it did not appear to work as implemented.
Changed code formatting to more closely follow Powershell standard.
1.5.0: Date: 11-17-2017: Changed Where-Object {$_ -ne $null} to specify the
data .TypeName instead when adding information to the array lists. Changed wording
for
"Defender Enabled" from true/false to yes/no. Changed report building
function to acces thresholds as parameters so that they can be specified once
instead of twice.
Eliminated .lastscandate -eq $null check as it was redundant and could
cause issues. Fixed the retry loop as it referenced the wrong variable and would
never trigger.
Worked on moving duplicate code into functions...this caused a myriad of
problems and has, for the time being, been abandonded.
1.4.0: Date: 11-9-2017: Added parallel online (ping) checking, added a retry
for any computers that information could not be obtained from, and changed output
for computers that no information was obtained from stating that a
connection could not be made.
1.3.0: Date: 10-31-2017: Commented out the Operating System, Defender Service
startup type, and Definition last updated date columns from the HTML report to
clean it up.
1.2.0: Added ability to log client OS and Defender service information
1.1.0: Added logging of Win32 Antivirus information. This will provide the
name of the currently installed A/V solution.
To-Do
#>
<# Configuration Settings #>
#$searchBase = 'DC=local,DC=domain,DC=com'
$searchBase = 'OU=_Computers,OU=location,DC=local,DC=domain,DC=com'
<# Alarm thresholds - Note that these will be color coded #>
# Computers whith antivirus definitions older than this many days will be reported
$signatureAgeDays = 5
# Computers with a 'Last Full System Scan' date more than this many days ago will
be reported
$scanAgeDays = 14
Import-Module 'C:\Scripts\Report-Functions.psm1'
function Test-Online {
<#
.SYNOPSIS
Test connectivity to one or more computers in parallel.
.DESCRIPTION
Tests one or more computers for network connectivity. The list is done in
parallel to quickly check a large number of machines.
.Notes
Written by Jason Dillman on 3-6-2018
Rev. 1.0
This is a streamlined and customized version of the Test-Online function
written by Dale Thompson.
There is no throttling built into this version which will be an issue if
too many computers are passed into the function. In my
testing performance is not an issue with 100-200 computers.
.EXAMPLE
'Computer1','Computer2' | Test-Online -Property Name | Where-Object
{$_.OnlineStatus -eq $true}
Tests 2 computers (named Computer1 and Computer2) and sends the names of
those that are on the network down the pipeline.
.INPUTS
String
.OUTPUTS
Same as input with the OnlineStatus property appended
#>
Param (
[Parameter(
Mandatory,
ValueFromPipeline=$true)]
$computersToTest
)
begin {
# Declare function variable(s)
$Jobs = @{}
}
process {
foreach ($computerName in $computersToTest) {
$job = Test-Connection -Count 2 -ComputerName $computerName -AsJob -
ErrorAction SilentlyContinue
$jobs.add($computerName, $job)
}
}
end {
while ($jobs.count -gt 0){
$runningJobNames = $jobs.keys.clone()
foreach ($runningJob in $runningJobNames){
if ($jobs.$runningJob.State -ne 'Completed'){
continue
}
if ($jobs.$runningJob | Receive-Job | Where-Object {$_.StatusCode -
eq 0} | Select-Object -First 1){
$output = $runningJob | Add-Member -Force -PassThru -
NotePropertyName OnlineStatus -NotePropertyValue $true
$output
Remove-Job $jobs.$runningJob
$jobs.Remove($runningJob)
} else {
$output = $runningJob | Add-Member -Force -PassThru -
NotePropertyName OnlineStatus -NotePropertyValue $false
$output
Remove-Job $jobs.$runningJob
$jobs.Remove($runningJob)
}
}
Start-Sleep -Milliseconds 200
}
}
} # End function Test-Online
<#
###################################################################################
###################################################################################
##
####################################################################### Start of
program
###########################################################################
###################################################################################
###################################################################################
##
#>
$ADComputerParameters = @{
Filter = "(OperatingSystem -like '*Windows Server 2016*') -or
(OperatingSystem -like '*Windows 10*') -or (OperatingSystem -like '*Windows Server
2019*')"
SearchBase = $searchBase
Properties = 'IPv4Address'
}
# Get the list of computers from the specified Active Directory OU. Filter out
offline computers, computers which OS doesn't include the Defender Powershell
module,
# the local computer, and VPN computers
$computerList = (Get-ADComputer @ADComputerParameters |
Where-Object {$_.IPv4Address -notlike '10.0.1*'}).Name |
Test-Online |
Where-Object {$_.OnlineStatus -eq $true -and $_ -notlike
$env:COMPUTERNAME}
if (($localOS -like '*Windows 10*') -or ($localOS -like '*2016*') -or ($localOS -
like '*2019*')){
# PSComputerName property is added since .PSComputerName is used later
$localComputerInformation = & $scriptBlock
$localComputerInformation | ForEach-Object {
$_ |
Where-Object {$_.PSComputerName -ne $env:COMPUTERNAME} |
Add-Member -NotePropertyName PSComputerName -NotePropertyValue
$env:COMPUTERNAME -Force
} # End ForEach-Object
}
$aggregateComputerInformation.add($localComputerInformation)
$aggregateComputerInformation.add($remoteComputerInformation)
# Add information required for the report from any computer that has an issue to
report
$computersToReport = foreach ($computerName in ($aggregateComputerInformation |
Foreach-Object {$_ | Select-Object -Property PSComputerName -
Unique}).PSComputerName){
if ($computersToReport){
#PS Version 4 compatibility. If there is only 1 computer then we re-create
$computersToReport as an array of 1 hash table
if ($computersToReport.gettype().name -eq 'OrderedDictionary'){
$computersToReport = @($computersToReport)
}
# Send the sorted Windows Defender info for all computers that failed any
status checks to New-DefenderReport to convert the info into an HTML table end
email the table
$emailBody = $computersToReport.foreach({[pscustomobject]$_ }) |
Sort-Object -Property 'Computer Name','Sort' |
Select-Object -Property 'Computer
Name',<#'OS','StartType','LastUpdate',#>'Defender Enabled','Service Status','A/V
Product','Definition Age','Last Full Scan','Threat Name','Color Coding','Primary
Column Name' |
New-HTMLReport
$emailParameters = @{
'To' = $emailTo
'From' = $emailFrom
'Subject' = $emailSubject
'Body' = $emailBody
'SmtpServer' = $smptServer
'BodyAsHtml' = $true
'Credential' = $credential
'Port' = $port
'UseSsl' = $true
}
Send-MailMessage @emailParameters
}