Public/New-NSPSysmonFileWatchConfig.ps1

function New-NSPSysmonFileWatchConfig {
    <#
    .SYNOPSIS
        Builds a Sysmon configuration that logs a file-create event (ID 11) when a file lands in the given folders - and, by default, nothing else.
    .DESCRIPTION
        For triggering a job when a file arrives: a scheduled task on Sysmon event 11
        (New-NSPEventTaskTrigger in NSP.Win.ScheduledTask) picks the event up. Returns the XML as
        text; Install-NSPSysmon writes and applies it, Test-NSPSysmonConfig checks it is in force.
 
        Each folder matches files anywhere beneath it ("begin with" the folder plus a backslash, so
        C:\Drop doesn't also match C:\Dropbox). -IncludeExtension narrows that to files with
        those extensions (each folder AND one of the extensions). -ExcludeExtension skips files
        the job writes itself (e.g. its own .pgp output) so it doesn't trigger on them.
 
        Sysmon has one configuration per computer. Unless -KeepDefaultEvents, every other event
        type gets an empty include rule (log nothing), so the computer's Sysmon log holds only the
        file watch - not every process start, which Sysmon logs when no rule says otherwise.
    .PARAMETER RuleName
        Names each include rule. Sysmon reports the matching rule's name in the event's RuleName
        field, so a task trigger can select only these events even when the rules are merged into
        a busier monitoring configuration. Omitted, rules are unnamed as before.
    .PARAMETER RuleGroupOnly
        Return only the include RuleGroup for review/merging into an existing configuration.
        Rejects global extension exclusions; does not write or apply any Sysmon configuration.
    .EXAMPLE
        New-NSPSysmonFileWatchConfig -Path 'D:\Jobs\Outbound' -ExcludeExtension '.pgp'
    .EXAMPLE
        New-NSPSysmonFileWatchConfig -Path 'D:\Drop' -IncludeExtension '.txt'
    .EXAMPLE
        $xml = New-NSPSysmonFileWatchConfig -Path 'D:\Drop\A', 'D:\Drop\B'
        Install-NSPSysmon -SysmonPath 'C:\Tools\Sysmon64.exe' -ConfigPath 'D:\Jobs\Sysmon.xml' -Configuration $xml
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Returns text; changes nothing.')]
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)][string[]]$Path,
        [string[]]$IncludeExtension,
        [string[]]$ExcludeExtension,
        [ValidatePattern('^\d+\.\d+$')][string]$SchemaVersion = '4.50',
        [switch]$KeepDefaultEvents,
        [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9 ._-]{0,63}$')][string]$RuleName,
        [switch]$RuleGroupOnly
    )

    if ($RuleGroupOnly -and $ExcludeExtension) { throw 'RuleGroupOnly rejects global exclusions; review exclusions in the authoritative configuration.' }
    $escape = { [Security.SecurityElement]::Escape([string]$args[0]) }
    $folders = foreach ($p in $Path) {
        $full = [IO.Path]::GetFullPath($p).TrimEnd('\')
        "$full\"
    }
    $toExtension = { foreach ($e in $args[0]) { if ($e) { '.' + $e.TrimStart('.', '*') } } }
    $includes = @(& $toExtension $IncludeExtension)
    $extensions = @(& $toExtension $ExcludeExtension)

    $lines = [Collections.Generic.List[string]]::new()
    $lines.Add("<Sysmon schemaversion=`"$SchemaVersion`">")
    $lines.Add(' <!-- File watch built by NSP.Win.Sysmon (New-NSPSysmonFileWatchConfig). Sysmon has one configuration per computer. -->')
    $lines.Add(' <EventFiltering>')
    $lines.Add(' <RuleGroup name="NSP file watch" groupRelation="or">')
    $lines.Add(' <FileCreate onmatch="include">')
    $named = if ($RuleName) { " name=`"$(& $escape $RuleName)`"" } else { '' }
    foreach ($f in $folders) {
        if (-not $includes.Count) { $lines.Add(" <TargetFilename$named condition=`"begin with`">$(& $escape $f)</TargetFilename>"); continue }
        foreach ($i in $includes) {
            $lines.Add(" <Rule$named groupRelation=`"and`">")
            $lines.Add(" <TargetFilename condition=`"begin with`">$(& $escape $f)</TargetFilename>")
            $lines.Add(" <TargetFilename condition=`"end with`">$(& $escape $i)</TargetFilename>")
            $lines.Add(' </Rule>')
        }
    }
    $lines.Add(' </FileCreate>')
    $lines.Add(' </RuleGroup>')
    if ($extensions.Count) {
        $lines.Add(' <RuleGroup name="NSP file watch exclusions" groupRelation="or">')
        $lines.Add(' <FileCreate onmatch="exclude">')
        foreach ($x in $extensions) { $lines.Add(" <TargetFilename condition=`"end with`">$(& $escape $x)</TargetFilename>") }
        $lines.Add(' </FileCreate>')
        $lines.Add(' </RuleGroup>')
    }
    if (-not $KeepDefaultEvents) {
        # Event types (other than FileCreate) the schema knows; an empty include rule logs none of them
        $others = @('ProcessCreate', 'FileCreateTime', 'NetworkConnect', 'ProcessTerminate', 'DriverLoad', 'ImageLoad',
            'CreateRemoteThread', 'RawAccessRead', 'ProcessAccess', 'RegistryEvent', 'FileCreateStreamHash', 'PipeEvent',
            'WmiEvent', 'DnsQuery', 'FileDelete', 'ClipboardChange', 'ProcessTampering')
        if ([version]$SchemaVersion -ge [version]'4.90') { $others += @('FileDeleteDetected', 'FileBlockExecutable', 'FileBlockShredding', 'FileExecutableDetected') }
        foreach ($name in $others) {
            $lines.Add(" <RuleGroup name=`"NSP file watch: no $name`" groupRelation=`"or`">")
            $lines.Add(" <$name onmatch=`"include`" />")
            $lines.Add(' </RuleGroup>')
        }
    }
    $lines.Add(' </EventFiltering>')
    $lines.Add('</Sysmon>')
    if ($RuleGroupOnly) { return ([xml]($lines -join "`r`n")).Sysmon.EventFiltering.RuleGroup[0].OuterXml }
    ($lines -join "`r`n") + "`r`n"
}