Server.Monitor.StoreClientInfo
Save data from client interrogation as client metadata.
This artifact listens for completions of an interrogation artifact —
typically Custom.Generic.Client.Info, your own override of
Generic.Client.Info
— and stores selected fields from the result as client metadata, so
they can be indexed and searched.
Field selection is controlled by InfoMetadata, a CSV parameter
mapping Source (the artifact source name, e.g. BasicInformation)
and Field (the column to read) to an Alias used as the metadata
key. If Alias is empty, the original Field is used. Example:
| Source | Field | Alias |
|---|---|---|
BasicInformation |
OS |
os |
BasicInformation |
Architecture |
arch |
ADStatus |
DomainJoined |
ad_joined |
MDATPHealth |
MDATPHealth.edrMachineId |
defender_id |
Field can refer to a nested field, including arrays. This means
that field names containing a period (".") are not supported.
Note
The sources referred to in InfoMetadata should only return a
single row. If more rows are returned, only the first is used.
See also
Server.Monitor.StoreClientHWInfo: sibling artifact tailored for hardware identification (extracting serial number as primary purpose)- How can I automatically add & update client metadata?: in-depth walkthrough about how to save interrogation data as client metadata
#metadata #automation
name: Server.Monitor.StoreClientInfo
author: Andreas Misje – @misje
description: |
Save data from client interrogation as client metadata.
This artifact listens for completions of an interrogation artifact —
typically `Custom.Generic.Client.Info`, your own override of
[`Generic.Client.Info`](/artifact_references/pages/generic.client.info/)
— and stores selected fields from the result as client metadata, so
they can be indexed and searched.
Field selection is controlled by `InfoMetadata`, a CSV parameter
mapping `Source` (the artifact source name, e.g. `BasicInformation`)
and `Field` (the column to read) to an `Alias` used as the metadata
key. If `Alias` is empty, the original `Field` is used. Example:
| Source | Field | Alias |
| ------ | ----- | ----- |
| `BasicInformation` | `OS` | `os` |
| `BasicInformation` | `Architecture` | `arch` |
| `ADStatus` | `DomainJoined` | `ad_joined` |
| `MDATPHealth` | `MDATPHealth.edrMachineId` | `defender_id` |
`Field` can refer to a nested field, including arrays. This means
that field names containing a period (".") are not supported.
> [!NOTE]
> The sources referred to in `InfoMetadata` should only return a
> single row. If more rows are returned, only the first is used.
## See also
- [`Server.Monitor.StoreClientHWInfo`](/exchange/artifacts/pages/server.monitor.storeclienthwinfo/):
sibling artifact tailored for hardware identification (extracting
serial number as primary purpose)
- [How can I automatically add & update client metadata?](/knowledge_base/tips/automating_metadata/):
in-depth walkthrough about how to save interrogation data as
client metadata
#metadata #automation
type: SERVER_EVENT
parameters:
- name: InterrogationArtifact
type: regex
description: |
Name of the client artifact to watch
default: Custom.Generic.Client.Info
- name: InfoMetadata
description: |
The artifact source (e.g. BasicInformation), the field to save, and an
optional alias to give the field as a metadata name
type: csv
default: |
Source,Field,Alias
BasicInformation,OS,os
BasicInformation,Architecture,arch
- name: KeepEmptyValues
description: |
If true, an empty value will be stored as metadata. If false, the metadata
will not be set at all. Note that if a metadata value was previously empty,
this will not remove that value.
type: bool
default: false
sources:
- query: |
LET ExtractSource(Artifact) = regex_replace(
re='[^/]+(?:/(?P<Source>.+))?',
replace='$1',
source=Artifact)
LET Interrogation = SELECT *
FROM foreach(row={
SELECT *
FROM watch_monitoring(artifact='System.Flow.Completion')
WHERE Flow.artifacts_with_results =~ InterrogationArtifact
},
query={
SELECT *
FROM foreach(row=Flow.artifacts_with_results,
query={
SELECT ExtractSource(Artifact=_value) AS _Source,
ClientId,
*
FROM source(client_id=ClientId,
flow_id=Flow.session_id,
artifact=InterrogationArtifact,
source=ExtractSource(Artifact=_value))
WHERE ExtractSource(Artifact=_value) IN InfoMetadata.Source
GROUP BY _Source
})
})
// With info as dict:
LET InfoDict = SELECT _value.ClientId AS ClientId,
_value._Source AS _Source,
_value AS Data
FROM items(item={ SELECT * FROM Interrogation })
LET SelectedMetadata = SELECT *
FROM foreach(row=InfoDict,
query={
SELECT ClientId,
to_dict(item={
SELECT *
FROM foreach(row=InfoMetadata,
query={
SELECT Alias || Field AS _key,
get(item=Data, member=Field) AS _value
FROM scope()
WHERE _Source = Source
AND (KeepEmptyValues OR (_value != NULL
AND len(list=str(str=_value))))
})
}) - dict(ClientId=NULL, _Source=NULL) AS Metadata
FROM scope()
})
// Set the metadata and return the dict of data, as well as the the
// client_set_metadata() result:
// Do not store an empty dict. This has proved to cause issues:
LET SetMetadata = SELECT *, if(condition=Metadata,
then=client_set_metadata(
client_id=ClientId,
metadata=Metadata),
else=false) AS Updated
FROM SelectedMetadata
SELECT *
FROM SetMetadata````