param( [int]$LookbackDays = 7, [int]$TopCount = 10, [string]$OutputPath = "..\reports\ocsentinel-summary.json", [string]$ConfigPath = "..\config\ocsentinel-settings.json", [string]$ClientConfigPath = "..\config\ocsentinel-client.json", [string]$SecretPath = "", [ValidateSet("disabled", "auto", "required")] [string]$UploadMode = "auto", [string]$VulnerabilityCsvPath = "", [string]$MirrorRoot = "", [ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")] [string]$Mode = "status" ) $ErrorActionPreference = "Stop" $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $runnerScript = Join-Path $scriptDir "run-ocsentinel.ps1" $outputFullPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $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 "Ninja-Property-Set" -ErrorAction SilentlyContinue) { $script:NinjaFieldBackend = "powershell" return } if (Test-Path $script:NinjaCliPath) { $script:NinjaFieldBackend = "cli" return } $script:NinjaFieldBackend = "none" } function Set-NinjaCustomFieldValue { param( [Parameter(Mandatory)] [string]$Name, [AllowEmptyString()] [string]$Value ) Initialize-NinjaFieldWriter switch ($script:NinjaFieldBackend) { "powershell" { Ninja-Property-Set $Name $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 = [ordered]@{ "ocsentinelstatus" = [string]$Report.AlertState "ocsentinelreason" = $Reason "ocsentinelbasestatus" = [string]$Report.BaseAlertState "ocsentinelevents" = [string]([int]$Report.TotalEvents) "ocsentineluniqueips" = [string]([int]$Report.UniqueIpCount) "ocsentinelcvecritical" = [string]([int]$Report.VulnerabilityCorrelation.CriticalCount) "ocsentinelcvetotal" = [string]([int]$Report.VulnerabilityCorrelation.TotalCount) "ocsentinelmode" = $Mode "ocsentineltriggered" = $Triggered.ToString().ToLowerInvariant() "ocsentinellastscanutc" = $generatedAtUtc } $updated = 0 foreach ($entry in $fieldValues.GetEnumerator()) { try { if (Set-NinjaCustomFieldValue -Name $entry.Key -Value $entry.Value) { $updated++ } } catch { Write-Warning "Failed to set Ninja custom field '$($entry.Key)': $($_.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 $scriptDir)) } $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) { exit 1 } exit 0