Logging PowerShell activity
Recent versions of Windows PowerShell provide several features for logging activity from PowerShell sessions. NXLog Agent can be configured to collect and parse PowerShell logs.
In addition to the sections below, see Securing PowerShell in the Enterprise, Greater Visibility Through PowerShell Logging, and PowerShell ♥ the Blue Team. Also see the Command line process auditing article on Microsoft Docs, the Windows Command Line Auditing and Sysmon chapters, which can be used to generate events for command line process creation (but not for commands executed through the PowerShell engine).
Module logging with PowerShell
Module logging, available since PowerShell 3, logs pipeline execution events for specified PowerShell modules. This feature writes Event ID 4103 events to the Microsoft-Windows-PowerShell/Operational channel.
Module logging can be enabled by setting the LogPipelineExecutionDetails
property of a module to TRUE
. Otherwise, this property can be enabled for selected
modules through Group Policy as follows.
-
Open the Group Policy MMC snapin (
gpedit.msc
). -
Go to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell and open the Turn on Module Logging setting.
-
Select Enabled. Then click the Show… button and enter the modules for which to enable logging. Use an asterisk (
*
) to enable logging for all modules.
This configuration collects all events with ID 4103 from the Windows PowerShell
Operational channel. First, the key-value pairs from the ContextInfo
field are
parsed to remove the \n
and \r\n
characters where required, after that, the
ContextInfo_
prefix is added to enhance visibility. In addition, the original
Message
and ContextInfo
fields are removed with their corresponding content
as they are available elsewhere in the output. Finally, the logs are converted
to JSON.
<Extension kvp>
Module xm_kvp
KVPDelimiter ,
KVDelimiter =
</Extension>
<Extension json>
Module xm_json
</Extension>
<Input in>
Module im_msvistalog
<QueryXML>
<QueryList>
<Query Id="0" Path="Microsoft-Windows-PowerShell/Operational">
<Select Path="Microsoft-Windows-PowerShell/Operational">
*[System[EventID=4103]]</Select>
</Query>
</QueryList>
</QueryXML>
<Exec>
if defined($ContextInfo)
{
$ContextInfo = replace($ContextInfo, "\r\n", ",");
$ContextInfo = replace($ContextInfo, "\n", ",");
$ContextInfo = replace($ContextInfo, " ", "");
kvp->parse_kvp($ContextInfo, "ContextInfo_");
delete($ContextInfo);
delete($Message);
}
json->to_json();
</Exec>
</Input>
{
"EventTime": "2020-01-29T05: 30: 45.727799-08: 00",
"Hostname": "NXLog-Server",
"Keywords": "0",
"EventType": "INFO",
"SeverityValue": 2,
"Severity": "INFO",
"EventID": 4103,
"SourceName": "Microsoft-Windows-PowerShell",
"ProviderGuid": "{A0C1853B-5C40-4B15-8766-3CF1C58F985A}",
"Version": 1,
"TaskValue": 106,
"OpcodeValue": 20,
"RecordNumber": 170,
"ActivityID": "{9C1FE60B-D6F2-0000-3316-209CF2D6D501}",
"ExecutionProcessID": 3648,
"ExecutionThreadID": 1060,
"Channel": "Microsoft-Windows-PowerShell/Operational",
"Domain": "NXLog-Server",
"AccountName": "Administrator",
"UserID": "S-1-5-21-2463765617-934790487-2583750676-500",
"AccountType": "User",
"Category": "Executing Pipeline",
"Opcode": "To be used when operation is just executing a method",
"Payload": "Update-Help has completed successfully.",
"EventReceivedTime": "2020-01-29T05: 30: 47.161585-08: 00",
"SourceModuleName": "in",
"SourceModuleType": "im_msvistalog",
"ContextInfo_Severity": "Informational",
"ContextInfo_Host Name": "ConsoleHost",
"ContextInfo_Host Version": "5.1.17763.592",
"ContextInfo_Host ID": "67d049eb-f3d6-4718-8cd2-b9dae30c4c7b",
"ContextInfo_Host Application": "C: \\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
"ContextInfo_Engine Version": "5.1.17763.592",
"ContextInfo_Runspace ID": "3145a9e1-18e3-4fa1-8700-fc78c783684b",
"ContextInfo_Pipeline ID": 6,
"ContextInfo_Command Name": "Update-Help",
"ContextInfo_Command Type": "Cmdlet",
"ContextInfo_Script Name": null,
"ContextInfo_Command Path": null,
"ContextInfo_Sequence Number": 79,
"ContextInfo_User": "NXLog-Server\\Administrator",
"ContextInfo_Connected User": null,
"ContextInfo_Shell ID": "Microsoft.PowerShell"
}
Script block logging
PowerShell 5 introduces script block logging, which records the content of all script blocks that are processed. Events with event ID 4104 are written to the Microsoft-Windows-PowerShell/Operational channel. Start and stop events can also be enabled; these events have event ID 4105 and event ID 4106 respectively.
Script block logging can be configured through Group Policy as follows.
-
Open the Group Policy MMC snapin (
gpedit.msc
). -
Go to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell and open the Turn on PowerShell Script Block Logging setting.
-
Select Enabled. Optionally, check the Log script block invocation start/stop events option (this will generate a high volume of event logs).
The following configuration collects events with IDs 4104, 4105, and 4106 from the Windows PowerShell Operational channel. Verbose-level events are excluded.
<Input script_block_logging>
Module im_msvistalog
<QueryXML>
<QueryList>
<Query Id="0" Path="Microsoft-Windows-PowerShell/Operational">
<Select Path="Microsoft-Windows-PowerShell/Operational">
*[System[(Level=0 or Level=1 or Level=2 or Level=3 or Level=4)
and ((EventID >= 4104 and EventID <= 4106))]]
</Select>
</Query>
</QueryList>
</QueryXML>
</Input>
Transcription
PowerShell provides "over-the-shoulder" transcription of PowerShell sessions with the Start-Transcript cmdlet. With PowerShell 5, system-wide transcription can be enabled via Group Policy; this is equivalent to calling the Start-Transcript cmdlet on each PowerShell session. Transcriptions are written to the current user’s Documents directory unless a system-level output directory is set in the policy settings.
**********************
Windows PowerShell transcript start
Start time: 20171030223248
Username: WIN-FT17VBNL4B2\Administrator
RunAs User: WIN-FT17VBNL4B2\Administrator
Machine: WIN-FT17VBNL4B2 (Microsoft Windows NT 10.0.14393.0)
Host Application: C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell.exe
Process ID: 4268
PSVersion: 5.1.14393.1770
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.1770
BuildVersion: 10.0.14393.1770
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
SerializationVersion: 1.1.0.1
**********************
**********************
Command start time: 20171030223255
**********************
PS C:\Users\Administrator> echo test
test
**********************
Command start time: 20171030223256
**********************
PS C:\Users\Administrator> exit
**********************
Windows PowerShell transcript end
End time: 20171030223256
**********************
System-wide transcription can be enabled through Group Policy as follows.
If system-wide transcription to a shared location is enabled, access to that directory should be limited to prevent users from viewing the transcripts of other users or computers. |
-
Open the Group Policy MMC snapin (
gpedit.msc
). -
Go to Computer Configuration > Administrative Templates > Windows Components > Windows PowerShell and open the Turn on PowerShell Transcription setting.
-
Select Enabled. Set a system-wide transcript output directory if required. Check the Include invocation headers option (this setting generates a timestamp for each command and is recommended).
This configuration reads and parses transcript files written to the
TRANSCRIPTS_DIR
directory (which should be set appropriately). Headers,
footers, and commands are parsed as separate events. $File
and $EventTime
fields are set for each event (invocation headers must be enabled for command
timestamps). $Command
and $Output
fields are added for command
events. Fields from the header entries are parsed with xm_kvp and
added to the event record. Finally, the logs are converted to JSON format and
forwarded via TCP.
The HeaderLine below must be changed if invocation headers are not enabled. See the comment in the configuration.
|
define TRANSCRIPTS_DIR C:\powershell
<Extension transcript_parser>
Module xm_multiline
# Use this if invocation headers are ON (recommended)
HeaderLine /^\*{22}$/
# Use this if invocation headers are OFF (not recommended)
#HeaderLine /^(\*{22}$|PS[^>]*>)/
<Exec>
$raw_event =~ s/^\xEF\xBB\xBF//;
if get_var('include_next_record') and $raw_event =~ /^\*{22}$/
{
set_var('include_next_record', FALSE);
$raw_event =~ s/^\*//;
}
else if $raw_event =~ /^Command start time: \d{14}$/
set_var('include_next_record', TRUE);
</Exec>
</Extension>
<Extension transcript_header_parser>
Module xm_kvp
KVPDelimiter \n
</Extension>
<Input transcription>
Module im_file
File '%TRANSCRIPTS_DIR%\\*PowerShell_transcript.*'
InputType transcript_parser
Recursive TRUE
<Exec>
# Add file name as field to event record
$File = file_name();
# Drop extra separator line events
if $raw_event =~ /^\*{22}$/
drop();
# Parse header and footer entries
else if $raw_event =~ /(?x)^\*{22}\r\n
(?<Message>Windows\ PowerShell\ transcript\ (start|end)\r\n
(?<KVP>.*))$/s
{
$KVP =~ s/\r//g;
transcript_header_parser->parse_kvp($KVP);
delete($KVP);
if ${Start time}
$EventTime = strptime(string(${Start time}), '%Y%m%d%H%M%S');
else
$EventTime = strptime(string(${End time}), '%Y%m%d%H%M%S');
}
# Parse command entries with invocation headers
else if $raw_event =~ /(?x)^\*{22}\r\n
(?<Part1>Command\ start\ time:\ (?<EventTime>\d{14})\r\n)
(?<Part2>\*{21}\r\nPS[^>]*>\ ?
(?<Command>[^\r\n]+)(\r\n(?<Output>.+))?)$/s
{
$Message = $Part1 + '*' + $Part2;
delete($Part1);
delete($Part2);
$EventTime = strptime(string($EventTime) + '', '%Y%m%d%H%M%S');
}
# Parse command entries without invocation headers
else if $raw_event =~ /(?x)^PS[^>]*>\ ?
(?<Command>[^\r\n]+)(\r\n(?<Output>.+))?$/s
$Message = $raw_event;
else log_warning('Failed to parse transcript entry');
</Exec>
</Input>
The following output shows the first two events of the log sample above.
{
"EventReceivedTime": "2017-10-30 22:32:49",
"SourceModuleName": "transcription",
"SourceModuleType": "im_file",
"File": "C:\\powershell\\\\20171030\\PowerShell_transcript.WIN-FT17VBNL4B2.LcxuCZbr.20171030223248.txt",
"Message": "Windows PowerShell transcript start\r\nStart time: 20171030223248\r\nUsername: WIN-FT17VBNL4B2\\Administrator\r\nRunAs User: WIN-FT17VBNL4B2\\Administrator\r\nMachine: WIN-FT17VBNL4B2 (Microsoft Windows NT 10.0.14393.0)\r\nHost Application: C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\PowerShell.exe\r\nProcess ID: 4268\r\nPSVersion: 5.1.14393.1770\r\nPSEdition: Desktop\r\nPSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.1770\r\nBuildVersion: 10.0.14393.1770\r\nCLRVersion: 4.0.30319.42000\r\nWSManStackVersion: 3.0\r\nPSRemotingProtocolVersion: 2.3\r\nSerializationVersion: 1.1.0.1",
"Start time": "20171030223248",
"Username": "WIN-FT17VBNL4B2\\Administrator",
"RunAs User": "WIN-FT17VBNL4B2\\Administrator",
"Machine": "WIN-FT17VBNL4B2 (Microsoft Windows NT 10.0.14393.0)",
"Host Application": "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\PowerShell.exe",
"Process ID": "4268",
"PSVersion": "5.1.14393.1770",
"PSEdition": "Desktop",
"PSCompatibleVersions": "1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.1770",
"BuildVersion": "10.0.14393.1770",
"CLRVersion": "4.0.30319.42000",
"WSManStackVersion": "3.0",
"PSRemotingProtocolVersion": "2.3",
"SerializationVersion": "1.1.0.1",
"EventTime": "2017-10-30 22:32:48"
}
{
"EventReceivedTime": "2017-10-30 22:32:56",
"SourceModuleName": "transcription",
"SourceModuleType": "im_file",
"File": "C:\\powershell\\\\20171030\\PowerShell_transcript.WIN-FT17VBNL4B2.LcxuCZbr.20171030223248.txt",
"Command": "echo test",
"EventTime": "2017-10-30 22:32:55",
"Output": "test",
"Message": "Command start time: 20171030223255\r\n**********************\r\nPS C:\\Users\\Administrator> echo test\r\ntest"
}