Skip to content

Instantly share code, notes, and snippets.

@LM-CT
Forked from zbalkan/New-SysmonArchiveQuota.ps1
Last active May 21, 2024 16:43
Show Gist options
  • Save LM-CT/a920de2d0d6bcaff4d9d4fd037c39ae8 to your computer and use it in GitHub Desktop.
Save LM-CT/a920de2d0d6bcaff4d9d4fd037c39ae8 to your computer and use it in GitHub Desktop.
If you use Sysmon and enabled FileDelete events started with Sysmon 11, you probably came up with the issue of instantly growing hidden archive. For those who have not solved the problem yet, I came up with a PowerShell cmdlet (run as SYSTEM) based on the article https://blog.nviso.eu/2022/06/30/enforcing-a-sysmon-archive-quota/
#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
$LimitSizeWithUnit = "$($Size)$($Unit)"
}
Process
{
foreach ($Drive in (Get-PSDrive -PSProvider FileSystem | Select-Object -Property Root))
{
$Archive = Join-Path -Path $Drive.Root.Remove(3) -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; `$MonitoredSize = (`$Archived | Measure-Object -Sum -Property Length).Sum; for(`$Index = 0; (`$Index -lt `$Archived.Count) -and (`$MonitoredSize -gt $LimitSizeWithUnit); `$Index++){ try {`$Archived[`$Index] | Remove-Item -Force -ErrorAction Stop; `$MonitoredSize -= `$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
{
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment