Understanding Script Block Logging (Part 6)

by Jun 29, 2018

This is part 6 of our mini-series covering PowerShell script block logging, and it’s time to address a final thing: when you execute very large PowerShell scripts, they are logged in chunks (parts). What’s missing is some logic that defragments the code parts:

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 {
    # store the code in this entry
    
    # if this is the first part, take it
    if ($_.Properties[0].Value -eq 1)
    {
      $code = $_.Properties[2].Value
    }
    # else, add it to the $code variable
    else
    {
      $code += $_.Properties[2].Value
    }
  
    # return the object when all parts have been processed
    if ($_.Properties[0].Value -eq $_.Properties[1].Value)
    {
      # 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 = $code
        # 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]
      }
    }
  } 
}

Essentially, this function checks whether the script consists of more than one part. If so, it adds the code to $code until the current part equals the final part. That’s it.

Twitter This Tip! ReTweet this Tip!