param( [int]$LookbackDays = 7, [int]$TopCount = 10, [string]$OutputPath = ".\reports\ocsentinel-summary.json", [string]$ConfigPath = ".\config\ocsentinel-settings.example.json", [string]$ClientConfigPath = ".\config\ocsentinel-client.json", [string]$SecretPath = "", [ValidateSet("disabled", "auto", "required")] [string]$UploadMode = "auto", [string]$VulnerabilityCsvPath = "", [string]$MirrorRoot = "", [switch]$SuppressTriggerExit, [ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")] [string]$Mode = "status" ) $ErrorActionPreference = "Stop" $repoRoot = Split-Path -Parent $PSScriptRoot $runnerScript = Join-Path $repoRoot "scripts\run-ocsentinel.ps1" $outputFullPath = [System.IO.Path]::GetFullPath((Join-Path $repoRoot $OutputPath)) $script:NinjaFieldBackend = $null $script:NinjaCliPath = "C:\ProgramData\NinjaRMMAgent\ninjarmm-cli.exe" function Resolve-PathLike { param( [Parameter(Mandatory)] [string]$PathValue, [Parameter(Mandatory)] [string]$BasePath ) if ([string]::IsNullOrWhiteSpace($PathValue)) { return $PathValue } if ([System.IO.Path]::IsPathRooted($PathValue) -or $PathValue.StartsWith("\\")) { return [System.IO.Path]::GetFullPath($PathValue) } return [System.IO.Path]::GetFullPath((Join-Path $BasePath $PathValue)) } function Initialize-NinjaFieldWriter { if ($null -ne $script:NinjaFieldBackend) { return } if (Get-Command -Name "Set-NinjaProperty" -ErrorAction SilentlyContinue) { $script:NinjaFieldBackend = "powershell-modern" return } if (Get-Command -Name "Ninja-Property-Set" -ErrorAction SilentlyContinue) { $script:NinjaFieldBackend = "powershell-legacy" return } if (Test-Path $script:NinjaCliPath) { $script:NinjaFieldBackend = "cli" return } $script:NinjaFieldBackend = "none" } function Set-NinjaCustomFieldValue { param( [Parameter(Mandatory)] [string]$Name, [AllowEmptyString()] [object]$Value, [Parameter(Mandatory)] [string]$Type ) Initialize-NinjaFieldWriter switch ($script:NinjaFieldBackend) { "powershell-modern" { Set-NinjaProperty -Name $Name -Value $Value -Type $Type -Force | Out-Null return $true } "powershell-legacy" { Ninja-Property-Set -Name $Name -Value $Value | Out-Null return $true } "cli" { & $script:NinjaCliPath set $Name $Value | Out-Null return $LASTEXITCODE -eq 0 } default { return $false } } } function Publish-NinjaCustomFields { param( [Parameter(Mandatory)] [pscustomobject]$Report, [Parameter(Mandatory)] [string]$Mode, [Parameter(Mandatory)] [bool]$Triggered, [Parameter(Mandatory)] [string]$Reason ) Initialize-NinjaFieldWriter if ($script:NinjaFieldBackend -eq "none") { Write-Host "Ninja custom fields: skipped (Ninja field writer not available)." return } $generatedAtUtc = "" if ($Report.GeneratedAtLocal) { try { $generatedAtUtc = ([DateTimeOffset]$Report.GeneratedAtLocal).ToUniversalTime().ToString("o") } catch { $generatedAtUtc = [string]$Report.GeneratedAtLocal } } $fieldValues = @( [pscustomobject]@{ Name = "ocsentinelstatus"; Type = "Text"; Value = [string]$Report.AlertState } [pscustomobject]@{ Name = "ocsentinelreason"; Type = "Text"; Value = $Reason } [pscustomobject]@{ Name = "ocsentinelbasestatus"; Type = "Text"; Value = [string]$Report.BaseAlertState } [pscustomobject]@{ Name = "ocsentinelevents"; Type = "Integer"; Value = [int]$Report.TotalEvents } [pscustomobject]@{ Name = "ocsentineluniqueips"; Type = "Integer"; Value = [int]$Report.UniqueIpCount } [pscustomobject]@{ Name = "ocsentinelcvecritical"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.CriticalCount } [pscustomobject]@{ Name = "ocsentinelcvetotal"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.TotalCount } [pscustomobject]@{ Name = "ocsentinelmode"; Type = "Text"; Value = $Mode } [pscustomobject]@{ Name = "ocsentineltriggered"; Type = "Checkbox"; Value = $Triggered } [pscustomobject]@{ Name = "ocsentinellastscanutc"; Type = "DateTime"; Value = $generatedAtUtc } ) $updated = 0 foreach ($entry in $fieldValues) { try { if (Set-NinjaCustomFieldValue -Name $entry.Name -Value $entry.Value -Type $entry.Type) { $updated++ } } catch { Write-Warning "Failed to set Ninja custom field '$($entry.Name)': $($_.Exception.Message)" } } Write-Host "Ninja custom fields: updated $updated field(s) via $script:NinjaFieldBackend." } $runnerArgs = @( "-ExecutionPolicy", "Bypass", "-File", $runnerScript, "-LookbackDays", $LookbackDays, "-TopCount", $TopCount, "-OutputPath", $OutputPath, "-ConfigPath", $ConfigPath, "-ClientConfigPath", $ClientConfigPath, "-UploadMode", $UploadMode ) if (-not [string]::IsNullOrWhiteSpace($SecretPath)) { $runnerArgs += @("-SecretPath", $SecretPath) } if (-not [string]::IsNullOrWhiteSpace($VulnerabilityCsvPath)) { $runnerArgs += @("-VulnerabilityCsvPath", $VulnerabilityCsvPath) } if (-not [string]::IsNullOrWhiteSpace($MirrorRoot)) { $runnerArgs += @("-MirrorRoot", (Resolve-PathLike -PathValue $MirrorRoot -BasePath $repoRoot)) } $null = & powershell @runnerArgs $runnerExitCode = $LASTEXITCODE if (-not (Test-Path $outputFullPath)) { throw "Expected report file was not created: $outputFullPath" } $report = Get-Content $outputFullPath -Raw | ConvertFrom-Json $status = [string]$report.AlertState $baseStatus = [string]$report.BaseAlertState $events = [int]$report.TotalEvents $uniqueIps = [int]$report.UniqueIpCount $criticalCves = [int]$report.VulnerabilityCorrelation.CriticalCount $totalCves = [int]$report.VulnerabilityCorrelation.TotalCount $monitorTriggered = $false $monitorReason = "" switch ($Mode) { "status" { $monitorTriggered = $status -ne "ok" $monitorReason = "Final status is $status. $($report.AlertReason)" } "attack-only" { $monitorTriggered = $baseStatus -ne "ok" $monitorReason = "Base attack status is $baseStatus. $($report.BaseAlertReason)" } "cve-critical" { $monitorTriggered = $criticalCves -gt 0 $monitorReason = "Critical/high CVE count is $criticalCves out of total CVEs $totalCves." } "attack-plus-cve" { $monitorTriggered = ($events -gt 0 -and $criticalCves -gt 0) $monitorReason = "Attack events=$events and critical/high CVEs=$criticalCves." } } Publish-NinjaCustomFields -Report $report -Mode $Mode -Triggered $monitorTriggered -Reason $monitorReason Write-Host "" Write-Host "OfficeCom Sentinel monitor mode: $Mode" Write-Host "Triggered: $monitorTriggered" Write-Host "Reason: $monitorReason" Write-Host "Status: $status" Write-Host "Base status: $baseStatus" Write-Host "Events: $events" Write-Host "Unique IPs: $uniqueIps" Write-Host "Critical/High CVEs: $criticalCves" Write-Host "Total CVEs: $totalCves" Write-Host "Report: $outputFullPath" Write-Host "Runner exit code: $runnerExitCode" if ($monitorTriggered -and -not $SuppressTriggerExit) { exit 1 } exit $runnerExitCode