-
-
Save zbalkan/17fbe38864a900a2f1eeac2088c5d49e to your computer and use it in GitHub Desktop.
#Requires -RunAsAdministrator | |
<# | |
.Synopsis | |
Generates Sysmon Archive file quota for `File Delete` events to help managing the size. | |
.DESCRIPTION | |
Based on: https://blog.nviso.eu/2022/06/30/enforcing-a-sysmon-archive-quota/ | |
.INPUTS | |
None. Cmdlet does not accept pipe values. | |
.OUTPUTS | |
None if succeeds, exception if fails. | |
.EXAMPLE | |
New-SysmonArchiveQuota -Force | |
.EXAMPLE | |
New-SysmonArchiveQuota -ArchiveFolder Sysmon -Size 2 -Unit GB -Force | |
.LINK | |
https://gist.github.com/zbalkan/17fbe38864a900a2f1eeac2088c5d49e | |
#> | |
function New-SysmonArchiveQuota | |
{ | |
[CmdletBinding(SupportsShouldProcess=$true, | |
PositionalBinding=$false, | |
HelpUri = 'https://gist.github.com/zbalkan/17fbe38864a900a2f1eeac2088c5d49e', | |
ConfirmImpact='Medium')] | |
Param | |
( | |
# Folder name where Sysmon stores archive files | |
[Parameter(Mandatory=$false, | |
HelpMessage="Folder name where Sysmon stores archive files")] | |
[ValidateLength(0,120)] | |
[String] | |
$ArchiveFolder = 'Sysmon', | |
# Size as a `float` | |
[Parameter(Mandatory=$false)] | |
[ValidateScript({($_ -gt 0)})] | |
[float] | |
$Size = 1.0, | |
# "TB", "GB", "MB" or "KB" | |
[Parameter(Mandatory=$false)] | |
[ValidateSet("TB", "GB", "MB", "KB")] | |
$Unit = 'GB', | |
# Overwrite current quota configuration | |
[Parameter(Mandatory=$false)] | |
[switch] | |
$Force | |
) | |
Begin | |
{ | |
# Check if SYSTEM user | |
$sid = ([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value | |
if ($sid -ne "S-1-5-18") | |
{ | |
Write-Error "The script must be run as SYSTEM. Use 'psexec -is powershell' then run the script." | |
exit 1 # Return ERROR exit code to set $LASTEXITCODE | |
} | |
$DelaySeconds = 10 | |
$SizeWithUnit = "$($Size)$($Unit)" | |
} | |
Process | |
{ | |
foreach ($Drive in (Get-PSDrive -PSProvider FileSystem | Select-Object -Property Root)) | |
{ | |
$Archive = Join-Path -Path $Drive.Root -ChildPath $ArchiveFolder | |
Write-Verbose "Sysmon Archive path: $Archive" | |
if ($pscmdlet.ShouldProcess($Archive, "Create archive size quota")) | |
{ | |
$FilterName = "SysmonArchiveWatcher_$($Drive.Root.Remove(1))" | |
Write-Verbose "New WMI filter: $FilterName" | |
$ConsumerName = "SysmonArchiveCleaner_$($Drive.Root.Remove(1))" | |
Write-Verbose "New WMI event: $ConsumerName" | |
$PreviousBindings = @(Get-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding | Where-Object {$_.Consumer.Name -Like $ConsumerName} ) | |
if ($PreviousBindings.Count -ne 0){ | |
if($Force) | |
{ | |
Write-Verbose "Removing previous filter-event binding." | |
$PreviousBindings | ForEach-Object { Remove-CimInstance $_ } | |
} | |
else | |
{ | |
Write-Warning "The WMI filter-event binding already exists. Skipping." | |
continue; | |
} | |
} | |
$PreviousFilters = @(Get-CimInstance -Namespace root/subscription -ClassName __EventFilter | Where-Object -Property Name -EQ $FilterName) | |
if ($PreviousFilters.Count -ne 0){ | |
if($Force) | |
{ | |
Write-Verbose "Removing previous WMI filter." | |
$PreviousFilters | ForEach-Object { Remove-CimInstance $_ } | |
} | |
else | |
{ | |
Write-Warning "The WMI filter $FilterName already exists. Skipping." | |
continue; | |
} | |
} | |
$PreviousConsumers = @(Get-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer | Where-Object -Property Name -EQ $ConsumerName) | |
if ($PreviousConsumers.Count -ne 0){ | |
if($Force) | |
{ | |
Write-Verbose "Removing previous WMI event." | |
$PreviousConsumers | ForEach-Object { Remove-CimInstance $_ } | |
} | |
else | |
{ | |
Write-Warning "The consumer $ConsumerName already exists. Skipping." | |
continue; | |
} | |
} | |
# Create a WMI filter for files being created within the Sysmon archive. | |
Write-Verbose "Creating WMI Filter $FilterName" | |
$Query = "SELECT * FROM __InstanceCreationEvent WITHIN $DelaySeconds WHERE TargetInstance ISA 'CIM_DataFile' AND TargetInstance.Drive='$($Drive.Root.Remove(2))' AND TargetInstance.Path='\\$($ArchiveFolder)' GROUP WITHIN $DelaySeconds" | |
Write-Verbose "Query: $Query" | |
$Filter = New-CimInstance -Namespace root/subscription -ClassName __EventFilter -Property @{ | |
Name = $FilterName; | |
EventNameSpace = 'root\cimv2'; | |
QueryLanguage = "WQL"; | |
Query = $Query | |
} | |
Write-Verbose "Creating WMI consumer event $ConsumerName" | |
# Create a command line consumer which will clean up the Sysmon archive folder until the quota is reached. | |
$Consumer = New-CimInstance -Namespace root/subscription -ClassName CommandLineEventConsumer -Property @{ | |
Name = $ConsumerName; | |
ExecutablePath = (Get-Command PowerShell).Source; | |
CommandLineTemplate = "-NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -Command `"`$Archived = Get-ChildItem -Path '$Archive' -File | Where-Object {`$_.LinkType -ne 'HardLink'} | Sort-Object -Property LastAccessTimeUtc; `$SizeWithUnit = (`$Archived | Measure-Object -Sum -Property Length).Sum; for(`$Index = 0; (`$Index -lt `$Archived.Count) -and (`$SizeWithUnit -gt $Limit); `$Index++){ try {`$Archived[`$Index] | Remove-Item -Force -ErrorAction Stop; `$SizeWithUnit -= `$Archived[`$Index].Length} catch {}}`"" | |
} | |
# Create a WMI binding from the filter to the consumer. | |
Write-Verbose "Creating WMI binding between filter $FilterName and consumer $ConsumerName" | |
$Binding = New-CimInstance -Namespace root/subscription -ClassName __FilterToConsumerBinding -Property @{ | |
Filter = [Ref]$Filter; | |
Consumer = [Ref]$Consumer; | |
} | |
} | |
} | |
} | |
End | |
{ | |
} | |
} |
Hi, Nice idea, Zafer I'll note, howerver, there's a comment in the linked nvisio article that one has to make sure that event has to do the deletion as system user. That makes sense since the sysmon folder is the default archive folder, and is owned by SYSTEM. Your script requires run as admin, but that's not the same thing as system. I guess one could take ownership, to avoid that problem, but that wouldn't be ideal for security reasons.
Hi @waydaws, you are right. I should have noted somewhere. In Powershell #requires
statements, that's the highest possible. Maybe I can check if the calling user is SYSTEM or SID is S-1-5-18 in the beginning and note that the script should be called by running psexec -i - s powershell.exe
first.
That’s a good idea.
I think there is an error in the script. When you defined the filter, you use the variable $Limit
, which is not defined anywhere in the script.
Renaming the declaration of $SizeWithUnit
to $Limit
in the Begin
section should fix it. Also, I don't know why you call the Sysmon folder total size SizeWithUnit, since it's returned as an integer of the number of bytes, without a unit, by Measure-Object
.
Hi @zbalkan, thank you for sharing this! :)
Use the commands below for cleanup. It is too simple for a new cmdlet.