Skip to content

feat(bdr-discovery): backup-eligibility discovery for Windows endpoints#81

Draft
Gumbees wants to merge 6 commits into
mainfrom
feature/bdr-discovery-needs-backup-detect
Draft

feat(bdr-discovery): backup-eligibility discovery for Windows endpoints#81
Gumbees wants to merge 6 commits into
mainfrom
feature/bdr-discovery-needs-backup-detect

Conversation

@Gumbees

@Gumbees Gumbees commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

New SYSTEM-context NinjaRMM script that decides whether a Windows endpoint needs to be backed up.

  • Hard signals (deterministic): Hyper-V host -> NO (the VMs get backed up, not the host); Domain Controller / Windows Server / non-embedded DB service -> YES.
  • Soft-signal triage (workstations with no hard signal): the script gathers a soft-signal census (user-profile data footprint by extension, non-admin SMB shares) and calls the Anthropic Messages API directly (Opus 4.7, prompt-cached system message) to decide. Claude returns DECISION:, REASONING:, and SHORT_SUMMARY:.
  • Resilience: when the inventory-companion script (msft-windows-host-classification.ps1) hasn't run yet, host-class fields read as null/missing and the script falls back to local Win32 detection (Get-CimInstance Win32_OperatingSystem, Get-Service NTDS, Get-WindowsFeature).

File-extension census

Office (incl. macro-enabled and templates), email stores (.pst/.ost/.eml/.msg/.mbox), Access/SQLite/SQL dumps, QuickBooks variants, designer (.psd/.ai/.indd), CAD (.dwg/.step/.skp), DICOM (.dcm) for the dental practice fleet, and virtual disks (.vhd/.vhdx/.vmdk/.vdi/.qcow2/.ova/.ovf). The Claude prompt explicitly flags virtual disks + DICOM + local DB files as strong YES signals.

NinjaRMM custom fields written

Field Type Notes
backupDiscoveryHasDatabase Boolean 1 if any DB service detected (embedded or not)
backupDiscoveryNeedsBackup Boolean Only written when a decision was reached; left untouched on pending so the next scan can retry
backupDiscoveryDecisionReason Text (<=190 chars) Truncated to fit the 200-char NinjaRMM single-line text cap
backupDiscoveryAIVerdict Text not-required / needed / not-needed / pending / failed-config / failed-retry-later
backupDiscoveryDetails WYSIWYG/HTML Color-coded summary table
backupDiscoveryAIPayload Multi-line Full JSON inventory (signals + verdict + reasoning)

Exit codes

  • 0 -- hard signals decided, or Claude returned a parseable verdict.
  • 1 -- Claude was needed but unreachable / unparseable after retries (5 transient retries, 3 parse retries with exponential backoff).
  • 2 -- Claude was needed but auth/config failed (missing key, 401/403, malformed request).

Test plan

  • Interactive run on a domain controller -> verdict not-required (hard signal), needsBackup=1, reason "Domain Controller".
  • Interactive run on a Hyper-V host -> needsBackup=0, reason cites Hyper-V host exclusion.
  • Interactive run on a Windows Server (non-DC, no Hyper-V) -> hard signal YES.
  • Interactive run on a workstation with no DB and meaningful profile data -> Claude path, prompts for API key, returns needed.
  • Interactive run on a fresh workstation (no profiles, no shares) -> Claude path, returns not-needed.
  • RMM-mode dry run via $env:RMM="1" with all custom-field vars defaulted -> writes flagged correctly in transcript.
  • RMM-mode failure paths: missing anthropicApiKey -> exit 2, aiVerdict=failed-config; bogus key -> exit 2, aiVerdict=failed-config after auth rejection.
  • Confirm transcript at $env:WINDIR\logs\bdr-discovery-needs-backup-detect.log (interactive) and $env:RMMScriptPath\logs\ (RMM).
  • Verify Ninja-Property-Get "Unable to find the specified field" returns are treated as missing (local-detection fallback fires) instead of being persisted as a value.

🤖 Generated with Claude Code


Update — dental-aware detection, imaging census, fleet jitter (commit c06b476)

The original DB detection was enterprise-SQL-centric and missed every proprietary dental engine. This commit makes signal collection dental-aware.

Database detection (Test-HasDatabase reworked) — now matches services on Name AND DisplayName via an engine catalog:

  • Dental PMS + QuickBooks engines = always customer data → hard YES: SAP SQL Anywhere (SQLANYs_*/*SQL Anywhere*/*Sybase*, covers Eaglesoft generally), Pervasive/Actian PSQL (Dentrix), Dentrix Ace Server / FairCom c-tree, DTX Studio Core (DEXIS), QuickBooks DB Server (QuickBooksDB*).
  • Enterprise SQL (MSSQL/MySQL/PG/Oracle/Mongo/Redis) still path-checked against embedded host apps.
  • SQLWriter/SQLBrowser/SQLAgent/QBCFMonitorService/FDLauncher demoted to informational companions — recorded, never trigger. Fixes the false hard-YES where a lone VSS-writer/helper implied a customer DB.

SoftDent (Test-SoftDentServer, new) — registry-based, since core data is FairCom c-tree flat files with no service/extension. Detects HKLM\SOFTWARE\PWInc (+ WOW6432Node), infers server role from the PWSvr subkey / PWsvr process, surfaces a data-path value; SoftDent server → hard YES. Exact registry value names are inferred and want confirmation against a live SoftDent server.

Imaging.dex (DEXIS) added to the unreplaceable cross-drive scan + census (DICOM already covered). Standard raster formats (png/jpg/jpeg/bmp/tif/tiff/gif/heic/heif/webp) added to the census behind a 100 KB floor (Test-CensusCountable) so icons/thumbnails/UI sprites don't inflate counts.

QuickBooks — data files (.qbw/.qbb/.qbm/.qba) scoped as a soft signal (may be copies); the QuickBooksDB* service is the hard signal (proves this box hosts the live company file).

Fleet jitter — random 0..$env:ClaudeJitterMaxSeconds (default 600s) sleep before the Claude call in RMM mode to smear API load across a fleet run. Note: 600s only satisfies Tier 2+ Anthropic limits (1000 RPM). At Tier 1 (50 RPM) ~8000 endpoints need a ~5h window; the real fix is reaching Tier 2 or the Message Batches API.

Still open

  • Honor the retry-after header on 429s (current Claude retry uses fixed backoff, which fights the API at fleet scale).
  • Confirm SoftDent registry value names on a live server.

Gumbees and others added 3 commits May 11, 2026 13:14
…67)

* improvement(docs): sync CLAUDE.md with DTC canonical change-taxonomy

Replace the deprecated enhancement/ + problem/ two-prefix model with the
canonical four-prefix taxonomy (bug/, refactor/, improvement/, feature/)
and two-tier GitHub labels (type:* + category:*). Mirrors
https://kb.dtctoday.com/books/developer-operations-devops/page/change-taxonomy.

- Rewrite Git Workflow section to reflect main-as-release (this repo has
  no development branch; the prior section described a model that did
  not match the actual repo).
- Add Halo Type / Category / GitHub labels / Branch prefix / Default
  semver mapping table.
- Note BREAKING: PR title override as future-state (no semver yet).
- Add dependabot categorization rule and 'name the branch after the
  change' guidance.
- Mark legacy enhancement/ and problem/ prefixes accepted on existing
  branches; new work uses the four-prefix model.
- Add db-* to the Category Organization list (db-mysql/ exists on disk).

* docs(taxonomy): add Enhancement/Refactor row + clarify Refactor spans both types
* Add NinjaRMM GUID to Halo PSA GUID Sync Script

Syncs DTC Org GUID from NinjaRMM custom fields to DTC Client GUID field in Halo PSA for all matching clients by name.

* Update ninja-halo-guid-sync.ps1

Fix Halo custom field name to CFDtcClientGuid

* Fix NinjaClientSecret placeholder comment typo

* Remove hardcoded credentials — replace with placeholders

Secrets were exposed in script body. Rotated all credentials. Real values stored in NinjaRMM script body (never leaves Ninja) and 1Password. GitHub copy uses placeholders only.

* Update script — use Script Variable injection for secrets

Refactored to inject client secrets via NinjaRMM Script Variables at runtime. Client IDs hardcoded (not secrets). Secrets never stored in repo. Validated working in production.

* Fix: Bring script into compliance with DTC PowerShell engineering standard

Addresses CodeRabbit review concerns on PR #49:
- Add RMM variable declaration block per script-template-powershell.ps1
- Add Start-Transcript/Stop-Transcript with C:\ProgramData\DTC\Logs\ output
- Add $ScriptLogName and $Description audit trail variables
- Replace Write-Host with Write-Information per output stream standard
- Add $ErrorActionPreference = 'Stop' and $ProgressPreference = 'SilentlyContinue'
- Add explicit exit codes (0/1/2 per DTC convention)
- Wrap all work in try/catch/finally per page 3299
New SYSTEM-context Ninja script that decides whether an endpoint
needs a backup. Hard signals (Hyper-V host -> NO; DC, Windows Server,
non-embedded DB -> YES) decide outright; workstations with no DC/
server/DB signal get triaged by Claude (Opus 4.7) using a soft-signal
census (user profile data, non-admin SMB shares, file-extension mix).

Soft signals include Office (incl. macro-enabled variants), email
stores, local DB files, QuickBooks, designer/CAD, DICOM (HIPAA),
and virtual-disk files (.vhd/.vhdx/.vmdk/.vdi/.qcow2/.ova) -- local
VMs almost always hold business data.

Writes backupDiscoveryNeedsBackup + reason + AI verdict + HTML
details + multi-line JSON payload to NinjaRMM custom fields. Reads
the hostClassIs* fields from msft-windows-host-classification.ps1
when available; falls back to local Win32 detection so the script is
resilient to inventory-not-yet-run cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Gumbees Gumbees added type:enhancement New work that adds or improves capability (Halo: Enhancement) category:feature Net-new capability the customer/operator didn't have labels May 27, 2026

@claude claude Bot 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.

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, push a new commit or reopen this pull request to trigger a review.

Gumbees and others added 3 commits May 27, 2026 15:42
The per-profile sweep previously walked only Desktop/Documents/Pictures/
Videos/Downloads, missing the default Outlook location entirely. A
workstation with an archive PST in %LOCALAPPDATA%\Microsoft\Outlook\
would have been triaged as if it had no email data at all.

Also exclude .ost bytes from the user-data footprint total: OSTs are
server-side mailbox caches that routinely run 20+ GB and would
false-positive the AI's ~5 GB business-data threshold. .ost still
contributes to the extension count as a "user has email" presence
signal; only its size is excluded.

AI prompt updated to call PST presence out as a strong YES signal
(unreplaceable user-created archive) and to treat .ost-only as
presence-not-backup-needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OST files are offline caches of an Exchange/M365 mailbox -- the
mailbox is already backed up in the cloud, so there's no reason to
flag the local cache. Removed .ost from the extension census and
dropped the OST-specific byte-skip workaround entirely.

PSTs (and VHDs, QuickBooks files, Access databases, DICOM images,
VM disks) can live anywhere on disk -- root of D:, C:\Backup,
C:\Hyper-V, a custom data drive. The previous sweep only walked
Desktop / Documents / Pictures / Videos / Downloads per profile, so
anything outside those folders was invisible. Added Find-CriticalFiles
which walks every fixed drive once (skipping Windows / Recycle Bin /
System Volume Information / PerfLogs) and surfaces every match with
path, kind, size, and modified time.

Results land in soft_signals.critical_files in both the Claude
payload and the final audit JSON, and the top 25 hits render in the
HTML details field. The AI prompt was rewritten around the new
block: a non-empty critical_files list is now an explicit YES signal,
and OST is no longer mentioned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… jitter

Detection was enterprise-SQL-centric and blind to the dental engines that
actually hold patient data. Rework signal collection to be dental-aware:

- Test-HasDatabase: match services by Name AND DisplayName via an engine
  catalog. Dental PMS + QuickBooks engines (SQL Anywhere/Eaglesoft,
  Pervasive/Actian, Dentrix Ace/FairCom c-tree, DTX Studio Core,
  QuickBooksDB) are always customer data -> hard YES. Demote
  SQLWriter/SQLBrowser/SQLAgent/QBCFMonitor to informational companions
  so a VSS-writer/helper alone no longer forces a false hard YES.
- Test-SoftDentServer: registry-based detection (HKLM\SOFTWARE\PWInc /
  PWSvr) since SoftDent core data is FairCom c-tree flat files with no
  service or distinctive extension; SoftDent server -> hard YES.
- Imaging: add .dex (DEXIS) to the unreplaceable cross-drive scan and
  census. Add standard raster formats (png/jpg/jpeg/bmp/tif/tiff/gif/
  heic/webp) to the census behind a 100 KB floor so icons/thumbnails are
  excluded (Test-CensusCountable).
- Fleet jitter: random 0..ClaudeJitterMaxSeconds (default 600s) sleep
  before the Claude call in RMM mode to smear API load across a fleet
  run; documented that 600s only fits Tier 2+ Anthropic rate limits.
- QuickBooks: data files scoped as soft signal; the QuickBooksDB service
  is the hard signal. Update Claude system prompt, AI payload, and HTML
  summary for the new signals.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Gumbees Gumbees marked this pull request as draft June 2, 2026 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category:feature Net-new capability the customer/operator didn't have type:enhancement New work that adds or improves capability (Halo: Enhancement)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants