This is part 2 of our mini-series covering PowerShell script block logging. Today, we’ll again read the script block logging log, but this time we’ll try and get back the log data in a more useful object-oriented way:
function Get-LoggedCode { # read all raw events $logInfo = @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 } Get-WinEvent -FilterHashtable $logInfo | # take each raw set of data... ForEach-Object { # create a new object and extract the interesting # parts from the raw data to compose a "cooked" # object with useful data [PSCustomObject]@{ # when this was logged Time = $_.TimeCreated # script code that was logged Code = $_.Properties[2].Value # if code was split into multiple log entries, # determine current and total part PartCurrent = $_.Properties[0].Value PartTotal = $_.Properties[1].Value # if total part is 1, code is not fragmented IsMultiPart = $_.Properties[1].Value -ne 1 # path of script file (this is empty for interactive # commands) Path = $_.Properties[4].Value # log level # by default, only level "Warning" will be logged Level = $_.LevelDisplayName # user who executed the code (SID) User = $_.UserId } } }
When you run this code, you now have a new command named Get-LoggedCode. When you execute it, it returns objects like this:
Time : 25.05.2018 10:57:36 Code : function Get-LoggedCode { # read all raw events $logInfo = @{ ProviderName="Microsoft-Windows-PowerShell"; Id = 4104 } Get-WinEvent -FilterHashtable $logInfo | # take each raw set of data... ForEach-Object { # create a new object and extract the interesting # parts from the raw data to compose a "cooked" # object with useful data: [PSCustomObject]@{ # when this was logged: Time = $_.TimeCreated # script code that was logged: Code = $_.Properties[2].Value # if code was split into multiple log entries, # determine current and total part: PartCurrent = $_.Properties[0].Value PartTotal = $_.Properties[1].Value # if total part is 1, code is not fragmented: IsMultiPart = $_.Properties[1].Value -ne 1 # path of script file (this is empty for interactive # commands) Path = $_.Properties[4].Value # log level # by default, only level "Warning" will be logged: Level = $_.LevelDisplayName # user who executed the code (SID) User = $_.UserId } } } PartCurrent : 1 PartTotal : 1 IsMultiPart : False Path : D:sample.ps1 Level : Warning User : S-1-5-21-2012478179-265285931-690539891-1001
In our example, we added “Select-Object” to not read the entire log but just the last entry. Here, we got back the code that we just executed. This may be different for you, and here is why:
By default, script block logging only logs code considered “security relevant” (in the returned data, Level is “Warning”). PowerShell determines internally which code is considered security relevant. In tomorrow’s tip, we will explain how you enable the “Verbose” mode. When this mode is turned on, any code is logged, so your log file grows in size considerably. Here is how you can check the ratio of default (“Warning”) logs versus verbose logs (taken from our test machine):
PS> Get-LoggedCode | Group-Object Level Count Name Group ----- ---- ----- 549 Verbose {@{Time=25.05.2018 10:57:52; Code=prompt;.. 36 Warning {@{Time=25.05.2018 10:57:52; Code={...
Please note: Since log entries have a maximum size, long code is split up in chunks. The “IsMultiPart”, “PartCurrent”, and “PartTotal” properties provide information about this.