Last active
March 7, 2026 16:12
-
-
Save kmahyyg/13ceb3feeb9f16290af6bff5dd3fe75b to your computer and use it in GitHub Desktop.
DefenderDLPTrace.ps1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #requires -RunAsAdministrator | |
| Set-StrictMode -Version Latest | |
| $ErrorActionPreference = 'Stop' | |
| # ------------------------------- | |
| # Static config (non-interactive friendly) | |
| # ------------------------------- | |
| $script:DDLP_Root = 'C:\Windows\TEMP' | |
| $script:DDLP_WprpPath = Join-Path $script:DDLP_Root 'DefenderDLPTrace.wprp' | |
| $script:DDLP_EtlPath = Join-Path $script:DDLP_Root 'DefenderDLPTrace.etl' | |
| $script:DDLP_ProfileId = 'DefenderDLPTrace.Verbose' | |
| $script:DDLP_ExpectedIds = @{ | |
| 'Microsoft-Antimalware-RTP' = @(22,23,24) | |
| 'Microsoft-Antimalware-Service' = @(21,52,53,54) | |
| } | |
| function New-DefenderDLPTraceProfile { | |
| [CmdletBinding()] | |
| param() | |
| if (-not (Test-Path $script:DDLP_Root)) { | |
| New-Item -Path $script:DDLP_Root -ItemType Directory -Force | Out-Null | |
| } | |
| $wprp = @' | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <WindowsPerformanceRecorder Version="1.0" Author="GPT-Codex-kmahyyg"> | |
| <Profiles> | |
| <EventCollector Id="EC_DefenderDLP" Name="Defender DLP Event Collector"> | |
| <BufferSize Value="64"/> | |
| <Buffers Value="128"/> | |
| <MaximumFileSize Value="128" FileMode="Circular" /> | |
| </EventCollector> | |
| <EventProvider Id="EP_AntimalwareRTP" Name="Microsoft-Antimalware-RTP"> | |
| <EventFilters FilterIn="true"> | |
| <!-- EventId Value="22"/ --> | |
| <EventId Value="23"/> | |
| <EventId Value="24"/> | |
| </EventFilters> | |
| </EventProvider> | |
| <EventProvider Id="EP_AntimalwareService" Name="Microsoft-Antimalware-Service"> | |
| <EventFilters FilterIn="true"> | |
| <!-- EventId Value="21"/ --> | |
| <EventId Value="52"/> | |
| <EventId Value="53"/> | |
| <EventId Value="54"/> | |
| </EventFilters> | |
| </EventProvider> | |
| <Profile Id="DefenderDLPTrace.Verbose.File" | |
| Name="DefenderDLPTrace" | |
| Description="Capture only selected Defender DLP Event IDs" | |
| DetailLevel="Verbose" | |
| LoggingMode="File"> | |
| <Collectors> | |
| <EventCollectorId Value="EC_DefenderDLP"> | |
| <EventProviders> | |
| <EventProviderId Value="EP_AntimalwareRTP"/> | |
| <EventProviderId Value="EP_AntimalwareService"/> | |
| </EventProviders> | |
| </EventCollectorId> | |
| </Collectors> | |
| </Profile> | |
| </Profiles> | |
| </WindowsPerformanceRecorder> | |
| '@ | |
| Set-Content -Path $script:DDLP_WprpPath -Value $wprp -Encoding UTF8 | |
| return $script:DDLP_WprpPath | |
| } | |
| function Start-DefenderDLPTrace { | |
| [CmdletBinding()] | |
| param( | |
| [switch]$OverwriteEtl | |
| ) | |
| if (-not (Get-Command wpr.exe -ErrorAction SilentlyContinue)) { | |
| throw "wpr.exe not found." | |
| } | |
| $wprpPath = New-DefenderDLPTraceProfile | |
| if ($OverwriteEtl -and (Test-Path $script:DDLP_EtlPath)) { | |
| Remove-Item -Path $script:DDLP_EtlPath -Force | |
| } | |
| # Best-effort cleanup; ignore failure if no active session | |
| try { & wpr.exe -cancel | Out-Null } catch { } | |
| $profileArg = "$wprpPath!$($script:DDLP_ProfileId)" | |
| & wpr.exe -start $profileArg -filemode | Out-Null | |
| [pscustomobject]@{ | |
| Action = 'Start' | |
| Success = $true | |
| Profile = $profileArg | |
| EtlPath = $script:DDLP_EtlPath | |
| Timestamp = (Get-Date).ToString('o') | |
| } | |
| } | |
| function Stop-DefenderDLPTrace { | |
| [CmdletBinding()] | |
| param( | |
| [switch]$SkipValidation | |
| ) | |
| if (-not (Get-Command wpr.exe -ErrorAction SilentlyContinue)) { | |
| throw "wpr.exe not found." | |
| } | |
| & wpr.exe -stop $script:DDLP_EtlPath | Out-Null | |
| if (-not (Test-Path $script:DDLP_EtlPath)) { | |
| throw "ETL not found after stop: $($script:DDLP_EtlPath)" | |
| } | |
| if ($SkipValidation) { | |
| return [pscustomobject]@{ | |
| Action = 'Stop' | |
| Success = $true | |
| Validated = $false | |
| EtlPath = $script:DDLP_EtlPath | |
| Timestamp = (Get-Date).ToString('o') | |
| } | |
| } | |
| $countsMap = @{} # key = "Provider|EventId" ; value = count | |
| $totalEvents = 0 | |
| Get-WinEvent -Path $script:DDLP_EtlPath -Oldest | ForEach-Object { | |
| $totalEvents++ | |
| $provider = $_.ProviderName | |
| $id = [int]$_.Id | |
| $key = "$provider|$id" | |
| if ($countsMap.ContainsKey($key)) { $countsMap[$key]++ } else { $countsMap[$key] = 1 } | |
| } | |
| $counts = @(foreach ($k in $countsMap.Keys) { | |
| $p = $k.Split('|',2)[0] | |
| $i = [int]$k.Split('|',2)[1] | |
| [pscustomobject]@{ | |
| Provider = $p | |
| EventId = $i | |
| Count = $countsMap[$k] | |
| } | |
| }) | Sort-Object Count -Descending | |
| $unexpected = @(foreach ($row in $counts) { | |
| if ($script:DDLP_ExpectedIds.ContainsKey($row.Provider) -and | |
| ($row.EventId -notin $script:DDLP_ExpectedIds[$row.Provider])) { | |
| $row | |
| } | |
| }) | |
| [pscustomobject]@{ | |
| Action = 'Stop' | |
| Success = $true | |
| Validated = $true | |
| ValidationPassed = ($unexpected.Count -eq 0) | |
| EtlPath = $script:DDLP_EtlPath | |
| TotalEvents = $totalEvents | |
| Summary = $counts | |
| Unexpected = @($unexpected) | |
| Timestamp = (Get-Date).ToString('o') | |
| } | |
| } |
Comments are disabled for this gist.