Private/ScanBridgeSysmonSetup.ps1
|
# ScanBridge Sysmon scan trigger: a Sysmon file watch on the route Source folders whose rules carry # one name, so the ScanBridge task's event trigger reacts to those events only. The task never # receives a filename from the event; each run is an ordinary pass over every route. $script:NSPScanBridgeSysmonRuleName='NSP ScanBridge' function Get-NSPScanBridgeWatchPlan { # Source folders to watch, and the event delay: the slowest route's MinimumAgeSeconds + 30 s. # Refuses a Spool or Archive at or under a Source, which would make ScanBridge trigger itself. param([Parameter(Mandatory)][string]$ConfigurationPath) $config=Import-PowerShellDataFile -LiteralPath $ConfigurationPath -ErrorAction Stop $routes=@($config.Routes | Where-Object { $_ }) if(-not $routes.Count) { throw 'The settings file has no Routes to watch.' } $full={ param($p) [IO.Path]::GetFullPath([string]$p).TrimEnd('\') } $sources=@($routes | ForEach-Object { if(Test-NSPPlaceholderValue $_.Source){ throw "Route '$($_.Name)' has no Source folder." }; & $full $_.Source } | Select-Object -Unique) foreach($route in $routes) { foreach($kind in 'Spool','Archive') { if(Test-NSPPlaceholderValue $route[$kind]) { continue } $path=& $full $route[$kind] foreach($source in $sources) { if($path -ieq $source -or $path.StartsWith($source+'\',[StringComparison]::OrdinalIgnoreCase)) { throw "Route '$($route.Name)': $kind '$path' is inside the watched Source '$source', so ScanBridge's own files would trigger it. Move the $kind outside every Source." } } } } $age=[int](@($routes | ForEach-Object { if($_.MinimumAgeSeconds){[int]$_.MinimumAgeSeconds}else{20} }) | Measure-Object -Maximum).Maximum [pscustomobject]@{Sources=$sources;MaximumAgeSeconds=$age;EventDelaySeconds=$age+30;RuleName=$script:NSPScanBridgeSysmonRuleName} } function Invoke-NSPScanBridgeSysmonSetup { <# Plans, then on confirm applies, the Sysmon file watch for this ScanBridge settings file. Sysmon missing: offers a Microsoft-signed download or a folder. Running another configuration: asks to Keep it (default; the rule group is saved for a manual merge) or Replace it. DRY RUN stops after the plan. Returns the mode: Own, Merge, or $null. #> [CmdletBinding()] param([Parameter(Mandatory)][string]$ConfigurationPath) Import-NSPToolkitModule -Name NSP.Win.Sysmon -MinimumVersion 0.1.1 $builder=Get-Command New-NSPSysmonFileWatchConfig -ErrorAction SilentlyContinue if(-not $builder -or -not $builder.Parameters.ContainsKey('RuleName') -or -not (Get-Command Save-NSPSysmon -ErrorAction SilentlyContinue)) { throw 'NSP.Win.Sysmon does not support named rules and signed downloads. Provision the build with ScanBridge trigger support first.' } $dryRun=[bool](Get-NSPDryRun) $plan=Get-NSPScanBridgeWatchPlan -ConfigurationPath $ConfigurationPath Write-NSPConsoleLine ("Watch for new PDFs in: {0}" -f ($plan.Sources -join '; ')) foreach($source in $plan.Sources) { if(-not (Test-Path -LiteralPath $source -PathType Container)) { Write-NSPConsoleLine "Source folder $source does not exist yet; Sysmon still watches the path." -Role Warning } } if(-not $dryRun -and -not (Test-NSPScanBridgeElevated)) { Write-NSPConsoleLine 'Installing or applying Sysmon needs an elevated session. Restart PowerShell as administrator.' -Role Error; return } $configDirectory=Split-Path -Parent $ConfigurationPath $sysmonConfig=Join-Path $configDirectory 'Sysmon.xml' $xml=New-NSPSysmonFileWatchConfig -Path $plan.Sources -IncludeExtension '.pdf' -RuleName $plan.RuleName $status=Get-NSPSysmonStatus $installArgs=@{ConfigPath=$sysmonConfig;Configuration=$xml} if(-not $status.Installed) { $found=Find-NSPSysmon if(-not $found) { $source=Read-NSPMenu -Prompt 'Sysmon is not installed. Where should it come from?' -CancelLabel 'Back' -Choices @( [pscustomobject]@{Label='Download from Microsoft';Value='Download';Help='From download.sysinternals.com; kept only when signed by Microsoft Corporation. Saved under C:\ProgramData\NSP\Sysmon.'} [pscustomobject]@{Label='Use a folder I have';Value='Folder';Help='A folder or USB drive containing Sysmon64.exe (or Sysmon.exe).'} ) if($null -eq $source) { return } if($source -eq 'Folder') { $found=Find-NSPSysmon -Path (Read-NSPNonEmpty -Prompt 'Folder containing Sysmon64.exe').Trim().Trim('"') if(-not $found) { Write-NSPConsoleLine 'No Sysmon64.exe or Sysmon.exe there.' -Role Error; return } } elseif($dryRun) { Write-NSPConsoleLine 'DRY RUN: would download Sysmon from Microsoft and check its signature.' -Role Warning } elseif(Read-NSPConfirm -Prompt 'Download Sysmon from Microsoft now?' -DefaultYes) { $saved=Save-NSPSysmon -Destination (Join-Path $env:ProgramData 'NSP\Sysmon') -Confirm:$false Write-NSPConsoleLine ("Downloaded Sysmon {0}, signed by {1}." -f $saved.Version,$saved.Signer) -Role Success $found=$saved.Path } else { return } } if($found) { $installArgs.SysmonPath=$found } $action="Install Sysmon$(if($found){" from $found"})" } elseif($status.ConfigFile -and [IO.Path]::GetFullPath([string]$status.ConfigFile) -ieq [IO.Path]::GetFullPath($sysmonConfig)) { $check=Test-NSPSysmonConfig -ConfigPath $sysmonConfig -Configuration $xml if($check.InSync) { Write-NSPConsoleLine 'Sysmon is already watching these folders with the ScanBridge rules.' -Role Success; return 'Own' } $action='Update the ScanBridge Sysmon configuration' } else { $other=if($status.ConfigFile){[string]$status.ConfigFile}else{'(an unrecorded file)'} Write-NSPConsoleLine ("Sysmon {0} is running another configuration: {1}" -f $status.Version,$other) -Role Warning $choice=Read-NSPMenu -Prompt 'What should happen to that configuration?' -CancelLabel 'Back' -Choices @( [pscustomobject]@{Label='Keep it and merge the ScanBridge rules by hand';Value='Keep';Help="Nothing changes in Sysmon. The ScanBridge rule group is saved next to the settings; once merged, the task reacts only to rules named '$($plan.RuleName)'."} [pscustomobject]@{Label='Replace it with the ScanBridge-only configuration';Value='Replace';Help='Whatever that configuration logs today (often security monitoring) stops being logged. Only for test or leftover configurations.'} ) if($null -eq $choice) { return } if($choice -eq 'Keep') { $fragmentPath=Join-Path $configDirectory 'ScanBridge-Sysmon-RuleGroup.xml' $fragment=New-NSPSysmonFileWatchConfig -Path $plan.Sources -IncludeExtension '.pdf' -RuleName $plan.RuleName -RuleGroupOnly if($dryRun) { Write-NSPConsoleLine "DRY RUN: would save the rule group to $fragmentPath." -Role Warning; return 'Merge' } [IO.File]::WriteAllText($fragmentPath,$fragment+"`r`n",(New-Object Text.UTF8Encoding($false))) Write-NSPConsoleLine "Saved the rule group to $fragmentPath. Add it inside <EventFiltering> of $other and re-apply that configuration with its owner's process." -Role Success return 'Merge' } $installArgs.Force=$true $action="Replace $other with the ScanBridge configuration" } Write-NSPConsoleLine ("Sysmon plan: {0}; configuration {1}; rule name '{2}'." -f $action,$sysmonConfig,$plan.RuleName) if($dryRun) { Write-NSPConsoleLine 'DRY RUN: Sysmon not installed or changed.' -Role Warning; return } if(-not (Read-NSPConfirm -Prompt "$action now?")) { return } Install-NSPSysmon @installArgs -Confirm:$false | Out-Null $check=Test-NSPSysmonConfig -ConfigPath $sysmonConfig -Configuration $xml if($check.InSync) { Write-NSPConsoleLine 'Sysmon is now watching the Source folders for new PDFs.' -Role Success; return 'Own' } Write-NSPConsoleLine ("Sysmon did not confirm the configuration: {0}" -f (@($check.Problems) -join '; ')) -Role Error } function Read-NSPScanBridgeTriggerMode { # Sysmon events plus a 15-minute sweep, or periodic only. Returns @{Sysmon;Minutes;DelaySeconds} or $null. param([Parameter(Mandatory)][string]$ConfigurationPath) $plan=try { Get-NSPScanBridgeWatchPlan -ConfigurationPath $ConfigurationPath } catch { Write-NSPConsoleLine $_.Exception.Message -Role Warning; $null } $installed=[bool](Get-Service -Name 'Sysmon64','Sysmon' -ErrorAction SilentlyContinue) $choices=@() if($plan -and $installed) { $choices+=[pscustomobject]@{Label='Sysmon events plus a sweep every 15 minutes';Value='Sysmon';Help=("Starts a pass {0} s after a PDF is created (route age {1} s + 30 s). Needs 'Set up the Sysmon scan trigger' done first." -f $plan.EventDelaySeconds,$plan.MaximumAgeSeconds)} } $choices+=[pscustomobject]@{Label='Periodic only';Value='Periodic';Help=$(if($installed){'No event trigger; runs on a fixed interval.'}else{'Sysmon is not installed here; runs on a fixed interval.'})} $mode=Read-NSPMenu -Choices $choices -Prompt 'When should the task run?' -CancelLabel 'Back' if($null -eq $mode) { return $null } if($mode -eq 'Sysmon') { return @{Sysmon=$true;Minutes=15;DelaySeconds=$plan.EventDelaySeconds} } $minutes=Read-NSPScanBridgeTaskInterval if($null -eq $minutes) { return $null } @{Sysmon=$false;Minutes=$minutes;DelaySeconds=$null} } |