From 91c5794502b0494627245d2d1c896ecf24562900 Mon Sep 17 00:00:00 2001 From: OfficeCom Codex Date: Sun, 26 Jul 2026 02:17:03 +0200 Subject: [PATCH] Add daily scans and Ninja burst mode --- build/build-client-package.ps1 | 1 + docs/ocsentinel-deployment.md | 13 +++++ installer/install-ocsentinel.ps1 | 14 ++++++ installer/runtime-run-ocsentinel-monitor.ps1 | 5 +- .../runtime-run-ocsentinel-scheduled.ps1 | 50 +++++++++++++++++++ installer/uninstall-ocsentinel.ps1 | 6 +++ release/stable/version.json | 8 +-- scripts/run-ocsentinel-monitor.ps1 | 5 +- scripts/run-ocsentinel-scheduled.ps1 | 50 +++++++++++++++++++ src/OCSentinelCli/OCSentinelCli.csproj | 8 +-- 10 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 installer/runtime-run-ocsentinel-scheduled.ps1 create mode 100644 scripts/run-ocsentinel-scheduled.ps1 diff --git a/build/build-client-package.ps1 b/build/build-client-package.ps1 index 04ffc2d..cf142d9 100644 --- a/build/build-client-package.ps1 +++ b/build/build-client-package.ps1 @@ -54,6 +54,7 @@ Copy-Item -Path (Join-Path $installerRoot "uninstall-ocsentinel.ps1") -Destinati Copy-Item -Path (Join-Path $installerRoot "update-ocsentinel.ps1") -Destination (Join-Path $packageRoot "scripts\update-ocsentinel.ps1") -Force Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel.ps1") -Force Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel-monitor.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel-monitor.ps1") -Force +Copy-Item -Path (Join-Path $installerRoot "runtime-run-ocsentinel-scheduled.ps1") -Destination (Join-Path $packageRoot "scripts\run-ocsentinel-scheduled.ps1") -Force Copy-Item -Path (Join-Path $repoRoot "scripts\protect-ocsentinel-secret.ps1") -Destination (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1") -Force Copy-Item -Path (Join-Path $repoRoot "config\ocsentinel-settings.example.json") -Destination (Join-Path $packageRoot "config\ocsentinel-settings.example.json") -Force diff --git a/docs/ocsentinel-deployment.md b/docs/ocsentinel-deployment.md index d020c9f..288ae42 100644 --- a/docs/ocsentinel-deployment.md +++ b/docs/ocsentinel-deployment.md @@ -30,6 +30,19 @@ powershell -ExecutionPolicy Bypass -File .\build\build-release-manifest.ps1 ` - `config\ocsentinel-settings.json` - `config\ocsentinel-client.json` +## Local Schedule And Burst Mode + +The installer creates two Windows Scheduled Tasks running as `SYSTEM`: + +- `OCSentinel Daily Scan`: runs every day at `08:00` and uploads one signed report. +- `OCSentinel Burst Check`: runs every five minutes. It performs no scan unless + the NinjaOne device custom field `ocsentinelburst` is enabled. + +Create `ocsentinelburst` as a device-level `Checkbox` custom field and allow +automation read access. Set it to `true` for a device to begin the five-minute +burst scans; clear it to stop them. The normal daily scan continues regardless +of the checkbox. + ## NinjaOne Tasks Create a PowerShell script in NinjaOne named `OCSentinel - Installieren oder aktualisieren`. diff --git a/installer/install-ocsentinel.ps1 b/installer/install-ocsentinel.ps1 index 40374b0..3ba42fb 100644 --- a/installer/install-ocsentinel.ps1 +++ b/installer/install-ocsentinel.ps1 @@ -32,6 +32,7 @@ if (Test-Path (Join-Path $packageRoot "config\ocsentinel-client.dev.example.json Copy-Item -Path (Join-Path $packageRoot "samples\ninja-vulnerability-export.example.csv") -Destination (Join-Path $samplesRoot "ninja-vulnerability-export.example.csv") -Force Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel.ps1") -Destination $scriptRoot -Force Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel-monitor.ps1") -Destination $scriptRoot -Force +Copy-Item -Path (Join-Path $packageRoot "scripts\run-ocsentinel-scheduled.ps1") -Destination $scriptRoot -Force if (Test-Path (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1")) { Copy-Item -Path (Join-Path $packageRoot "scripts\protect-ocsentinel-secret.ps1") -Destination $scriptRoot -Force } @@ -69,8 +70,21 @@ Set-ItemProperty -Path $uninstallKey -Name "QuietUninstallString" -Value $uninst Set-ItemProperty -Path $uninstallKey -Name "NoModify" -Value 1 -Type DWord Set-ItemProperty -Path $uninstallKey -Name "NoRepair" -Value 1 -Type DWord +$scheduledScript = Join-Path $scriptRoot "run-ocsentinel-scheduled.ps1" +$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest +$taskSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Minutes 30) -MultipleInstances IgnoreNew + +$dailyAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scheduledScript`" -Kind daily" -WorkingDirectory $scriptRoot +$dailyTrigger = New-ScheduledTaskTrigger -Daily -At 08:00 +Register-ScheduledTask -TaskName "OCSentinel Daily Scan" -Action $dailyAction -Trigger $dailyTrigger -Principal $taskPrincipal -Settings $taskSettings -Description "OfficeCom Sentinel daily signed scan and upload." -Force | Out-Null + +$burstAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scheduledScript`" -Kind burst" -WorkingDirectory $scriptRoot +$burstTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(2) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 3650) +Register-ScheduledTask -TaskName "OCSentinel Burst Check" -Action $burstAction -Trigger $burstTrigger -Principal $taskPrincipal -Settings $taskSettings -Description "OfficeCom Sentinel burst check; scans only when Ninja field ocsentinelburst is enabled." -Force | Out-Null + Write-Host "Installation complete." Write-Host "Main path: $installRoot" Write-Host "Runner: $(Join-Path $scriptRoot 'run-ocsentinel.ps1')" Write-Host "Monitor: $(Join-Path $scriptRoot 'run-ocsentinel-monitor.ps1')" Write-Host "Updater: $(Join-Path $scriptRoot 'update-ocsentinel.ps1')" +Write-Host "Schedule: Daily scan at 08:00; burst check every 5 minutes." diff --git a/installer/runtime-run-ocsentinel-monitor.ps1 b/installer/runtime-run-ocsentinel-monitor.ps1 index 760add7..0186a54 100644 --- a/installer/runtime-run-ocsentinel-monitor.ps1 +++ b/installer/runtime-run-ocsentinel-monitor.ps1 @@ -9,6 +9,7 @@ param( [string]$UploadMode = "auto", [string]$VulnerabilityCsvPath = "", [string]$MirrorRoot = "", + [switch]$SuppressTriggerExit, [ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")] [string]$Mode = "status" ) @@ -227,8 +228,8 @@ Write-Host "Total CVEs: $totalCves" Write-Host "Report: $outputFullPath" Write-Host "Runner exit code: $runnerExitCode" -if ($monitorTriggered) { +if ($monitorTriggered -and -not $SuppressTriggerExit) { exit 1 } -exit 0 +exit $runnerExitCode diff --git a/installer/runtime-run-ocsentinel-scheduled.ps1 b/installer/runtime-run-ocsentinel-scheduled.ps1 new file mode 100644 index 0000000..f48aecb --- /dev/null +++ b/installer/runtime-run-ocsentinel-scheduled.ps1 @@ -0,0 +1,50 @@ +[CmdletBinding()] +param( + [ValidateSet("daily", "burst")] + [string]$Kind = "daily" +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$monitorScript = Join-Path $scriptDir "run-ocsentinel-monitor.ps1" +$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat" +$mutexName = "Global\OfficeComSentinelScan" + +function Get-NinjaBurstEnabled { + if (Get-Command -Name "Get-NinjaProperty" -ErrorAction SilentlyContinue) { + return [bool](Get-NinjaProperty -Name "ocsentinelburst" -Type "Checkbox") + } + + if (Get-Command -Name "Ninja-Property-Get" -ErrorAction SilentlyContinue) { + $value = Ninja-Property-Get -Name "ocsentinelburst" + return [string]$value -match "^(1|true|yes)$" + } + + Write-Warning "Ninja custom-field reader is unavailable; burst scan skipped." + return $false +} + +if ($Kind -eq "burst" -and -not (Get-NinjaBurstEnabled)) { + Write-Host "OfficeCom Sentinel burst check: disabled." + exit 0 +} + +$createdNew = $false +$mutex = [Threading.Mutex]::new($false, $mutexName, [ref]$createdNew) +try { + if (-not $mutex.WaitOne(0)) { + Write-Host "OfficeCom Sentinel scan skipped: another scan is already running." + exit 0 + } + + Write-Host "OfficeCom Sentinel scheduled $Kind scan started." + & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -UploadMode required -SecretPath $secretPath -SuppressTriggerExit + exit $LASTEXITCODE +} +finally { + if ($null -ne $mutex) { + try { $mutex.ReleaseMutex() } catch { } + $mutex.Dispose() + } +} diff --git a/installer/uninstall-ocsentinel.ps1 b/installer/uninstall-ocsentinel.ps1 index ab9667d..48dcdda 100644 --- a/installer/uninstall-ocsentinel.ps1 +++ b/installer/uninstall-ocsentinel.ps1 @@ -5,6 +5,12 @@ $ErrorActionPreference = "Stop" $installRoot = Join-Path ${env:ProgramFiles} "OCSentinel" $uninstallKey = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\OCSentinel" +foreach ($taskName in @("OCSentinel Daily Scan", "OCSentinel Burst Check")) { + if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) { + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false + } +} + if (Test-Path $uninstallKey) { Remove-Item -Path $uninstallKey -Force -Recurse } diff --git a/release/stable/version.json b/release/stable/version.json index 2acb3d8..29a1d8f 100644 --- a/release/stable/version.json +++ b/release/stable/version.json @@ -1,8 +1,8 @@ { "channel": "stable", - "version": "1.2.11", - "publishedAtUtc": "2026-07-25T19:17:43.7567344Z", - "artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.11/OCSentinelClient-win-x64.zip", - "sha256": "4a5415ce281f444a987a3fd72980c159afaa37787bbaa396f42d2c496736e5e1", + "version": "1.3.0", + "publishedAtUtc": "2026-07-26T00:16:43.0355921Z", + "artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.3.0/OCSentinelClient-win-x64.zip", + "sha256": "35dfc6e1022da0e7c4ae26d656c6c588365fd152deb5b29c1ff2d5ea95c8ac56", "minUpdaterVersion": "1.0.0" } diff --git a/scripts/run-ocsentinel-monitor.ps1 b/scripts/run-ocsentinel-monitor.ps1 index d1b447e..58b838d 100644 --- a/scripts/run-ocsentinel-monitor.ps1 +++ b/scripts/run-ocsentinel-monitor.ps1 @@ -9,6 +9,7 @@ param( [string]$UploadMode = "auto", [string]$VulnerabilityCsvPath = "", [string]$MirrorRoot = "", + [switch]$SuppressTriggerExit, [ValidateSet("status", "attack-only", "cve-critical", "attack-plus-cve")] [string]$Mode = "status" ) @@ -227,8 +228,8 @@ Write-Host "Total CVEs: $totalCves" Write-Host "Report: $outputFullPath" Write-Host "Runner exit code: $runnerExitCode" -if ($monitorTriggered) { +if ($monitorTriggered -and -not $SuppressTriggerExit) { exit 1 } -exit 0 +exit $runnerExitCode diff --git a/scripts/run-ocsentinel-scheduled.ps1 b/scripts/run-ocsentinel-scheduled.ps1 new file mode 100644 index 0000000..f48aecb --- /dev/null +++ b/scripts/run-ocsentinel-scheduled.ps1 @@ -0,0 +1,50 @@ +[CmdletBinding()] +param( + [ValidateSet("daily", "burst")] + [string]$Kind = "daily" +) + +$ErrorActionPreference = "Stop" + +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$monitorScript = Join-Path $scriptDir "run-ocsentinel-monitor.ps1" +$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat" +$mutexName = "Global\OfficeComSentinelScan" + +function Get-NinjaBurstEnabled { + if (Get-Command -Name "Get-NinjaProperty" -ErrorAction SilentlyContinue) { + return [bool](Get-NinjaProperty -Name "ocsentinelburst" -Type "Checkbox") + } + + if (Get-Command -Name "Ninja-Property-Get" -ErrorAction SilentlyContinue) { + $value = Ninja-Property-Get -Name "ocsentinelburst" + return [string]$value -match "^(1|true|yes)$" + } + + Write-Warning "Ninja custom-field reader is unavailable; burst scan skipped." + return $false +} + +if ($Kind -eq "burst" -and -not (Get-NinjaBurstEnabled)) { + Write-Host "OfficeCom Sentinel burst check: disabled." + exit 0 +} + +$createdNew = $false +$mutex = [Threading.Mutex]::new($false, $mutexName, [ref]$createdNew) +try { + if (-not $mutex.WaitOne(0)) { + Write-Host "OfficeCom Sentinel scan skipped: another scan is already running." + exit 0 + } + + Write-Host "OfficeCom Sentinel scheduled $Kind scan started." + & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -UploadMode required -SecretPath $secretPath -SuppressTriggerExit + exit $LASTEXITCODE +} +finally { + if ($null -ne $mutex) { + try { $mutex.ReleaseMutex() } catch { } + $mutex.Dispose() + } +} diff --git a/src/OCSentinelCli/OCSentinelCli.csproj b/src/OCSentinelCli/OCSentinelCli.csproj index 0668ab5..e7488e1 100644 --- a/src/OCSentinelCli/OCSentinelCli.csproj +++ b/src/OCSentinelCli/OCSentinelCli.csproj @@ -9,10 +9,10 @@ OCSentinelCli OfficeCom Sentinel OfficeCom - 1.2.11 - 1.2.11.0 - 1.2.11.0 - 1.2.11 + 1.3.0 + 1.3.0.0 + 1.3.0.0 + 1.3.0