Understanding Script Block Logging (Part 5)

by Jun 28, 2018

This is part 5 of our mini-series covering PowerShell script block logging. We are almost done, and what’s missing is a better way to read logged code. In our previous approach, the user who executed the code was returned as a cryptic SID instead of a clear-text name. Here’s a function that turns the user SID into a real name, and also uses a clever cache to speed up the SID lookup:

function Get-LoggedCode
{
  # to speed up SID-to-user translation,
  # we use a hash table with already translated SIDs
  # it is empty at first
  $translateTable = @{}

  # read all raw events
  $logInfo = @{ ProviderName="Microsoft-Windows-PowerShell" Id = 4104 }
  Get-WinEvent -FilterHashtable $logInfo | 
  # take each raw set of data...
  ForEach-Object {
    # turn SID into user
    $userSID = $_.UserId

    # if the cache does not contain the user SID yet...
    if (!$translateTable.ContainsKey($userSid))
    {
      try
      {
        # ...try and turn it into a real name, and add it
        # to the cache
        $identifier = New-Object System.Security.Principal.SecurityIdentifier($userSID)
        $result = $identifier.Translate( [System.Security.Principal.NTAccount]).Value
        $translateTable[$userSid] = $result
      }
      catch
      {
        # if this fails, use the SID instead of a real name
        $translateTable[$userSid] = $userSID
      }
    }
    else
    {
    
    }
  
    # 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 
      # take the real user name from the cache of translated
      # user names
      User = $translateTable[$userSID]
    }
  } 
}

Twitter This Tip! ReTweet this Tip!