Skip to content

Add Chrome Remote Desktop detection script#50

Merged
Gumbees merged 11 commits into
mainfrom
enhancement/chrome-remote-desktop-detection
May 11, 2026
Merged

Add Chrome Remote Desktop detection script#50
Gumbees merged 11 commits into
mainfrom
enhancement/chrome-remote-desktop-detection

Conversation

@Gumbees

@Gumbees Gumbees commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Summary

New rmm-misc/chrome-remote-desktop-detect.ps1 detects whether Chrome Remote Desktop is installed or active on an endpoint and writes 1 or 0 to a NinjaRMM custom field.

Built from sample code, then aligned with the DTC script template (script-template-powershell.ps1) and modeled on msft-office/cve-2026-21509-detect.ps1 for structure.

What it checks

The sample only looked at HKLM uninstall keys and the remoting_host process, which misses the most common case: Chrome Remote Desktop installed per-user via the Chrome browser, which lives in HKCU and %LOCALAPPDATA%. This script covers all of:

  1. HKLM uninstall registry (system-wide MSI install)
  2. HKCU uninstall registry for every loaded user hive (per-user install)
  3. Program Files\Google\Chrome Remote Desktop install path
  4. %LOCALAPPDATA%\Google\Chrome Remote Desktop for every user profile on the box
  5. chromoting Windows service (Chrome Remote Desktop Service)
  6. remoting_host.exe process

If any check returns true, the result is 1. Otherwise 0. Installation counts as active per the requirement.

Output

  • Writes 1 or 0 to a NinjaRMM custom field (default name Remote, configurable via $NinjaCustomField RMM variable)
  • Full transcript log at $ENV:WINDIR\logs\chrome-remote-desktop-detect.log (or $RMMScriptPath\logs\ when run from RMM)
  • Exit code matches the result (0 not active, 1 active)

Template alignment

  • Standard ## $VarName RMM variable comment block at the top
  • $RMM-aware dual execution mode with $Description audit trail
  • Log directory creation before Start-Transcript
  • Start-Transcript / Stop-Transcript wrapping all logic
  • One function per detection method, named Test-CRD*
  • Ninja-Property-Set call gated to $RMM -eq 1 so interactive runs do not blow up on the missing cmdlet

Test plan

  • Run interactively on a clean machine (no CRD) — expect result 0, no Ninja call attempted
  • Run interactively on a machine with CRD installed per-user via Chrome — expect result 1, HKCU and/or User AppData checks should fire
  • Run from NinjaRMM with \$RMM=1 against a known-clean machine — expect Remote custom field set to 0
  • Run from NinjaRMM with \$RMM=1 against a machine with CRD — expect Remote custom field set to 1
  • Verify transcript log written to \$RMMScriptPath\logs\chrome-remote-desktop-detect.log

Summary by CodeRabbit

  • New Features

    • Cross-context Chrome Remote Desktop detection covering both system and per-user presence, with shared state tracking and exit codes reflecting detection.
    • Optional automated reporting to RMM custom fields when run in system context; interactive mode prints intended outputs.
  • Documentation

    • Expanded README and workflow docs with branch guidance, RMM/environment-variable conventions, detection patterns, logging rules, and testing guidance.

New rmm-misc/chrome-remote-desktop-detect.ps1 detects Chrome Remote
Desktop installation or activity across all install vectors:

- HKLM and HKCU uninstall registry keys (catches per-user Chrome
  installs that HKLM-only checks miss)
- Program Files install path
- %LOCALAPPDATA%\Google\Chrome Remote Desktop for every user profile
- chromoting Windows service
- remoting_host.exe process

Writes 1 (active) or 0 (not active) to a configurable NinjaRMM custom
field (default name "Remote"). Installation counts as active.

Follows the standard DTC script template with RMM/interactive dual
execution modes, transcript logging, and the Ninja-Property-Set call
gated to RMM mode so interactive runs are safe.
@Gumbees Gumbees added the enhancement New functionality, improvements, or enhancements label Apr 7, 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.

Fits the app-{vendor-or-app} pattern better than rmm-misc, since Chrome
Remote Desktop is a Google Chrome family product and rmm-misc is a
catch-all that should not be the default home for app-specific scripts.
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Gumbees has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 21 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75a0fb6f-7ba5-40f6-b1c2-4ef5666413ef

📥 Commits

Reviewing files that changed from the base of the PR and between 3decd48 and 08f396f.

📒 Files selected for processing (1)
  • script-template-powershell.ps1
📝 Walkthrough

Walkthrough

Adds two PowerShell detection scripts: a SYSTEM-context detector and a user-context detector that share per-tenant JSON state; scripts perform registry/file/service/process checks, log transcripts, update shared state, and conditionally write NinjaRMM custom fields.

Changes

Cohort / File(s) Summary
System-Level detector
app-google-chrome/chrome-remote-desktop-detect-system.ps1
New SYSTEM-context script. Validates OrgName, initializes RMM env defaults, starts transcript, reads shared JSON under %PUBLIC%\{OrgName}\rmm-db\, runs four system checks (HKLM uninstall 32/64, Program Files path, chromoting service, remoting_host process), computes detected/contextFoundIn/foundDetailsHtml, and (when $env:RMM -eq "1") writes results to NinjaRMM custom fields via Ninja-Property-Set. Exits with code = detected.
User-Level detector
app-google-chrome/chrome-remote-desktop-detect-user.ps1
New user-context script. Requires OrgName and optional Description, starts transcript, checks HKCU uninstall (standard and WOW6432Node) and %LOCALAPPDATA%\Google\Chrome Remote Desktop, reads/modifies per-tenant JSON state (google-chrome-remote-desktop-user-active.json) adding/removing the current user entry with UTC timestamp, writes JSON back, logs outcome, and exits with 0/1.
Documentation
CLAUDE.md, README.md
Large documentation updates: new Git workflow (branches development/release), environment-variable-driven input conventions, guidance for RMM simulation and transcript paths, detection patterns (HKLM/HKCU/Program Files/services/processes), NinjaRMM custom field usage and SYSTEM-only constraint, and cross-context detection design using two scripts + shared JSON. Updated testing and contributing guidance.

Sequence Diagram(s)

sequenceDiagram
  participant UserScript as User Detector
  participant SharedJSON as Shared JSON (%PUBLIC%\{OrgName}\rmm-db\)
  participant SystemScript as System Detector
  participant NinjaRMM as NinjaRMM (Ninja-Property-Set)
  participant Host as Host OS

  UserScript->>Host: start transcript, read HKCU/LocalAppData
  Host-->>UserScript: detection result
  UserScript->>SharedJSON: read current state
  UserScript->>SharedJSON: write/update user entry with timestamp
  SharedJSON-->>UserScript: ack
  UserScript->>Host: stop transcript, exit (0/1)

  SystemScript->>Host: start transcript, validate OrgName/envs
  SystemScript->>Host: perform HKLM, ProgramFiles, Service, Process checks
  Host-->>SystemScript: check results
  SystemScript->>SharedJSON: read user-context JSON
  SharedJSON-->>SystemScript: return user entries
  SystemScript->>SystemScript: compute detected/contextFoundIn/foundDetailsHtml
  alt $env:RMM == "1"
    SystemScript->>NinjaRMM: Ninja-Property-Set per-field (try/catch)
    NinjaRMM-->>SystemScript: ack/fail
  else
    SystemScript->>Host: print would-write values
  end
  SystemScript->>Host: stop transcript, exit (detected)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibble through keys and folders bright,
I log the traces in the soft moonlight,
Users mark timestamps, the system checks hum,
Fields set or printed — detection's begun,
A hopping rabbit cheers: "All done!" ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding a Chrome Remote Desktop detection script, which is the primary objective evident across both new PowerShell scripts and supporting documentation updates.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhancement/chrome-remote-desktop-detection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Nate Smith added 2 commits April 7, 2026 13:30
More descriptive than the generic NinjaCustomField. Names the field by
what it stores (Google Chrome [Remote Desktop] active state, boolean)
rather than by the platform consuming it.
Drops HKU hive iteration and C:\Users profile iteration in favor of
plain HKCU and $env:LOCALAPPDATA checks. Script is deployed in three
contexts (daily from SYSTEM, at boot from SYSTEM, at user login as the
user) and the user-login run is what catches per-user Chrome installs.
SYSTEM runs still cover system-wide installs and any active service or
process.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
app-google-chrome/chrome-remote-desktop-detect.ps1 (1)

239-240: Exit code semantics are inverted from typical convention.

Using exit 1 to indicate "active/found" and exit 0 for "not found" inverts the standard success=0 convention. This is documented in the header comments (line 26), but verify that downstream RMM alerting/automation handles this correctly—some RMM platforms may interpret exit code 1 as a script failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect.ps1` around lines 239 - 240,
The script currently uses exit $result where $result maps 1="found/active" and
0="not found", inverting the usual success=0 convention; change the exit
semantics so that a successful detection (resource found/active) returns exit 0
and a non-detection returns a non-zero code (e.g., 1), update the logic around
the exit invocation (the Stop-Transcript / exit $result block) to map detection
boolean to these standard codes, and update the header comment describing exit
semantics to match the new convention; ensure any references to the variable
name (e.g., $result) or function that sets it are adjusted accordingly so
downstream RMM interprets the exit code correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app-google-chrome/chrome-remote-desktop-detect.ps1`:
- Around line 156-170: Test-CRDUserAppData currently hardcodes "C:\Users" and
will miss relocated profile folders; update it to enumerate user profiles from
the system profile registry (HKLM:\SOFTWARE\Microsoft\Windows
NT\CurrentVersion\ProfileList) or via Win32_UserProfile (Get-CimInstance
Win32_UserProfile) to read each ProfileImagePath, expand any environment
variables and skip built-in/system SIDs, then join "AppData\Local\Google\Chrome
Remote Desktop" to each resolved profile path and Test-Path as before (preserve
the existing Write-Host message and return behavior). Ensure you still filter
out Default/Public/Default User/All Users equivalents by name or by well-known
SIDs when using registry/CIM.
- Around line 227-237: The Ninja-Property-Set call in the RMM branch uses named
parameters (-Name, -Value) but the cmdlet expects positional parameters; update
the call inside the if ($RMM -eq 1) try block to call Ninja-Property-Set with
positional arguments (first the $NinjaCustomField, then $result) and keep the
existing try/catch and logging around it so the Write-Host success and error
messages still reference $NinjaCustomField and $result.

---

Nitpick comments:
In `@app-google-chrome/chrome-remote-desktop-detect.ps1`:
- Around line 239-240: The script currently uses exit $result where $result maps
1="found/active" and 0="not found", inverting the usual success=0 convention;
change the exit semantics so that a successful detection (resource found/active)
returns exit 0 and a non-detection returns a non-zero code (e.g., 1), update the
logic around the exit invocation (the Stop-Transcript / exit $result block) to
map detection boolean to these standard codes, and update the header comment
describing exit semantics to match the new convention; ensure any references to
the variable name (e.g., $result) or function that sets it are adjusted
accordingly so downstream RMM interprets the exit code correctly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7abf6608-c8f7-4e41-9018-a07dbff101ce

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac5146 and 6fccab6.

📒 Files selected for processing (1)
  • app-google-chrome/chrome-remote-desktop-detect.ps1

Comment on lines +156 to +170
function Test-CRDUserAppData {
$found = $false
$userProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | Where-Object {
$_.Name -notin @('Public', 'Default', 'Default User', 'All Users')
}

foreach ($userProfile in $userProfiles) {
$crdPath = Join-Path $userProfile.FullName "AppData\Local\Google\Chrome Remote Desktop"
if (Test-Path $crdPath) {
Write-Host " [Profile] Found in $($userProfile.Name): $crdPath"
$found = $true
}
}
return $found
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded "C:\Users" path may miss relocated profiles.

Windows allows the default profile directory to be relocated (e.g., D:\Users). Consider using the registry or $env:SystemDrive for better portability.

Suggested fix using ProfileList registry
 function Test-CRDUserAppData {
     $found = $false
-    $userProfiles = Get-ChildItem "C:\Users" -Directory -ErrorAction SilentlyContinue | Where-Object {
-        $_.Name -notin @('Public', 'Default', 'Default User', 'All Users')
-    }
+    # Query ProfileList for actual profile paths (handles relocated profiles)
+    $profileListPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
+    $userProfiles = Get-ChildItem $profileListPath -ErrorAction SilentlyContinue | ForEach-Object {
+        $profilePath = (Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue).ProfileImagePath
+        if ($profilePath -and (Test-Path $profilePath)) {
+            Get-Item $profilePath -ErrorAction SilentlyContinue
+        }
+    } | Where-Object {
+        $_.Name -notin @('Public', 'Default', 'Default User', 'All Users', 'systemprofile', 'LocalService', 'NetworkService')
+    }
 
     foreach ($userProfile in $userProfiles) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect.ps1` around lines 156 - 170,
Test-CRDUserAppData currently hardcodes "C:\Users" and will miss relocated
profile folders; update it to enumerate user profiles from the system profile
registry (HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList) or via
Win32_UserProfile (Get-CimInstance Win32_UserProfile) to read each
ProfileImagePath, expand any environment variables and skip built-in/system
SIDs, then join "AppData\Local\Google\Chrome Remote Desktop" to each resolved
profile path and Test-Path as before (preserve the existing Write-Host message
and return behavior). Ensure you still filter out Default/Public/Default
User/All Users equivalents by name or by well-known SIDs when using
registry/CIM.

Comment on lines +227 to +237
if ($RMM -eq 1) {
try {
Ninja-Property-Set -Name $NinjaCustomField -Value $result
Write-Host "Wrote $result to NinjaRMM custom field '$NinjaCustomField'"
} catch {
Write-Host "ERROR: Failed to write to NinjaRMM custom field '$NinjaCustomField' - $_"
}
} else {
Write-Host "Interactive mode - skipping Ninja-Property-Set call"
Write-Host "Would have written: Ninja-Property-Set -Name '$NinjaCustomField' -Value $result"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

NinjaRMM Ninja-Property-Set PowerShell cmdlet syntax parameters

💡 Result:

Ninja-Property-Set (NinjaOne / NinjaRMM) PowerShell command

Syntax (positional arguments):

Ninja-Property-Set <fieldName> <value>

Ninja-Property-Set is used to set a NinjaOne custom field to a specific value. [1]

Parameters (positional):

  • <fieldName> – The custom field Name (not the label) you want to write to. [1]
  • <value> – The value to assign (format depends on the field type; use quotes for values containing spaces). [1]

Examples

Ninja-Property-Set domainjoined 1
Ninja-Property-Set domainjoined 0

[1]

Multi-line text example (use `n to embed newlines; wrap in quotes):

Ninja-Property-Set multiline "line with spaces`nline2`nline3`nfinalval"

[2]

Note: NinjaOne’s newer PowerShell functions are Get-NinjaProperty / Set-NinjaProperty (described as evolutions of Ninja-Property-Get / Ninja-Property-Set). If you meant the full parameterized cmdlet-style syntax, it’s typically documented under Set-NinjaProperty instead. [2]

Sources: [1] [2]


Fix Ninja-Property-Set syntax to use positional parameters.

The Ninja-Property-Set cmdlet expects positional arguments, not named -Name and -Value parameters. The current syntax will fail.

Suggested fix
 if ($RMM -eq 1) {
     try {
-        Ninja-Property-Set -Name $NinjaCustomField -Value $result
+        Ninja-Property-Set $NinjaCustomField $result
         Write-Host "Wrote $result to NinjaRMM custom field '$NinjaCustomField'"
     } catch {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect.ps1` around lines 227 - 237,
The Ninja-Property-Set call in the RMM branch uses named parameters (-Name,
-Value) but the cmdlet expects positional parameters; update the call inside the
if ($RMM -eq 1) try block to call Ninja-Property-Set with positional arguments
(first the $NinjaCustomField, then $result) and keep the existing try/catch and
logging around it so the Write-Host success and error messages still reference
$NinjaCustomField and $result.

The combined script had to apologize for half its checks in either
context. Splitting it into two purpose-built scripts:

  chrome-remote-desktop-detect-system.ps1
    - Runs from SYSTEM (daily, boot)
    - HKLM, Program Files, chromoting service, remoting_host process
    - Logs to $ENV:WINDIR\logs
    - Default custom field: Remote

  chrome-remote-desktop-detect-user.ps1
    - Runs in user context (login)
    - HKCU and %LOCALAPPDATA% only
    - Logs to $ENV:LOCALAPPDATA\dtc-logs (user-writable)
    - Default custom field: RemoteUser

The two scripts default to different custom field names so they don't
overwrite each other. OR them together in NinjaRMM dashboards or
conditions to get a single 'CRD anywhere' signal.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app-google-chrome/chrome-remote-desktop-detect.ps1 (1)

35-35: Naming style is inconsistent with repo PowerShell rule.

Variables/functions in this file use mixed casing (e.g., ScriptLogName, Test-CRDInstalledHKLM) rather than camelCase. Consider normalizing to reduce style drift across scripts.

As per coding guidelines, "Use camelCase for PowerShell variables and functions".

Also applies to: 90-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect.ps1` at line 35, Variables and
function names in this script use mixed/Pascal casing (e.g., ScriptLogName,
Test-CRDInstalledHKLM) which violates the repo rule to use camelCase; rename all
top-level variables and function identifiers to camelCase (for example
ScriptLogName -> scriptLogName, Test-CRDInstalledHKLM -> test-CrdInstalledHklm
or testCrdInstalledHklm depending on your function naming convention), and
update every reference/callsite within the file (including any invocations,
argument passing, and exported/used symbols) to the new names to ensure
consistency across the file (also apply the same changes to other identifiers in
the 90-179 region mentioned).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app-google-chrome/chrome-remote-desktop-detect.ps1`:
- Around line 51-57: The checks currently only test for $null so empty strings
slip through; update the conditionals to use robust string emptiness checks
(e.g. [string]::IsNullOrWhiteSpace) for $RMMScriptPath and $Description so empty
or whitespace-only values trigger the fallback: when $RMMScriptPath is not
null/empty/whitespace set $LogPath = "$RMMScriptPath\logs\$ScriptLogName",
otherwise set $LogPath = "$ENV:WINDIR\logs\$ScriptLogName" (this preserves the
RMM fallback to %WINDIR%\logs), and change the $Description check to use the
same IsNullOrWhiteSpace test before writing the "Description is null..."
message.

---

Nitpick comments:
In `@app-google-chrome/chrome-remote-desktop-detect.ps1`:
- Line 35: Variables and function names in this script use mixed/Pascal casing
(e.g., ScriptLogName, Test-CRDInstalledHKLM) which violates the repo rule to use
camelCase; rename all top-level variables and function identifiers to camelCase
(for example ScriptLogName -> scriptLogName, Test-CRDInstalledHKLM ->
test-CrdInstalledHklm or testCrdInstalledHklm depending on your function naming
convention), and update every reference/callsite within the file (including any
invocations, argument passing, and exported/used symbols) to the new names to
ensure consistency across the file (also apply the same changes to other
identifiers in the 90-179 region mentioned).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c4ccb49-c18a-405b-82d8-9afce80d8129

📥 Commits

Reviewing files that changed from the base of the PR and between 6fccab6 and a701b61.

📒 Files selected for processing (1)
  • app-google-chrome/chrome-remote-desktop-detect.ps1

Comment on lines +51 to +57
if ($null -ne $RMMScriptPath) {
$LogPath = "$RMMScriptPath\logs\$ScriptLogName"
} else {
$LogPath = "$ENV:WINDIR\logs\$ScriptLogName"
}
if ($null -eq $Description) {
Write-Host "Description is null. This was most likely run automatically from the RMM."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Harden RMM fallback checks for empty values.

Line 51 and Line 56 only test for $null, so empty strings can bypass fallback/default behavior and produce weak paths/descriptions.

Suggested fix
-    if ($null -ne $RMMScriptPath) {
+    if (-not [string]::IsNullOrWhiteSpace($RMMScriptPath)) {
         $LogPath = "$RMMScriptPath\logs\$ScriptLogName"
     } else {
         $LogPath = "$ENV:WINDIR\logs\$ScriptLogName"
     }
-    if ($null -eq $Description) {
+    if ([string]::IsNullOrWhiteSpace($Description)) {
         Write-Host "Description is null. This was most likely run automatically from the RMM."
         $Description = "RMM Automated Scan"
     }

As per coding guidelines, "Input handling must detect $RMM ... set $LogPath to %WINDIR%\logs in interactive, and $RMMScriptPath\logs (fallback to %WINDIR%\logs) in RMM".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect.ps1` around lines 51 - 57, The
checks currently only test for $null so empty strings slip through; update the
conditionals to use robust string emptiness checks (e.g.
[string]::IsNullOrWhiteSpace) for $RMMScriptPath and $Description so empty or
whitespace-only values trigger the fallback: when $RMMScriptPath is not
null/empty/whitespace set $LogPath = "$RMMScriptPath\logs\$ScriptLogName",
otherwise set $LogPath = "$ENV:WINDIR\logs\$ScriptLogName" (this preserves the
RMM fallback to %WINDIR%\logs), and change the $Description check to use the
same IsNullOrWhiteSpace test before writing the "Description is null..."
message.

Nate Smith added 3 commits April 7, 2026 13:41
Both detection scripts now contribute to a single text custom field
(default 'RemoteContext') in addition to their boolean fields. Each
script reads the current value, drops or adds its own context label,
and writes the merged result back, so the field always reflects the
union of both signals: '', 'System', 'User', or 'System, User'.

Read-merge-write avoids the clobber problem ... the system script's
'System' label survives a user-context login on a machine where the
user's HKCU does not have CRD, and vice versa. New RMM variable
$CustomFieldGoogleChromeContextString is shared between both scripts
and defaults to the same field name in each.
NinjaRMM passes script preset variables as environment variables, so
$RMM, $Description, $RMMScriptPath, and the two custom field names
all need to be accessed via $env:VarName. Both scripts now reference
$env: at every use site so the source is obvious from any line.

Defaults for the custom field name variables are set at the top by
writing to $env: directly, so the rest of the script can keep using
the $env: form consistently.

Also fixed an inverted null check inherited from script-template-
powershell.ps1 that read 'if RMMScriptPath is null, build the log path
from $RMMScriptPath' (which would have produced a path starting with
a backslash). The check is now '-not IsNullOrEmpty'.

NOTE: This diverges from the documented repo convention in CLAUDE.md
and script-template-powershell.ps1, both of which use bare $RMM. The
existing pattern only works in true RMM mode if NinjaRMM also injects
preset variables as PowerShell session variables, which is unverified.
Worth a separate cleanup pass on the template and existing scripts.
…mdlets

Reported failure from a real user-context run:
  Failed to start ninjarmm-cli.
  Unable to find ninjarmm-cli.exe.
  Updated NinjaRMM context field 'googleChromeRemoteDesktopContextFoundIn':
    'Unable to find ninjarmm-cli.exe.' -> 'Unable to find ninjarmm-cli.exe.'

The Ninja-Property-Get and Ninja-Property-Set cmdlets only work from
SYSTEM context (they shell out to ninjarmm-cli.exe which is in a
SYSTEM-only path). When called from user context they printed the
error string to stdout, which the read-merge-write helper happily
captured as the existing field value and 'wrote back' to the same
broken value.

Architecture change:
  - User-context script no longer calls any Ninja cmdlets. It writes
    a presence-only state file at
    $env:GoogleChromeRemoteDesktopStateDir\:USERNAME.txt when CRD
    is detected, removes it when not. File mtime is the timestamp.
  - System-context script is now the SOLE writer to NinjaRMM. It runs
    its own system-side checks (HKLM, Program Files, service, process)
    and ALSO reads the per-user state file directory, aggregating
    both signals into a single boolean and a context string.
  - System script purges stale state files older than
    $env:GoogleChromeRemoteDesktopStateMaxAgeDays (default 90).
  - System script defensively checks WindowsIdentity.IsSystem before
    calling Ninja cmdlets, so a misconfigured deployment falls back
    to logging instead of writing garbage to the custom field.
  - The Update-CRDContextField read-merge-write helper is gone.
    Single writer means single source of truth, no race conditions,
    no merge logic needed.

Default state directory: C:\ProgramData\DTC\google-chrome-remote-desktop-state
ProgramData allows authenticated users to create files with inherited
permissions, so each user can write their own state file without
needing the directory pre-provisioned with custom ACLs.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
app-google-chrome/chrome-remote-desktop-detect-system.ps1 (1)

228-240: ⚠️ Potential issue | 🟠 Major

Use the supported Ninja field-write syntax.

Both Ninja-Property-Set calls still use -Name/-Value. If the agent only exposes the documented positional form, the boolean write and the shared-context update will both fail in RMM mode. Please make the same change in the companion user script.

Proposed fix
-        Ninja-Property-Set -Name $FieldName -Value $newValue
+        Ninja-Property-Set $FieldName $newValue
@@
-        Ninja-Property-Set -Name $env:CustomFieldGoogleChromeActiveBoolean -Value $result
+        Ninja-Property-Set $env:CustomFieldGoogleChromeActiveBoolean $result
What is the documented PowerShell syntax for NinjaOne/NinjaRMM `Ninja-Property-Set`? Does it support `-Name` / `-Value`, or only positional arguments like `Ninja-Property-Set <fieldName> <value>`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect-system.ps1` around lines 228 -
240, The Ninja-Property-Set calls are using named parameters (-Name / -Value)
which the agent may not support; replace both occurrences so they call
Ninja-Property-Set with positional arguments (field name first, value second)
instead of -Name/-Value — update the call that currently uses Ninja-Property-Set
-Name $FieldName -Value $newValue (the shared-context update in the try/catch)
and the call that uses Ninja-Property-Set -Name
$env:CustomFieldGoogleChromeActiveBoolean -Value $result (the RMM-only boolean
write), and make the identical change in the companion user script so both use
Ninja-Property-Set <fieldName> <value> style invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app-google-chrome/chrome-remote-desktop-detect-system.ps1`:
- Around line 207-233: The read-modify-write in the RemoteContext merge (uses
Ninja-Property-Get, Ninja-Property-Set, FieldName, ThisContext, IsActive) is not
atomic and races between the boot and user scripts; fix by making the update
atomic or eliminating concurrent writers: either (A) switch to separate
per-context fields (e.g., RemoteContext_System and RemoteContext_User) so each
script only writes its own key, or (B) implement a compare-and-swap retry loop
in both chrome-remote-desktop-detect-system.ps1 and
chrome-remote-desktop-detect-user.ps1 that reads current via Ninja-Property-Get,
computes newValue, then attempts to write only if the current value is unchanged
(retry on mismatch/backoff), or alternatively designate one authoritative writer
for merging and have the other script only set a flag the authoritative writer
consumes.

In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1`:
- Around line 110-160: The detection currently only checks the active user's
hive (Test-CRDInstalledHKCU and Test-CRDUserAppData) and then sets a
machine-level result ($isActive/$result), which can clear a device-level "user"
finding when a different profile on the same device still has CRD; update the
logic to aggregate across all local user profiles instead of using only the
current HKCU/LOCALAPPDATA: iterate relevant user SIDs under HKU and each user's
LocalAppData folders to call the equivalent checks (or a helper that accepts a
hive/path) and treat the machine as active if any profile yields true, ensuring
$isActive/$result is derived from the aggregated per-profile results rather than
a single logged-in user scan (modify Test-CRDInstalledHKCU/Test-CRDUserAppData
or add a new enumerator function to perform profile-wide checks).
- Around line 60-76: The script currently writes logs to $env:LOCALAPPDATA for
both interactive and RMM paths; change it to follow the template contract: keep
the Read-Host validation loop and set/ensure $env:Description is captured, then
set $LogPath to "$env:WINDIR\logs\$ScriptLogName" in the interactive branch
(when $env:RMM -ne "1"); in the RMM branch (when $env:RMM -eq "1") set $LogPath
to "$env:RMMScriptPath\logs\$ScriptLogName" but fall back to
"$env:WINDIR\logs\$ScriptLogName" if $env:RMMScriptPath is null/empty; update
references to $env:LOCALAPPDATA to $env:WINDIR only for these log path
assignments while keeping the existing Read-Host loop and $env:Description
handling.

---

Duplicate comments:
In `@app-google-chrome/chrome-remote-desktop-detect-system.ps1`:
- Around line 228-240: The Ninja-Property-Set calls are using named parameters
(-Name / -Value) which the agent may not support; replace both occurrences so
they call Ninja-Property-Set with positional arguments (field name first, value
second) instead of -Name/-Value — update the call that currently uses
Ninja-Property-Set -Name $FieldName -Value $newValue (the shared-context update
in the try/catch) and the call that uses Ninja-Property-Set -Name
$env:CustomFieldGoogleChromeActiveBoolean -Value $result (the RMM-only boolean
write), and make the identical change in the companion user script so both use
Ninja-Property-Set <fieldName> <value> style invocation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 980f6add-1c39-4dd1-b73e-33622da1edd0

📥 Commits

Reviewing files that changed from the base of the PR and between a701b61 and bcc3692.

📒 Files selected for processing (2)
  • app-google-chrome/chrome-remote-desktop-detect-system.ps1
  • app-google-chrome/chrome-remote-desktop-detect-user.ps1

Comment thread app-google-chrome/chrome-remote-desktop-detect-system.ps1 Outdated
Comment on lines +60 to +76
if ($env:RMM -ne "1") {
$ValidInput = 0
while ($ValidInput -ne 1) {
$env:Description = Read-Host "Please enter the ticket # and/or your initials for audit trail"
if ($env:Description) {
$ValidInput = 1
} else {
Write-Host "Invalid input. Please try again."
}
}
$LogPath = "$env:LOCALAPPDATA\dtc-logs\$ScriptLogName"
} else {
if (-not [string]::IsNullOrEmpty($env:RMMScriptPath)) {
$LogPath = "$env:RMMScriptPath\logs\$ScriptLogName"
} else {
$LogPath = "$env:LOCALAPPDATA\dtc-logs\$ScriptLogName"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep the user script on the standard transcript path contract.

Interactive mode and the RMM fallback now write under %LOCALAPPDATA%\dtc-logs, but the PowerShell template requires %WINDIR%\logs for interactive runs and $RMMScriptPath\logs with a %WINDIR%\logs fallback in RMM mode. This divergence will break the repo's documented log discovery and RMM-simulation workflow unless the template is changed at the same time. As per coding guidelines, "Input handling must detect $RMM (1 for RMM, undefined for interactive); interactive uses Read-Host with validation loop; RMM uses pre-set variables; always capture $Description; set $LogPath to %WINDIR%\logs in interactive, and $RMMScriptPath\logs (fallback to %WINDIR%\logs) in RMM".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1` around lines 60 -
76, The script currently writes logs to $env:LOCALAPPDATA for both interactive
and RMM paths; change it to follow the template contract: keep the Read-Host
validation loop and set/ensure $env:Description is captured, then set $LogPath
to "$env:WINDIR\logs\$ScriptLogName" in the interactive branch (when $env:RMM
-ne "1"); in the RMM branch (when $env:RMM -eq "1") set $LogPath to
"$env:RMMScriptPath\logs\$ScriptLogName" but fall back to
"$env:WINDIR\logs\$ScriptLogName" if $env:RMMScriptPath is null/empty; update
references to $env:LOCALAPPDATA to $env:WINDIR only for these log path
assignments while keeping the existing Read-Host loop and $env:Description
handling.

Comment thread app-google-chrome/chrome-remote-desktop-detect-user.ps1
Nate Smith added 2 commits April 7, 2026 14:25
Reverts the over-engineered ProgramData per-user state file approach
in favor of what was actually asked for: a single shared JSON at
C:\Users\Public\DTC\rmm-db\google-chrome-remote-desktop-user-active.json
mapping username -> ISO timestamp.

User script:
  - Detects HKCU + %LOCALAPPDATA%
  - Reads the JSON, adds/updates own entry if active, removes own
    entry if not, writes JSON back
  - No NinjaRMM cmdlet calls. No staleness checks. No defensive
    IsSystem checks.

System script (sole writer to NinjaRMM):
  - Detects HKLM + Program Files + chromoting service +
    remoting_host process
  - Reads the JSON to learn which users are active
  - Writes 3 NinjaRMM custom fields:
      googleChromeRemoteDesktopDetected (boolean 1/0)
      googleChromeRemoteDesktopContextFoundIn (text:
        'System' / 'User' / 'User + System')
      googleChromeRemoteDesktopFoundDetails (HTML: pretty list of
        system hits and active usernames with last-seen timestamps)

All custom field names are configurable via $env: variables for
deployment flexibility. Defaults match the camelCase naming convention
already in use.
Script changes:
  - Add required $env:OrgName variable to both CRD scripts. Hard-fail
    in RMM mode if missing, prompt interactively. Used to namespace
    the shared state file under %PUBLIC%\<OrgName>\rmm-db\ for
    white-label deployability across MSPs.
  - Drop the now-redundant $env:GoogleChromeRemoteDesktopUserStatePath
    override variable. The path is computed once at the top into a
    local $UserStatePath from $env:PUBLIC + $env:OrgName.
  - User-context script also namespaces its log directory by OrgName:
    $env:LOCALAPPDATA\:OrgName-logs
CLAUDE.md update:
  - Document the $env: convention for reading RMM-supplied variables
    (NinjaRMM passes presets as environment variables, bare $RMM
    silently fails in true RMM mode)
  - New 'Application Detection Patterns' section covering all six
    install/runtime vectors (HKLM, HKCU, Program Files, %LOCALAPPDATA%,
    Windows service, running process)
  - New 'NinjaRMM Custom Field Patterns' section covering field types
    (Boolean, Text, HTML, Multi-line) and the SYSTEM-only Ninja-Property
    cmdlet caveat
  - New 'Cross-Context Detection (User + System Split)' section
    documenting the dual-script + shared JSON pattern, the
    $env:PUBLIC\:OrgName\rmm-db\ convention, JSON-vs-SQLite
    rationale, and required $env:OrgName variable
  - Updated 'Testing Scripts' section to use $env:RMM=1 instead of
    bare $RMM=1
  - Bundled with the existing in-progress CLAUDE.md cleanup that was
    sitting in working tree (verbose Git Workflow section removed in
    favor of the condensed version)

README.md rewrite:
  - Expanded from minimal stub to a proper entry-point document
  - Quick start for both interactive and RMM execution
  - Repository structure table
  - Script conventions including the $env: pattern
  - Cross-context detection pattern with the canonical CRD example
  - Field type table for NinjaRMM custom fields
  - Points to CLAUDE.md and CONTRIBUTING.md for deeper details

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app-google-chrome/chrome-remote-desktop-detect-user.ps1 (1)

215-220: Consider the impact of failed state file writes.

If Write-CRDUserState fails, the script logs the error but still exits with the detection result ($result). This means the system script won't see this user's activity even though CRD was detected. The current behavior is acceptable for logging purposes, but consider whether a write failure should be more prominently flagged.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1` around lines 215 -
220, The write of the CRD user state file may fail silently while the script
still returns the detection result; update the catch block around
Write-CRDUserState (referencing Write-CRDUserState, $UserStatePath, $state, and
$result) to escalate the failure: log the error as now, then set a distinct
failure indicator (e.g. set $result to a non-zero/error value or set a global
flag like $CRDStateWriteFailed) and exit with a non-zero exit code (or otherwise
surface the failure to the caller) so callers can detect that state persistence
failed.
README.md (1)

132-139: Add language specifiers to fenced code blocks.

Markdownlint flags these code blocks as missing a language identifier. Since they display file paths rather than executable code, use text or plaintext to satisfy linters and signal intent.

📝 Suggested fix
-```
+```text
 category/<thing>-detect-system.ps1   # runs as SYSTEM, daily + boot
 category/<thing>-detect-user.ps1     # runs in user context, at login

Shared state file:
- +text
$env:PUBLIC$env:OrgName\rmm-db<thing>-user-active.json

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 132 - 139, The fenced code blocks in the README lack
language specifiers which markdownlint flags; update both code fences to include
a language like "text" (or "plaintext") so they read as ```text ... ``` for the
block containing "category/<thing>-detect-system.ps1   # runs as SYSTEM, daily +
boot" / "category/<thing>-detect-user.ps1     # runs in user context, at login"
and for the Shared state file block containing
"$env:PUBLIC\$env:OrgName\rmm-db\<thing>-user-active.json".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1`:
- Around line 203-205: The timestamp currently uses local time but appends a 'Z'
(UTC) suffix via Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'; change the timestamp
generation in the $isActive block so it emits an actual UTC timestamp (e.g.,
convert the current time to UTC and format it as ISO 8601 with the trailing 'Z')
before assigning to $state[$env:USERNAME] and writing the host message; update
the Get-Date usage referenced in that block to use a UTC conversion (or
PowerShell's UTC option) so the 'Z' suffix is accurate.

---

Nitpick comments:
In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1`:
- Around line 215-220: The write of the CRD user state file may fail silently
while the script still returns the detection result; update the catch block
around Write-CRDUserState (referencing Write-CRDUserState, $UserStatePath,
$state, and $result) to escalate the failure: log the error as now, then set a
distinct failure indicator (e.g. set $result to a non-zero/error value or set a
global flag like $CRDStateWriteFailed) and exit with a non-zero exit code (or
otherwise surface the failure to the caller) so callers can detect that state
persistence failed.

In `@README.md`:
- Around line 132-139: The fenced code blocks in the README lack language
specifiers which markdownlint flags; update both code fences to include a
language like "text" (or "plaintext") so they read as ```text ... ``` for the
block containing "category/<thing>-detect-system.ps1   # runs as SYSTEM, daily +
boot" / "category/<thing>-detect-user.ps1     # runs in user context, at login"
and for the Shared state file block containing
"$env:PUBLIC\$env:OrgName\rmm-db\<thing>-user-active.json".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2221d824-6d30-43a1-830e-0ac3caf4ade5

📥 Commits

Reviewing files that changed from the base of the PR and between bcc3692 and 3decd48.

📒 Files selected for processing (4)
  • CLAUDE.md
  • README.md
  • app-google-chrome/chrome-remote-desktop-detect-system.ps1
  • app-google-chrome/chrome-remote-desktop-detect-user.ps1
✅ Files skipped from review due to trivial changes (2)
  • CLAUDE.md
  • app-google-chrome/chrome-remote-desktop-detect-system.ps1

Comment on lines +203 to +205
if ($isActive) {
$state[$env:USERNAME] = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
Write-Host "Adding/updating $env:USERNAME in user state file"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Timestamp uses local time with misleading 'Z' suffix.

Line 204 uses Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ' which appends a literal 'Z' to local time. The 'Z' suffix in ISO 8601 specifically indicates UTC, but Get-Date returns local time by default. While the system script only displays these timestamps and doesn't perform time calculations, this could cause confusion in multi-timezone environments.

🔧 Suggested fix to use actual UTC
 if ($isActive) {
-    $state[$env:USERNAME] = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
+    $state[$env:USERNAME] = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
     Write-Host "Adding/updating $env:USERNAME in user state file"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app-google-chrome/chrome-remote-desktop-detect-user.ps1` around lines 203 -
205, The timestamp currently uses local time but appends a 'Z' (UTC) suffix via
Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ'; change the timestamp generation in the
$isActive block so it emits an actual UTC timestamp (e.g., convert the current
time to UTC and format it as ISO 8601 with the trailing 'Z') before assigning to
$state[$env:USERNAME] and writing the host message; update the Get-Date usage
referenced in that block to use a UTC conversion (or PowerShell's UTC option) so
the 'Z' suffix is accurate.

The previous template documented and exemplified the bare $RMM /
$Description / $RMMScriptPath pattern, which silently fails in true
NinjaRMM execution because NinjaRMM passes preset variables as
environment variables (not PowerShell session variables). The bare
references resolve to $null and the script falls through to the
interactive branch, then bails when there is no TTY for Read-Host.

Template now:
  - Reads all RMM-supplied variables via $env: at every use site
  - Compares $env:RMM against the string "1" (env vars are strings)
  - Documents the variable comment block convention with $env: prefix
  - Includes a commented-out example of the optional variable default
    pattern (write back to $env: so the rest of the script can keep
    using the $env: form consistently)
  - Mentions $env:OrgName as a required variable for cross-context
    scripts that share state with a user-context companion
  - Creates the log directory before Start-Transcript so log writes
    do not silently fail on a fresh box
  - Fixes the inverted null check inherited from the old template
    (was 'if RMMScriptPath is null, build path from $RMMScriptPath'
    which produced a path starting with a backslash)
  - Fixes the 'VARIALBES' typo in the variable header comment
  - Points to CLAUDE.md for the full pattern documentation

@Gumbees Gumbees left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Summary

Adds a Chrome Remote Desktop cross-context detection pair (system + user) with shared JSON state under %PUBLIC%\<OrgName>\rmm-db\, plus substantial CLAUDE.md and template updates documenting the env-var RMM variable convention and the cross-context detection pattern.

What works

  • The two-script pattern (*-detect-system.ps1 runs as SYSTEM with Ninja-Property-Set; *-detect-user.ps1 runs in user context, writes JSON, never touches Ninja cmdlets) is the correct architecture. SYSTEM can't see HKCU or %LOCALAPPDATA% and Ninja-Property-Set only works as SYSTEM ... documenting and enforcing the split is right.
  • $env:OrgName namespacing under %PUBLIC% is shell-friendly and avoids ACL surprises ... user processes can write, SYSTEM can read.
  • Detection coverage is broad: HKLM + HKCU uninstall (32 and 64 bit), Program Files, %LOCALAPPDATA%, chromoting service, remoting_host.exe process. The original sample missed the HKCU/LOCALAPPDATA per-user case which is the common install path for CRD.
  • CLAUDE.md additions on Ninja-Property-Set only working as SYSTEM (and silently returning the error string from Ninja-Property-Get) is a real footgun ... worth documenting at the canon level.
  • app-google-chrome/ is a reasonable new folder under the app-* convention.

Concerns

  • The CLAUDE.md change pivots the canonical pattern from $RMM to $env:RMM (string "1", not integer). That's a meaningful contract change for the rest of the repo ... PR #52 just landed using the integer/non-env pattern, and most existing scripts use $RMM -ne 1. Either the existing scripts get a sweep to convert, or the doc needs to clarify "new scripts use $env: convention, existing scripts keep $RMM until touched." Halfway-applied standards are worse than none.
  • The Git Workflow section was removed from CLAUDE.md. Intentional? It's still valuable context for new contributors and Claude Code sessions ... if it moved somewhere else (CONTRIBUTING.md?) make sure the link survives.
  • The 1064-line diff includes both the new scripts and a substantial CLAUDE.md / template rewrite. Mechanically this is two PRs glued together. Not blocking, but a future split would make each easier to review and revert independently if needed.
  • Once the env-var convention is canon, the existing template (script-template-powershell.ps1) needs to ship matching shape, or new contributors will copy stale boilerplate. Make sure the template in this PR is the canonical template moving forward.

Suggestions

  • Consider a one-line "deprecated, use env-var pattern" comment near the old $RMM example in the README so existing scripts don't get cargo-culted forward.
  • The HTML field generator using StringBuilder is nice ... worth lifting into a tiny shared helper in s3-api-lib/ or a new rmm-ninja-helpers.ps1 if more scripts adopt it.
  • Add a Ninja-Property-Set wrapper that no-ops outside RMM mode (the pattern in PR #43's Monitor-InstallerPatches uses try/catch for this) so dev runs don't error.

Verdict

COMMENT ... no blockers, scripts are solid and the documentation is genuinely useful. Main thing is the env-var convention pivot has knock-on implications for the rest of the repo that should be planned explicitly rather than left to drift.

@Gumbees Gumbees merged commit 5a15473 into main May 11, 2026
1 check passed
@Gumbees Gumbees deleted the enhancement/chrome-remote-desktop-detection branch May 11, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New functionality, improvements, or enhancements

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant