16 Commits

Author SHA1 Message Date
OfficeCom Codex
7431c656d3 Use documented NinjaOne custom field commands
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 18s
2026-07-25 21:17:59 +02:00
OfficeCom Codex
49b0e3025d Fix Ninja client configuration parsing
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 18s
2026-07-25 21:14:50 +02:00
OfficeCom Codex
aefec51581 Fix NinjaOne environment variable lookup
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 19s
2026-07-25 21:10:24 +02:00
OfficeCom Codex
7555e92aac Add one-time NinjaOne installation script
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 25s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 19s
2026-07-25 21:01:02 +02:00
OfficeCom Codex
b97b554f84 Add NinjaOne client configuration and upload test
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 25s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 19s
2026-07-25 20:58:54 +02:00
OfficeCom Codex
77d7eaa0b5 Read NinjaOne rollout variables from environment
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 19s
2026-07-25 18:53:34 +02:00
OfficeCom Codex
f91f45ad92 Prepare automated release validation
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 17s
2026-07-25 10:25:07 +02:00
OfficeCom Codex
c27b53ea0d Use repository secret for Gitea releases
Some checks failed
OfficeCom Sentinel Client / build-client-windows (push) Has been cancelled
OfficeCom Sentinel Client / validate-client (push) Has been cancelled
2026-07-25 10:24:56 +02:00
OfficeCom Codex
f9d7647046 Detect Gitea release tags by reference name
Some checks failed
OfficeCom Sentinel Client / build-client-windows (push) Failing after 18s
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
2026-07-25 10:22:43 +02:00
OfficeCom Codex
d37fba137e Run Gitea release pipeline from tag environment
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 18s
2026-07-25 10:20:55 +02:00
OfficeCom Codex
e73d79b520 Publish stable manifest for v1.2.6
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 22s
OfficeCom Sentinel Client / build-client-windows (push) Has been skipped
2026-07-25 10:16:52 +02:00
OfficeCom Codex
b0e3e7dc74 Preserve upload configuration during scan and upload
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 23s
OfficeCom Sentinel Client / build-client-windows (push) Failing after 19s
2026-07-25 10:15:23 +02:00
OfficeCom Codex
290033680b Publish release assets from tag workflow
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 25s
OfficeCom Sentinel Client / build-client-windows (push) Has been skipped
2026-07-25 03:08:12 +02:00
OfficeCom Codex
3ea0baa147 Build Windows client packages on Linux runner
All checks were successful
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Has been skipped
2026-07-25 03:06:31 +02:00
OfficeCom Codex
e01aa3dce3 Target Windows Gitea runner label
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 25s
OfficeCom Sentinel Client / build-client-windows (push) Has been cancelled
2026-07-25 02:43:12 +02:00
OfficeCom Codex
8bc1d78fc9 Publish stable manifest for v1.2.5
Some checks failed
OfficeCom Sentinel Client / validate-client (push) Successful in 24s
OfficeCom Sentinel Client / build-client-windows (push) Has been cancelled
2026-07-25 02:40:40 +02:00
10 changed files with 299 additions and 53 deletions

View File

@@ -33,8 +33,9 @@ jobs:
build-client-windows: build-client-windows:
needs: validate-client needs: validate-client
if: startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.build_windows == 'true') # .NET can publish a self-contained Windows x64 client from Linux.
runs-on: windows # This keeps releases independent of a Windows Gitea runner.
runs-on: ubuntu-22.04
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -50,10 +51,14 @@ jobs:
./build/build-client-package.ps1 ./build/build-client-package.ps1
- name: Build release manifest for tags - name: Build release manifest for tags
if: startsWith(github.ref, 'refs/tags/v')
shell: pwsh shell: pwsh
run: | run: |
$tag = "${{ github.ref_name }}" $ref = if ($env:GITHUB_REF) { $env:GITHUB_REF } else { $env:GITEA_REF }
$tag = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { Split-Path -Leaf $ref }
if ($tag -notlike "v*") {
Write-Host "Not a version tag; skipping release manifest."
exit 0
}
$artifactUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/$tag/OCSentinelClient-win-x64.zip" $artifactUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/$tag/OCSentinelClient-win-x64.zip"
./build/build-release-manifest.ps1 -ArtifactUrl $artifactUrl ./build/build-release-manifest.ps1 -ArtifactUrl $artifactUrl
@@ -66,3 +71,41 @@ jobs:
artifacts/OCSentinelClient-win-x64.zip.sha256 artifacts/OCSentinelClient-win-x64.zip.sha256
artifacts/version.json artifacts/version.json
if-no-files-found: warn if-no-files-found: warn
- name: Publish Gitea release assets
shell: pwsh
env:
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
run: |
$ref = if ($env:GITHUB_REF) { $env:GITHUB_REF } else { $env:GITEA_REF }
$tag = if ($env:GITHUB_REF_NAME) { $env:GITHUB_REF_NAME } else { Split-Path -Leaf $ref }
if ($tag -notlike "v*") {
Write-Host "Not a version tag; skipping Gitea release publication."
exit 0
}
$headers = @{ Authorization = "token $env:GITEA_TOKEN" }
$repository = if ($env:GITEA_REPOSITORY) { $env:GITEA_REPOSITORY } else { $env:GITHUB_REPOSITORY }
$serverUrl = if ($env:GITEA_SERVER_URL) { $env:GITEA_SERVER_URL } else { $env:GITHUB_SERVER_URL }
if ([string]::IsNullOrWhiteSpace($env:GITEA_TOKEN) -or [string]::IsNullOrWhiteSpace($repository) -or [string]::IsNullOrWhiteSpace($serverUrl)) {
throw "Gitea release environment is incomplete. Expected GITEA_TOKEN, repository, and server URL."
}
$baseUrl = "$serverUrl/api/v1/repos/$repository"
$releaseBody = @{
tag_name = $tag
target_commitish = "${{ github.sha }}"
name = "OfficeCom Sentinel $tag"
body = "Automated OfficeCom Sentinel client release."
} | ConvertTo-Json
try {
$release = Invoke-RestMethod -Method Get -Headers $headers -Uri "$baseUrl/releases/tags/$tag"
}
catch {
$release = Invoke-RestMethod -Method Post -Headers $headers -ContentType "application/json" -Body $releaseBody -Uri "$baseUrl/releases"
}
foreach ($file in @("artifacts/OCSentinelClient-win-x64.zip", "artifacts/OCSentinelClient-win-x64.zip.sha256", "artifacts/version.json")) {
$assetName = [System.IO.Path]::GetFileName($file)
Invoke-RestMethod -Method Post -Headers $headers -InFile $file -ContentType "application/octet-stream" -Uri "$baseUrl/releases/$($release.id)/assets?name=$assetName" | Out-Null
}

View File

@@ -46,8 +46,13 @@ function Initialize-NinjaFieldWriter {
return return
} }
if (Get-Command -Name "Set-NinjaProperty" -ErrorAction SilentlyContinue) {
$script:NinjaFieldBackend = "powershell-modern"
return
}
if (Get-Command -Name "Ninja-Property-Set" -ErrorAction SilentlyContinue) { if (Get-Command -Name "Ninja-Property-Set" -ErrorAction SilentlyContinue) {
$script:NinjaFieldBackend = "powershell" $script:NinjaFieldBackend = "powershell-legacy"
return return
} }
@@ -64,14 +69,20 @@ function Set-NinjaCustomFieldValue {
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[AllowEmptyString()] [AllowEmptyString()]
[string]$Value [object]$Value,
[Parameter(Mandatory)]
[string]$Type
) )
Initialize-NinjaFieldWriter Initialize-NinjaFieldWriter
switch ($script:NinjaFieldBackend) { switch ($script:NinjaFieldBackend) {
"powershell" { "powershell-modern" {
Ninja-Property-Set $Name $Value | Out-Null 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 return $true
} }
"cli" { "cli" {
@@ -112,28 +123,28 @@ function Publish-NinjaCustomFields {
} }
} }
$fieldValues = [ordered]@{ $fieldValues = @(
"ocsentinelstatus" = [string]$Report.AlertState [pscustomobject]@{ Name = "ocsentinelstatus"; Type = "Text"; Value = [string]$Report.AlertState }
"ocsentinelreason" = $Reason [pscustomobject]@{ Name = "ocsentinelreason"; Type = "Text"; Value = $Reason }
"ocsentinelbasestatus" = [string]$Report.BaseAlertState [pscustomobject]@{ Name = "ocsentinelbasestatus"; Type = "Text"; Value = [string]$Report.BaseAlertState }
"ocsentinelevents" = [string]([int]$Report.TotalEvents) [pscustomobject]@{ Name = "ocsentinelevents"; Type = "Integer"; Value = [int]$Report.TotalEvents }
"ocsentineluniqueips" = [string]([int]$Report.UniqueIpCount) [pscustomobject]@{ Name = "ocsentineluniqueips"; Type = "Integer"; Value = [int]$Report.UniqueIpCount }
"ocsentinelcvecritical" = [string]([int]$Report.VulnerabilityCorrelation.CriticalCount) [pscustomobject]@{ Name = "ocsentinelcvecritical"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.CriticalCount }
"ocsentinelcvetotal" = [string]([int]$Report.VulnerabilityCorrelation.TotalCount) [pscustomobject]@{ Name = "ocsentinelcvetotal"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.TotalCount }
"ocsentinelmode" = $Mode [pscustomobject]@{ Name = "ocsentinelmode"; Type = "Text"; Value = $Mode }
"ocsentineltriggered" = $Triggered.ToString().ToLowerInvariant() [pscustomobject]@{ Name = "ocsentineltriggered"; Type = "Checkbox"; Value = $Triggered }
"ocsentinellastscanutc" = $generatedAtUtc [pscustomobject]@{ Name = "ocsentinellastscanutc"; Type = "DateTime"; Value = $generatedAtUtc }
} )
$updated = 0 $updated = 0
foreach ($entry in $fieldValues.GetEnumerator()) { foreach ($entry in $fieldValues) {
try { try {
if (Set-NinjaCustomFieldValue -Name $entry.Key -Value $entry.Value) { if (Set-NinjaCustomFieldValue -Name $entry.Name -Value $entry.Value -Type $entry.Type) {
$updated++ $updated++
} }
} }
catch { catch {
Write-Warning "Failed to set Ninja custom field '$($entry.Key)': $($_.Exception.Message)" Write-Warning "Failed to set Ninja custom field '$($entry.Name)': $($_.Exception.Message)"
} }
} }

View File

@@ -1,8 +1,8 @@
{ {
"channel": "stable", "channel": "stable",
"version": "1.2.4", "version": "1.2.11",
"publishedAtUtc": "2026-07-25T00:00:00Z", "publishedAtUtc": "2026-07-25T19:17:43.7567344Z",
"artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.4/OCSentinelClient-win-x64.zip", "artifactUrl": "https://gitea.officecom.cloud/officecom/oc-sentinel/releases/download/v1.2.11/OCSentinelClient-win-x64.zip",
"sha256": "eab8217bdda725e4969849d881fa6c9d892b6c5658aaf153a783b1d0f611f481", "sha256": "4a5415ce281f444a987a3fd72980c159afaa37787bbaa396f42d2c496736e5e1",
"minUpdaterVersion": "1.0.0" "minUpdaterVersion": "1.0.0"
} }

View File

@@ -18,6 +18,20 @@ $clientConfigPath = Join-Path $installRoot "config\ocsentinel-client.json"
$secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1" $secretScriptPath = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat" $secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
# NinjaOne script variables are exposed as process environment variables.
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) {
$WebhookUrl = $env:WebhookUrl
}
if ([string]::IsNullOrWhiteSpace($SecretValue)) {
$SecretValue = $env:SecretValue
}
$runInitialScan = $RunInitialStatusScan.IsPresent
if (-not $runInitialScan -and -not [string]::IsNullOrWhiteSpace($env:RunInitialStatusScan)) {
$runInitialScan = $env:RunInitialStatusScan -match '^(1|true|yes|on)$'
}
function Assert-ArtifactSignature { function Assert-ArtifactSignature {
param([Parameter(Mandatory)][string]$ExecutablePath) param([Parameter(Mandatory)][string]$ExecutablePath)
@@ -108,7 +122,7 @@ if (-not [string]::IsNullOrWhiteSpace($SecretValue)) {
} }
} }
if ($RunInitialStatusScan) { if ($runInitialScan) {
if (-not (Test-Path -LiteralPath $monitorPath)) { if (-not (Test-Path -LiteralPath $monitorPath)) {
throw "OCSentinel was installed, but the monitor script is missing." throw "OCSentinel was installed, but the monitor script is missing."
} }

View File

@@ -0,0 +1,62 @@
[CmdletBinding()]
param(
[string]$WebhookUrl = "",
[string]$SecretValue = ""
)
$ErrorActionPreference = "Stop"
function Get-NinjaValue {
param([Parameter(Mandatory)][string]$Name)
$value = [Environment]::GetEnvironmentVariable($Name, "Process")
if ($null -eq $value) {
return ""
}
return $value.Trim()
}
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) {
$WebhookUrl = Get-NinjaValue -Name "webhookurl"
}
if ([string]::IsNullOrWhiteSpace($SecretValue)) {
$SecretValue = Get-NinjaValue -Name "secretvalue"
}
if ([string]::IsNullOrWhiteSpace($WebhookUrl) -or [string]::IsNullOrWhiteSpace($SecretValue)) {
throw "WebhookUrl and SecretValue must be supplied as NinjaOne script variables."
}
$installRoot = Join-Path $env:ProgramFiles "OCSentinel"
$configPath = Join-Path $installRoot "config\ocsentinel-client.json"
$secretScript = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
$monitorScript = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
foreach ($path in @($configPath, $secretScript, $monitorScript)) {
if (-not (Test-Path -LiteralPath $path)) {
throw "OCSentinel installation is incomplete. Missing: $path"
}
}
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
$config.n8nWebhookUrl = $WebhookUrl
$config.environment = "production"
$config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8
Write-Host "OCSentinel upload endpoint configured."
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScript -SecretValue $SecretValue
if ($LASTEXITCODE -ne 0) {
throw "Writing the protected upload secret failed with code $LASTEXITCODE"
}
Write-Host "Running signed OCSentinel test scan and upload."
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript `
-Mode status `
-ClientConfigPath $configPath `
-SecretPath $secretPath `
-UploadMode required
exit $LASTEXITCODE

View File

@@ -0,0 +1,74 @@
[CmdletBinding()]
param(
[string]$ManifestUrl = "https://gitea.officecom.cloud/officecom/oc-sentinel/raw/main/release/stable/version.json",
[string]$WebhookUrl = "",
[string]$SecretValue = ""
)
$ErrorActionPreference = "Stop"
$ProgressPreference = "SilentlyContinue"
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Get-NinjaValue {
param([Parameter(Mandatory)][string]$Name)
$value = [Environment]::GetEnvironmentVariable($Name, "Process")
if ($null -eq $value) {
return ""
}
return $value.Trim()
}
if ([string]::IsNullOrWhiteSpace($WebhookUrl)) { $WebhookUrl = Get-NinjaValue -Name "webhookurl" }
if ([string]::IsNullOrWhiteSpace($SecretValue)) { $SecretValue = Get-NinjaValue -Name "secretvalue" }
if ([string]::IsNullOrWhiteSpace($WebhookUrl) -or [string]::IsNullOrWhiteSpace($SecretValue)) {
throw "WebhookUrl and SecretValue must be set as NinjaOne script variables."
}
$manifest = Invoke-RestMethod -Method Get -Uri $ManifestUrl -TimeoutSec 60
if ([string]::IsNullOrWhiteSpace($manifest.artifactUrl) -or [string]::IsNullOrWhiteSpace($manifest.sha256)) {
throw "The release manifest is incomplete."
}
$downloadRoot = Join-Path $env:ProgramData ("OCSentinel\\install-" + [Guid]::NewGuid().ToString("N"))
$zipPath = Join-Path $downloadRoot "OCSentinelClient.zip"
$extractPath = Join-Path $downloadRoot "payload"
try {
New-Item -ItemType Directory -Force -Path $extractPath | Out-Null
Write-Host "Downloading OCSentinel $($manifest.version)."
Invoke-WebRequest -Uri $manifest.artifactUrl -OutFile $zipPath -TimeoutSec 300
$actualHash = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($actualHash -ne ([string]$manifest.sha256).ToLowerInvariant()) {
throw "Release package SHA-256 validation failed."
}
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force
$installer = Get-ChildItem -Path $extractPath -Recurse -Filter "install-ocsentinel.ps1" | Select-Object -First 1
if ($null -eq $installer) { throw "The release package does not contain the installer." }
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $installer.FullName
if ($LASTEXITCODE -ne 0) { throw "Installer failed with code $LASTEXITCODE" }
}
finally {
if (Test-Path -LiteralPath $downloadRoot) { Remove-Item -LiteralPath $downloadRoot -Recurse -Force }
}
$installRoot = Join-Path $env:ProgramFiles "OCSentinel"
$configPath = Join-Path $installRoot "config\ocsentinel-client.json"
$secretScript = Join-Path $installRoot "scripts\protect-ocsentinel-secret.ps1"
$monitorScript = Join-Path $installRoot "scripts\run-ocsentinel-monitor.ps1"
$secretPath = "C:\ProgramData\OCSentinel\secrets\ocsentinel-upload-secret.dat"
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json
$config.n8nWebhookUrl = $WebhookUrl
$config.environment = "production"
$config | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath $configPath -Encoding UTF8
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $secretScript -SecretValue $SecretValue
if ($LASTEXITCODE -ne 0) { throw "Writing the protected upload secret failed with code $LASTEXITCODE" }
Write-Host "Running initial signed scan and upload."
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $monitorScript -Mode status -ClientConfigPath $configPath -SecretPath $secretPath -UploadMode required
exit $LASTEXITCODE

View File

@@ -46,8 +46,13 @@ function Initialize-NinjaFieldWriter {
return return
} }
if (Get-Command -Name "Set-NinjaProperty" -ErrorAction SilentlyContinue) {
$script:NinjaFieldBackend = "powershell-modern"
return
}
if (Get-Command -Name "Ninja-Property-Set" -ErrorAction SilentlyContinue) { if (Get-Command -Name "Ninja-Property-Set" -ErrorAction SilentlyContinue) {
$script:NinjaFieldBackend = "powershell" $script:NinjaFieldBackend = "powershell-legacy"
return return
} }
@@ -64,14 +69,20 @@ function Set-NinjaCustomFieldValue {
[Parameter(Mandatory)] [Parameter(Mandatory)]
[string]$Name, [string]$Name,
[AllowEmptyString()] [AllowEmptyString()]
[string]$Value [object]$Value,
[Parameter(Mandatory)]
[string]$Type
) )
Initialize-NinjaFieldWriter Initialize-NinjaFieldWriter
switch ($script:NinjaFieldBackend) { switch ($script:NinjaFieldBackend) {
"powershell" { "powershell-modern" {
Ninja-Property-Set $Name $Value | Out-Null 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 return $true
} }
"cli" { "cli" {
@@ -112,28 +123,28 @@ function Publish-NinjaCustomFields {
} }
} }
$fieldValues = [ordered]@{ $fieldValues = @(
"ocsentinelstatus" = [string]$Report.AlertState [pscustomobject]@{ Name = "ocsentinelstatus"; Type = "Text"; Value = [string]$Report.AlertState }
"ocsentinelreason" = $Reason [pscustomobject]@{ Name = "ocsentinelreason"; Type = "Text"; Value = $Reason }
"ocsentinelbasestatus" = [string]$Report.BaseAlertState [pscustomobject]@{ Name = "ocsentinelbasestatus"; Type = "Text"; Value = [string]$Report.BaseAlertState }
"ocsentinelevents" = [string]([int]$Report.TotalEvents) [pscustomobject]@{ Name = "ocsentinelevents"; Type = "Integer"; Value = [int]$Report.TotalEvents }
"ocsentineluniqueips" = [string]([int]$Report.UniqueIpCount) [pscustomobject]@{ Name = "ocsentineluniqueips"; Type = "Integer"; Value = [int]$Report.UniqueIpCount }
"ocsentinelcvecritical" = [string]([int]$Report.VulnerabilityCorrelation.CriticalCount) [pscustomobject]@{ Name = "ocsentinelcvecritical"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.CriticalCount }
"ocsentinelcvetotal" = [string]([int]$Report.VulnerabilityCorrelation.TotalCount) [pscustomobject]@{ Name = "ocsentinelcvetotal"; Type = "Integer"; Value = [int]$Report.VulnerabilityCorrelation.TotalCount }
"ocsentinelmode" = $Mode [pscustomobject]@{ Name = "ocsentinelmode"; Type = "Text"; Value = $Mode }
"ocsentineltriggered" = $Triggered.ToString().ToLowerInvariant() [pscustomobject]@{ Name = "ocsentineltriggered"; Type = "Checkbox"; Value = $Triggered }
"ocsentinellastscanutc" = $generatedAtUtc [pscustomobject]@{ Name = "ocsentinellastscanutc"; Type = "DateTime"; Value = $generatedAtUtc }
} )
$updated = 0 $updated = 0
foreach ($entry in $fieldValues.GetEnumerator()) { foreach ($entry in $fieldValues) {
try { try {
if (Set-NinjaCustomFieldValue -Name $entry.Key -Value $entry.Value) { if (Set-NinjaCustomFieldValue -Name $entry.Name -Value $entry.Value -Type $entry.Type) {
$updated++ $updated++
} }
} }
catch { catch {
Write-Warning "Failed to set Ninja custom field '$($entry.Key)': $($_.Exception.Message)" Write-Warning "Failed to set Ninja custom field '$($entry.Name)': $($_.Exception.Message)"
} }
} }

View File

@@ -5,7 +5,10 @@ internal static class ScanAndUploadCommand
public static int Execute(string[] args) public static int Execute(string[] args)
{ {
string outputPath = @"C:\ProgramData\OCSentinel\reports\latest.json"; string outputPath = @"C:\ProgramData\OCSentinel\reports\latest.json";
string? clientConfigPath = null;
string? secretPath = null;
bool hasOutput = false; bool hasOutput = false;
var scanArgs = new List<string>();
for (int i = 0; i < args.Length; i++) for (int i = 0; i < args.Length; i++)
{ {
@@ -13,11 +16,26 @@ internal static class ScanAndUploadCommand
{ {
outputPath = args[i + 1]; outputPath = args[i + 1];
hasOutput = true; hasOutput = true;
break; scanArgs.Add(args[i]);
scanArgs.Add(args[++i]);
continue;
} }
if (string.Equals(args[i], "--client-config", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
clientConfigPath = args[++i];
continue;
}
if (string.Equals(args[i], "--secret-path", StringComparison.OrdinalIgnoreCase) && i + 1 < args.Length)
{
secretPath = args[++i];
continue;
}
scanArgs.Add(args[i]);
} }
List<string> scanArgs = [.. args];
if (!hasOutput) if (!hasOutput)
{ {
scanArgs.Add("--output"); scanArgs.Add("--output");
@@ -36,6 +54,16 @@ internal static class ScanAndUploadCommand
outputPath outputPath
}; };
if (!string.IsNullOrWhiteSpace(clientConfigPath))
{
uploadArgs.AddRange(["--client-config", clientConfigPath]);
}
if (!string.IsNullOrWhiteSpace(secretPath))
{
uploadArgs.AddRange(["--secret-path", secretPath]);
}
return UploadCommand.Execute([.. uploadArgs]); return UploadCommand.Execute([.. uploadArgs]);
} }
} }

View File

@@ -8,6 +8,9 @@ internal static class JsonOptions
public static readonly JsonSerializerOptions Default = new() public static readonly JsonSerializerOptions Default = new()
{ {
WriteIndented = true, WriteIndented = true,
// Client configuration is also written by PowerShell/NinjaOne scripts.
// Accept their conventional camelCase names (for example n8nWebhookUrl).
PropertyNameCaseInsensitive = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}; };
} }

View File

@@ -9,10 +9,10 @@
<RootNamespace>OCSentinelCli</RootNamespace> <RootNamespace>OCSentinelCli</RootNamespace>
<Product>OfficeCom Sentinel</Product> <Product>OfficeCom Sentinel</Product>
<Company>OfficeCom</Company> <Company>OfficeCom</Company>
<Version>1.2.5</Version> <Version>1.2.11</Version>
<AssemblyVersion>1.2.3.0</AssemblyVersion> <AssemblyVersion>1.2.11.0</AssemblyVersion>
<FileVersion>1.2.3.0</FileVersion> <FileVersion>1.2.11.0</FileVersion>
<InformationalVersion>1.2.3</InformationalVersion> <InformationalVersion>1.2.11</InformationalVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>