Scripts/Invoke-EndpointDetection.ps1

param([parameter(Mandatory)]$Rules)

filter Read-WinEvent {
        $WinEvent = [ordered]@{} 
        $XmlData = [xml]$_.ToXml()
        $SystemData = $XmlData.Event.System
        $SystemData | 
        Get-Member -MemberType Properties | 
        Select-Object -ExpandProperty Name |
        ForEach-Object {
            $Field = $_
            if ($Field -eq 'TimeCreated') {
                $WinEvent.$Field = Get-Date -Format 'yyyy-MM-dd hh:mm:ss' $SystemData[$Field].SystemTime
            } elseif ($SystemData[$Field].'#text') {
                $WinEvent.$Field = $SystemData[$Field].'#text'
            } else {
                $SystemData[$Field]  | 
                Get-Member -MemberType Properties | 
                Select-Object -ExpandProperty Name |
                ForEach-Object { 
                    $WinEvent.$Field = @{}
                    $WinEvent.$Field.$_ = $SystemData[$Field].$_
                }
            }
        }
        $XmlData.Event.EventData.Data |
        ForEach-Object { 
            $WinEvent.$($_.Name) = $_.'#text'
        }
        return New-Object -TypeName PSObject -Property $WinEvent
}

function Send-Alert {
    [CmdletBinding(DefaultParameterSetName = 'Log')]
    Param(
        [Parameter(Mandatory, Position = 0)][ValidateSet("Balloon","Log","Email")][string]$AlertMethod,
        [Parameter(Mandatory, Position = 1)]$Subject,
        [Parameter(Mandatory, Position = 2)]$Body,
        [Parameter(ParameterSetName = "Log")][string]$LogName,
        [Parameter(ParameterSetName = "Log")][string]$LogSource,
        [Parameter(ParameterSetName = "Log")][ValidateSet("Information","Warning")]$LogEntryType = "Warning",
        [Parameter(ParameterSetName = "Log")][int]$LogEventId = 1,
        [Parameter(ParameterSetName = "Email")][string]$EmailServer,
        [Parameter(ParameterSetName = "Email")][string]$EmailServerPort,
        [Parameter(ParameterSetName = "Email")][string]$EmailAddressSource,
        [Parameter(ParameterSetName = "Email")][string]$EmailPassword,
        [Parameter(ParameterSetName = "Email")][string]$EmailAddressDestination
    )
    <#
        .SYNOPSIS
        Sends an alert.

        .DESCRIPTION
        When called, this function will either write to the Windows Event log, send an email, or generate a Windows balloon tip notification.
        
        .LINK
        https://mcpmag.com/articles/2017/09/07/creating-a-balloon-tip-notification-using-powershell.aspx
    #>


    if ($AlertMethod -eq "Balloon") {
        Add-Type -AssemblyName System.Windows.Forms
        Unregister-Event -SourceIdentifier IconClicked -ErrorAction Ignore
        Remove-Job -Name IconClicked -ErrorAction Ignore
        Remove-Variable -Name Balloon -ErrorAction Ignore
        $Balloon = New-Object System.Windows.Forms.NotifyIcon
        [void](Register-ObjectEvent `
            -InputObject $Balloon `
            -EventName MouseDoubleClick `
            -SourceIdentifier IconClicked `
            -Action { $Balloon.Dispose() }
        )
        $IconPath = (Get-Process -Id $pid).Path
        $Balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($IconPath)
        $Balloon.BalloonTipIcon = [System.Windows.Forms.ToolTipIcon]::Warning
        $Balloon.BalloonTipTitle = $Subject
        $Balloon.BalloonTipText = $Body
        $Balloon.Visible = $true
        $Balloon.ShowBalloonTip(10000)
    } elseif ($AlertMethod -eq "Log") {
        $LogExists = Get-EventLog -LogName $LogName -Source $LogSource -ErrorAction Ignore -Newest 1
        if (-not $LogExists) {
            New-EventLog -LogName $LogName -Source $LogSource -ErrorAction Ignore
        }
        Write-EventLog `
            -LogName $LogName `
            -Source $LogSource `
            -EntryType $LogEntryType `
            -EventId $LogEventId `
            -Message $Body
    } elseif ($AlertMethod -eq "Email") {
        $EmailClient = New-Object Net.Mail.SmtpClient($EmailServer, $EmailServerPort)
        $EmailClient.EnableSsl = $true
        $EmailClient.Credentials = New-Object System.Net.NetworkCredential($EmailAddressSource, $EmailPassword)
        $EmailClient.Send($EmailAddressSource, $EmailAddressDestination, $Subject, $Body)
    }
}

function Get-Hash {
    param([parameter(Mandatory)]$Object)
    $Stream = ([System.IO.MemoryStream]::New([System.Text.Encoding]::ASCII.GetBytes($Object)))
    $Hash = Get-FileHash -Algorithm MD5 -InputStream $Stream | Select-Object -ExpandProperty Hash
    return $Hash
}

function Invoke-EventFrequencyAnalysis {
    param([parameter(Mandatory)]$Rule)
    $FilterHashTable = @{
        LogName = $Rule.LogName
        Id = $Rule.EventId
        StartTime = $(Get-Date).AddMinutes(-$Rule.Minutes)
    }
    $Events = Get-WinEvent -FilterHashtable $FilterHashTable | 
        Read-WinEvent |
        Where-Object {
            $_.$($Rule.Field) -match $Rule.Value
        }
    $Frequency = $Events.Count
    if ($Frequency -gt $Rule.Threshold) {
        $Properties = [ordered]@{ 
            RuleName = $Rule.Name
            Hash = ""
            Events = @()
        }
        $Alert = New-Object -TypeName psobject -Property $Properties
        $Events | 
        Select-Object -First $Rule.Threshold |
        ForEach-Object {
            $RecordId = $_.EventRecordId
            $Properties = [ordered]@{
                TimeCreated = $_.TimeCreated
                RecordId = $RecordId
                $($Rule.Field) = $_.$($Rule.Field)
            }
            $Alert.Events += $Properties 
        }
        $Alert.Hash = Get-Hash $Events
        $Body = $Alert | ConvertTo-Json
        Send-Alert `
            -AlertMethod "Balloon" `
            -Subject $Rule.Name `
            -Body $Body `
            -LogName "Endpoint-Detection" `
            -LogSource "Endpoint-Detection" `
            -LogEntryType Warning `
            -LogEventId 1337
    }
}

function Invoke-EventSpikeAnalysis {}
function Invoke-EventFlatlineAnalysis {}
function Invoke-EventBlacklistAnalysis {}
function Invoke-EventWhitelistAnalysis {}
function Invoke-EventPatternAnalysis {}
function Invoke-EventChangeAnalysis {}
function Invoke-EventNewTermAnalysis {}
function Invoke-EventCardinalityAnalysis {}

function Get-RuleFile {
    param([Parameter(Mandatory)]$Path)
    $Rule = Get-Content $Path | ConvertFrom-Json
    switch ($Rule.type) {
        "frequency" { Invoke-EventFrequencyAnalysis -Rule $Rule }
        "spike" { Invoke-EventSpikeAnalysis -Rule $Rule }
        "flatline" { Invoke-EventFlatlineAnalysis -Rule $Rule }
        "blacklist" { Invoke-EventBlacklistAnalysis -Rule $Rule }
        "whitelist" { Invoke-EventWhitelistAnalysis -Rule $Rule }
        "pattern" { Invoke-EventPatternAnalysis -Rule $Rule }
        "change" { Invoke-EventChangeAnalysis -Rule $Rule }
        "newterm" { Invoke-EventNewTermAnalysis -Rule $Rule }
        "cardinality" { Invoke-EventCardinalityAnalysis -Rule $Rule }
    }
}

if (Test-Path $Rules) {
    $ItemType = (Get-Item -Path $Rules).GetType()
    if ($ItemType -eq [System.IO.DirectoryInfo]) {
        Get-ChildItem -Path $Rules -Recurse |
        ForEach-Object {
            Get-RuleFile -Path $_.FullName
        }
    } else {
        Get-RuleFile -Path $Rules
    }
} else {
    Write-Error "Failed to find $Rules."
}