Skip to content

feat(ninjaone): auto-create CVE vulnerability scan group when missing#5

Open
Logan Cook (MWG-Logan) wants to merge 1 commit into
devfrom
feat/ninjaone-cve-autocreate-scan-group
Open

feat(ninjaone): auto-create CVE vulnerability scan group when missing#5
Logan Cook (MWG-Logan) wants to merge 1 commit into
devfrom
feat/ninjaone-cve-autocreate-scan-group

Conversation

@MWG-Logan

@MWG-Logan Logan Cook (MWG-Logan) commented Jul 14, 2026

Copy link
Copy Markdown
Member

Resolves KelvinTegelaar/CIPP#6349, "[Feature Request]: Auto-create NinjaOne vulnerability scan group during CVE sync".

Summary

Previously, Invoke-NinjaOneTenantSync looked up the NinjaOne CVE vulnerability scan group by name and failed the CVE sync step for the tenant if that scan group did not already exist in NinjaOne. This forced admins to manually pre-create the scan group in every tenant before CVE sync could function.

  • Extract the scan-group lookup into a new, independently testable helper Resolve-NinjaOneCveScanGroup (Private/NinjaOne). It now:
    • Looks up the scan group by configured name.
    • Auto-creates it via the NinjaOne API when not found, using the configured (or default) device-id/cve-id header names.
    • Returns $null and logs an Error via Write-LogMessage when lookup and creation both fail, so the outer CVE sync block can log and continue instead of throwing.
  • Wire Invoke-NinjaOneTenantSync's CVE sync block to call the new helper instead of inline lookup-or-fail logic.
  • Fix an unrelated but adjacent bug: the NinjaOne API Authorization header was being built without the required Bearer prefix, which would have caused every authenticated NinjaOne API call to fail with 401 Unauthorized.

Tests

  • Tests/NinjaOne/Resolve-NinjaOneCveScanGroup.Tests.ps1: 5 scenarios covering existing scan group found, auto-create on missing, auto-create using custom header names, auto-create using default header names, and the not-found/creation-failure path.

  • Tests/NinjaOne/Invoke-NinjaOneTenantSync.Tests.ps1: a comprehensive Pester suite for the full sync function (16 scenarios) covering successful sync, tenant match validation, hostname allow-list validation, CVE sync (including the new auto-create path, exception filtering, and isolated failure handling), UserDocuments/LicenseDocuments toggles, final custom-fields PATCH failure handling, and the tenant-sync concurrency guard.

    While writing the concurrency-guard test, discovered a genuine, pre-existing production bug: the guard parses the stored lastStartTime (a UTC ISO-8601 string) with Get-Date($string), which returns a Kind=Local DateTime, then compares it directly against (Get-Date).ToUniversalTime() (Kind=Utc). .NET's DateTime comparison operators ignore Kind and compare raw ticks, so the "already running" check is silently wrong by the host's UTC offset on any non-UTC host. This is out of scope for #6349 and is tracked separately as Invoke-NinjaOneTenantSync: concurrency "already running" guard fails to detect running sync on non-UTC hosts #4; the test documents the current (buggy) comparison behavior deterministically across host timezones rather than masking or silently working around it.

Both suites pass 100% (5/5 and 16/16). PSScriptAnalyzer run clean on all touched/new files (only pre-existing repo-wide style warnings, no errors).

Copilot AI review requested due to automatic review settings July 14, 2026 17:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the NinjaOne tenant sync CVE upload flow by extracting scan-group resolution into a dedicated helper that can auto-create the configured CVE vulnerability scan group when it doesn’t already exist, and adds Pester coverage around the new behavior and the overall sync flow.

Changes:

  • Added Resolve-NinjaOneCveScanGroup helper to lookup (and when missing, create) the CVE scan group for a tenant.
  • Updated Invoke-NinjaOneTenantSync to use the helper instead of inline scan-group lookup logic.
  • Added Pester suites covering scan-group resolution and a broad set of tenant-sync scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
Tests/NinjaOne/Resolve-NinjaOneCveScanGroup.Tests.ps1 Adds focused unit coverage for scan-group lookup/create behavior and headers.
Tests/NinjaOne/Invoke-NinjaOneTenantSync.Tests.ps1 Adds broader end-to-end + edge-case coverage for the tenant sync function, including CVE sync integration.
Modules/CippExtensions/Public/NinjaOne/Invoke-NinjaOneTenantSync.ps1 Switches CVE scan-group lookup to the new helper.
Modules/CippExtensions/Private/NinjaOne/Resolve-NinjaOneCveScanGroup.ps1 Implements scan-group resolution and auto-creation logic.

Comment thread Modules/CippExtensions/Private/NinjaOne/Resolve-NinjaOneCveScanGroup.ps1 Outdated
Resolves KelvinTegelaar/CIPP#6349, "[Feature Request]: Auto-create
NinjaOne vulnerability scan group during CVE sync".

Previously, `Invoke-NinjaOneTenantSync` looked up the NinjaOne CVE
vulnerability scan group by name and failed the CVE sync step for the
tenant if that scan group did not already exist in NinjaOne. This
forced admins to manually pre-create the scan group in every tenant
before CVE sync could function.

- Extract the scan-group lookup into a new, independently testable
  helper `Resolve-NinjaOneCveScanGroup` (Private/NinjaOne). It now:
  - Looks up the scan group by configured name.
  - Auto-creates it via the NinjaOne API when not found, using the
    configured (or default) device-id/cve-id header names.
  - Returns $null and logs an Error via Write-LogMessage when lookup
    and creation both fail, so the outer CVE sync block can log and
    continue instead of throwing.
- Wire `Invoke-NinjaOneTenantSync`'s CVE sync block to call the new
  helper instead of inline lookup-or-fail logic.
- Fix an unrelated but adjacent bug: the NinjaOne API Authorization
  header was being built without the required "Bearer " prefix
  (`"Bearer $($Token.access_token)"`), which would have caused every
  authenticated NinjaOne API call to fail with 401 Unauthorized.

- Address PR review feedback (review comment r3581392606) on
  `Resolve-NinjaOneCveScanGroup`:
  - Wrap the initial scan-group lookup GET request in its own
    try/catch. Previously an uncaught exception here (e.g. 401,
    timeout) would propagate out of the function, contradicting its
    documented `$null`-on-failure contract; it now logs an
    Error-severity message and returns $null like the create-failure
    path already did.
  - Pipe the `Where-Object` name match through `Select-Object -First
    1` so the function always returns a single scan-group object as
    documented, even if NinjaOne has multiple scan groups sharing the
    same name (previously it could return an array in that case,
    silently breaking downstream `.id`/`.deviceIdHeader`/`.cveIdHeader`
    access and upload-URI construction in the caller).

Tests:
- Add `Tests/NinjaOne/Resolve-NinjaOneCveScanGroup.Tests.ps1`: 5
  scenarios covering existing scan group found, auto-create on
  missing, auto-create using custom header names, auto-create using
  default header names, and the not-found/creation-failure path.
  Plus 2 new scenarios added for the review-feedback fix: the
  initial lookup GET failing (returns $null, logs Error, does not
  throw, does not attempt create), and multiple scan groups sharing
  the same name (returns a single object, not an array).
- Add `Tests/NinjaOne/Invoke-NinjaOneTenantSync.Tests.ps1`: a
  comprehensive Pester suite for the full sync function (16
  scenarios) covering successful sync, tenant match validation,
  hostname allow-list validation, CVE sync (including the new
  auto-create path, exception filtering, and isolated failure
  handling), UserDocuments/LicenseDocuments toggles, final
  custom-fields PATCH failure handling, and the tenant-sync
  concurrency guard.

  While writing the concurrency-guard test, discovered a genuine,
  pre-existing production bug: the guard parses the stored
  `lastStartTime` (a UTC ISO-8601 string) with `Get-Date($string)`,
  which returns a `Kind=Local` DateTime, then compares it directly
  against `(Get-Date).ToUniversalTime()` (`Kind=Utc`). .NET's
  DateTime comparison operators ignore `Kind` and compare raw ticks,
  so the "already running" check is silently wrong by the host's UTC
  offset on any non-UTC host. This is out of scope for #6349 and is
  tracked separately as KelvinTegelaar/CIPP#6351; the test documents the
  current (buggy) comparison behavior deterministically across host
  timezones rather than masking or silently working around it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@MWG-Logan
Logan Cook (MWG-Logan) force-pushed the feat/ninjaone-cve-autocreate-scan-group branch from 838f17c to 24db2c5 Compare July 14, 2026 18:11
@MWG-Logan Logan Cook (MWG-Logan) changed the title fix(ninjaone): auto-create CVE vulnerability scan group when missing feat(ninjaone): auto-create CVE vulnerability scan group when missing Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants