Write custom field values from a script
Set custom field values on the device a script runs on with Set-NetLockField or nl_field_set, so inventory data such as LAPS, Entra join or BitLocker state is kept without a sensor.
Write custom field values from a script
A script can store values in custom fields of the device it runs on. This keeps inventory data that no built-in sensor covers, such as the LAPS status, the Entra ID join state or the BitLocker protector types, up to date from a recurring job. Reading values in a script is described in Script variables and custom fields in scripts.
Note: Write-back from jobs and sensors needs the Comm agent 3.2.0.4 or newer. Write-back from the remote shell (classic, bulk, real-time terminal) and from the public API works with any agent version, because the server evaluates the output itself. Deploy the web console first, then the server, then the agents.
Before you start
- A custom field definition with a
Manualfield of typeText,MultilineorSecretwhose key the script will write, for examplelaps_status. The field does not need to be inheritable. - The script permissions listed in Script variables and custom fields in scripts. No extra permission is needed for the write-back itself: the device writes its own value.
The helper per shell
Call the helper with the field key and the value. The helper is defined in the prelude line of the script whenever the script mentions it; see What happens on the device.
| Shell | Call |
|---|---|
| PowerShell | Set-NetLockField -Key 'laps_status' -Value $status |
| Bash, Zsh | nl_field_set laps_status "$status" |
| Python3 | nl_field_set('laps_status', status) |
The Insert variable menu of the script editor lists the three calls under Write back to custom fields and inserts them at the cursor. The Preview for device dialog marks a script that writes back and shows the helper in the prelude line.
A script that calls the helper and refers to no variable gets a prelude line with the helper only. A script that does neither is delivered unchanged.
The marker line. The helper prints one line per call to standard output:
NETLOCK_FIELD_SET:<key>:<base64 of the UTF-8 value>You can print that line yourself, from any language, as long as it starts at the beginning of a line. The line is taken out of the output before the output is logged, matched or shown, so it never appears in an event or in an API result.
What is accepted
The server checks every value before it is stored:
- Key. Letters, digits, underscores and hyphens, 1 to 255 characters. The key is matched to the field the same way a variable is resolved: written in upper case with every other character turned into an underscore, so a script may write
AV_OU_IDfor a legacy keyAV-OU ID. The value is stored under the key as spelt in the definition. When two definitions share a key, the value goes to the definition that was created first, the one a script also reads from. - Field. A
Manualfield of typeText,MultilineorSecret. AJob Resultfield, aSQL Selectfield and a manualTablefield are rejected. - Size. At most 64 KB per value and at most 50 values per run. Values beyond the limit are rejected.
- Duplicates. When a run writes the same key several times, the last value wins.
A rejected value is reported back to the agent and logged with the key and the reason (unknown_key, not_manual, too_large, invalid_key, or invalid_base64 for a hand-written marker line); the value itself is never logged. Rejections are not shown in the console. Check the agent log (jobs and sensors) or the server log (remote shell) when a value does not arrive.
What is stored. The value lands on the device with the source script; the device page shows an icon next to the value whose tooltip reads Set by script on <date>, and the public API returns "source": "script". A Secret field is stored encrypted like a value entered in the console. A write-back sets the device's own value; it never writes to a group, location, tenant or global level.
Policy re-sync. The device is marked to fetch its policy again only when the stored value actually changed. A recurring job that writes the same value every hour updates the timestamp and the source, but does not trigger a re-sync.
Where it works, and the limits
| Path | Who evaluates the output | Agent needed | Notes |
|---|---|---|---|
| Jobs | Comm agent | 3.2.0.4 or newer | Marker lines are removed before the job result is logged and put into the event. Hidden jobs write as well. |
| Script sensors and their action scripts | Comm agent | 3.2.0.4 or newer | Marker lines are removed before the expected result is matched and before the action history and the event are written. |
Sensor live test (console and POST /v1/sensors/{id}/test) | Comm agent | 3.2.0.4 or newer | Nothing is written. The result lists Would set: <keys> under further information. |
| Remote shell, classic mode | Server | Any | Also for Run as user. The cleaned output goes to the dialog and the Remote Shell event. |
| Bulk remote shell | Server | Any | Per device. |
Public API run-command and run-script | Server | Any | The action record and the Remote Shell event hold the cleaned output. |
| Real-time terminal | Server (and a current Remote Agent) | Any | See the limits below. CMD has no helper. |
Older Comm agents. With an agent older than 3.2.0.4 the marker lines of a job or sensor stay in the output and are visible in the event; nothing is stored.
Timeout. A job or sensor script that runs into its timeout returns only the timeout message. Its output, and with it every value it wrote, is lost for that run. Keep write-back scripts well inside their timeout.
Expected result. The expected-result pattern of a script sensor is matched against the cleaned output. A pattern that looks for NETLOCK_FIELD_SET: never matches.
PowerShell scope. Set-NetLockField is a function in the script's scope. It is not visible inside ForEach-Object -Parallel, Start-Job or Invoke-Command (a different runspace). Collect the value in the main scope and call the helper there.
Captured output. The PowerShell helper writes through [Console]::Out, not through the pipeline, so $x = Set-NetLockField ... or Set-NetLockField ... | Out-Null do not swallow the marker line. The Bash and Python helpers print to standard output; do not redirect it away.
Real-time terminal
The real-time terminal streams the shell's output. The server watches the stream for marker lines, removes them and stores the values; a current Remote Agent removes them on the device already. Two limits follow from the terminal:
- Long values on Windows. The Windows terminal can break a line that is longer than the terminal width into several lines. A marker line with a long value can then be torn: the value is rejected as invalid base64 and the remainder appears in the terminal. Keep values written from the terminal short (a status word, an id), or use the classic mode or a job for long values.
CMDhas no helper. Write-back works in the PowerShell, Bash, Zsh and Python terminals only.
Two details worth knowing: a prompt that happens to end in the first letters of the marker is shown with the next output instead of immediately, and there is no timeout in the terminal, so a marker line without a line break stays back until more output follows or the session ends.
While a template runs as a script in the terminal, the rendered script, prelude line included, is staged as a file in the Remote Agent's program data under Scripts\RealTime on the device and removed when the session ends; leftovers of a crashed session are removed when the agent starts. The terminal always runs as SYSTEM or root, so a value written from it is the device's value.
Examples
LAPS status (Windows, PowerShell). Records which LAPS flavour is present.
$status = if (Get-Module -ListAvailable -Name LAPS) { 'windows-laps' }
elseif (Test-Path 'HKLM:\SOFTWARE\Policies\Microsoft Services\AdmPwd') { 'legacy-laps' }
else { 'not-installed' }
Set-NetLockField -Key 'laps_status' -Value $status
Write-Output "LAPS: $status"Entra ID join state (Windows, PowerShell). Parses dsregcmd.
$dsreg = dsregcmd /status
$joined = if ($dsreg -match '^\s*AzureAdJoined\s*:\s*YES') { 'joined' } else { 'not-joined' }
$device = ($dsreg | Select-String '^\s*DeviceId\s*:\s*(\S+)').Matches.Groups[1].Value
Set-NetLockField -Key 'entra_join_state' -Value $joined
Set-NetLockField -Key 'entra_device_id' -Value $deviceBitLocker protector status (Windows, PowerShell). Records whether the system drive is protected and which protector types it has.
$volume = Get-BitLockerVolume -MountPoint $env:SystemDrive
$protectors = ($volume.KeyProtector | ForEach-Object { $_.KeyProtectorType }) -join ','
Set-NetLockField -Key 'bitlocker_protection' -Value "$($volume.ProtectionStatus)"
Set-NetLockField -Key 'bitlocker_protectors' -Value $protectorsNote: Do not write recovery passwords into custom fields. Recovery keys are escrowed by the BitLocker management of NetLock RMM and are readable only through its own, audited paths.
Distribution name (Linux, Bash).
nl_field_set os_pretty_name "$(. /etc/os-release && printf '%s' "$PRETTY_NAME")"Python version (any platform, Python3).
import platform
nl_field_set('python_version', platform.python_version())Each example runs as a recurring job (for example Every X hours) attached to the policy of the devices; see Write, test, and schedule a PowerShell script. Flag the job Hidden when its only purpose is to feed the field.
Verify it worked
- Run the script once through the classic remote shell. The output shows your
Write-Outputlines but noNETLOCK_FIELD_SET:line. - Open the device page. The field shows the value and an icon with the tooltip
Set by script on <date>. GET /v1/devices/{id}/custom-fieldsreturns the value with"source": "script".
Troubleshooting
- The marker line is visible in the job event. The Comm agent is older than 3.2.0.4. Update the agent.
- The value does not arrive and the agent log says
unknown_key. No definition has a manual field with that key, or the key is misspelt.not_manualmeans the field is aJob Result,SQL SelectorTablefield. - The value does not arrive after a run through the real-time terminal on Windows. The value was longer than the terminal line. Use the classic mode.
- The device does not pick up a changed job. The value was the same as before; only a changed value marks the device for a re-sync.
Related
- Script variables and custom fields in scripts — reading values, the prelude line, secrets.
- Chapter 8.4 — Custom Fields — field types, keys and levels.
- Chapter 3.3 — Device detail view — the custom fields on the device page.
- X.9 — Public API — reading the values back with
sourceandlevel.
Script variables and custom fields in scripts
Refer to device data and custom field values from a script with tokens, so one script serves every tenant, and understand how the values reach the device.
System overview & licensing
System health monitoring, licence status and the Members Portal API key for self-hosted deployments.