Mixing of plugin decoders with regular expressions
I am not sure if this is possible by changing the filebeat pipeline. But you can follow this workaround to achieve something similar.
I have used command monitoring to collect Windows PowerShell event channel logs.
This is the script I am using for command monitoring.
$log = 'Microsoft-Windows-PowerShell/Operational'
$stateFile = "$env:TEMP\psh_last_time.txt"
# Determine start time
$startTime = if (Test-Path $stateFile) {
Get-Content $stateFile
} else {
(Get-Date).AddDays(-1).ToString('o')
}
# Get events
$events = Get-WinEvent -FilterHashtable @{
LogName = $log
StartTime = [datetime]$startTime
} -ErrorAction SilentlyContinue
# Output each event as a separate JSON object
if ($events) {
foreach ($event in $events) {
$event | ConvertTo-Json -Depth 5 -Compress
}
# Update the state file
(Get-Date).ToString('o') | Out-File $stateFile
}
Next, configure the Windows agent to run this script in the command monitoring. Add this at the end of the ossec.conf file of your agent.
<ossec_config>
<wodle name="command">
<disabled>no</disabled>
<tag>PowerShell_Operational_logs</tag>
<command>powershell.exe -NoProfile -ExecutionPolicy Bypass -File "C:\Users\User\Documents\psh-new-events.ps1"</command>
<interval>1m</interval>
<run_on_start>yes</run_on_start>
</wodle>
</ossec_config>
Ref: https://documentation.wazuh.com/current/user-manual/capabilities/command-monitoring/index.html
Now custom decoders to decode the user field.
In my log, I had this nested field data.Properties.Value
"Properties":[{"Value":" Severity = Informational\r\n Host Name = ConsoleHost\r\n Host Version = 5.1.26100.7462\r\n Host ID = 29900246-2e11-4d19-9660-82764f433198\r\n Host Application = C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\r\n Engine Version = 5.1.26100.7462\r\n Runspace ID = 27e0bed8-293c-4321-a82b-a9725df7f51d\r\n Pipeline ID = 251\r\n Command Name = Add-Type\r\n Command Type = Cmdlet\r\n Script Name = \r\n Command Path = \r\n Sequence Number = 48\r\n User = SAKIB-HOME\\User\r\n Connected User = \r\n Shell ID = Microsoft.PowerShell\r\n"}
I have decoded this value as a single field User = SAKIB-HOME\\\User
Add this decoder to your custom decoder file.
<decoder name="json">
<prematch>^{\s*"</prematch>
</decoder>
<decoder name="ps_child">
<parent>json</parent>
<regex>User = (\.+)\\r\.+Connected User = \.+</regex>
<order>user_name</order>
</decoder>
<decoder name="ps_child">
<parent>json</parent>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
Now add this custom rule for tigger alert for event ID 4103.
<group name="windows,powershell,">
<rule id="100301" level="5">
<decoded_as>json</decoded_as>
<field name="LogName">Microsoft-Windows-PowerShell/Operational</field>
<match>4103</match>
<description>Microsoft-Windows-PowerShell/Operational log: Event ID - 4103</description>
</rule>
</group>
After that, reload the rule engine or restart the wazuh-manager to load the new decoders and rules.
Now in ruleset test it was working fine and triggering alert, but I was having filebeat mapping error for some fields, and I processed those fields from filebeat before forwarding it indexer.
For that,
Backup the existing pipeline file:
cp /usr/share/filebeat/module/wazuh/alerts/ingest/pipeline.json /tmp/pipeline.json
Open the pipeline file:
vi /usr/share/filebeat/module/wazuh/alerts/ingest/pipeline.json
Locate the processors section, and insert the following snippet after the "date_index_name" section and before the first remove block.
{
"script": {
"if": "ctx?.location == 'command_PowerShell_Operational_logs' && ctx?.data?.UserId != null && ctx.data.UserId instanceof Map",
"lang": "painless",
"source": "if (ctx.data.UserId.containsKey('AccountDomainSid')) { ctx.data.UserId.remove('AccountDomainSid'); }"
}
},
{
"script": {
"if": "ctx?.location == 'command_PowerShell_Operational_logs' && ctx?.data?.Properties != null && ctx.data.Properties instanceof List",
"lang": "painless",
"source": "for (def p : ctx.data.Properties) { if (p != null && p instanceof Map && p.containsKey('Value') && p.Value != null) { p.Value = p.Value.toString(); } }"
}
},
Check the screenshot for reference.
Save the configuration and apply the pipeline:
filebeat setup --pipelines
systemctl restart filebeat
After that, try to reproduce event 1403 and check if you are able to see the alert with the desired fields in your dashboards.
Check the screenshot for reference.
You can do furter modification based on your needs. Let me know if this works for you.

The Security UserID value is present in the alerts, same as the event viewer, check the data.UserId.Value filed
I am sharing the screenshot for your reference.

I am using these rules.
<group name="windows,powershell,">
<rule id="100301" level="5">
<decoded_as>json</decoded_as>
<field name="LogName">Microsoft-Windows-PowerShell/Operational</field>
<regex>"Id":4103</regex>
<description>Microsoft-Windows-PowerShell/Operational log: Event ID - 4103</description>
</rule>
<rule id="100302" level="5">
<decoded_as>json</decoded_as>
<field name="LogName">Microsoft-Windows-PowerShell/Operational</field>
<regex>"Id":4104</regex>
<description>Microsoft-Windows-PowerShell/Operational log: Event ID - 4104</description>
</rule>
</group>
After checking this behavior, I found out that the lines below are intended to extract the userID field for the event channel logs with eventchannel decoder, and the field is incorrectly referenced as a label value and, as it is an attribute, is never extracted.
} else if (!strcmp(child_attr[p]->element, "Channel")) {
cJSON_AddStringToObject(json_system_in, "channel", child_attr[p]->content);
if(child_attr[p]->attributes && child_attr[p]->values && !strcmp(child_attr[p]->values[0], "UserID")){
cJSON_AddStringToObject(json_system_in, "userID", child_attr[p]->values[0]);
}
} else if (!strcmp(child_attr[p]->element, "Security")) {
if(child_attr[p]->attributes && child_attr[p]->values && !strcmp(child_attr[p]->values[0], "UserID")){
cJSON_AddStringToObject(json_system_in, "securityUserID", child_attr[p]->values[0]);
}
https://github.com/wazuh/wazuh/blob/4.14.3/src/analysisd/decoders/winevtchannel.c#L199
Referencing the userID as an attribute is enough to extract the field correctly:
EX:
} else if (!strcmp(child_attr[p]->element, "Channel")) {
cJSON_AddStringToObject(json_system_in, "channel", child_attr[p]->content);
if(child_attr[p]->attributes && child_attr[p]->values && !strcmp(child_attr[p]->attributes[0], "UserID")){
cJSON_AddStringToObject(json_system_in, "userID", child_attr[p]->values[0]);
}
} else if (!strcmp(child_attr[p]->element, "Security")) {
if(child_attr[p]->attributes && child_attr[p]->values && !strcmp(child_attr[p]->attributes[0], "UserID")){
cJSON_AddStringToObject(json_system_in, "securityUserID", child_attr[p]->values[0]);
}
This is a bug, and we are aware of it. We are already working on our newer version 5.0 with a new rule engine, and these issues will be resolved on the new rule engine.
For now, you have two options: you can either install Wazuh Manager from the source code and modify these and recomplaine package using the modified source code.
https://documentation.wazuh.com/current/deployment-options/wazuh-from-sources/wazuh-server/index.html
Or you can follow the workaround with the script I shared before.