Search code examples
powershellhashtable

Replace PSObj property value based on a list


I've the following PSObj with some properties stored in an $array :

ComputerName      : MyComputer
Time              : 08/11/2022 13:57:53
DetectionFile     : MyBadFile.exe
ThreatName        : WS.Reputation.1
Action            : 12

I'm trying to replace the action ID number by it's corresponding description. I've a hashtable with the possibles reasons behind the Action ID

$ActionId = @{
    0  = 'Unknown'
    1  = 'Blocked'
    2  = 'Allowed'
    3  = 'No Action'
    4  = 'Logged'
    5  = 'Command Script Run'
    6  = 'Corrected'
    7  = 'Partially Corrected'
    8  = 'Uncorrected'
    10 = 'Delayed   Requires reboot to finish the operation.'
    11 = 'Deleted'
    12 = 'Quarantined'
    13 = 'Restored'
    14 = 'Detected'
    15 = 'Exonerated    No longer suspicious (re-scored).'
    16 = 'Tagged    Marked with extended attributes.'
}

I'm trying to parse each item of this array, and each value of the reason ID to replace the ID by the reason string

    # parse array
    foreach ($Item in $array) {
        # parse possible values
        foreach ($value in $ActionId) {
            if ($value -eq $item.Action) {
                $Item.Action = $ActionId[$value]
                $Item.Action
            }
        }

From my understanding, I'm missing the correct syntax here

$Item.Action = $ActionId[$value]

I do not get any errors, but from the debugger, I'm replacing the action property by $null with the above...


Solution

  • The immediate fix is to loop over the keys (.Keys) of your $ActionId hashtable:

    foreach ($Item in $array) {
      # parse possible values
      foreach ($value in $ActionId.Keys) {
          if ($value -eq $item.Action) {
              $Item.Action = $ActionId[$value]
              $Item.Action  # diagnostic output
          }
      }
    }
    

    Note:

    • To avoid confusion, consider renaming $value to $key.

    • Generally, note that hashtables are not enumerated in the pipeline / in looping constructs in PowerShell.

      • That is, foreach ($value in $ActionId) ... doesn't actually loop over the hashtable's entries, and is the same as $value = $ActionID)

      • If you want to enumerate a hashtable's entries - as key-value pairs of type System.RuntimeType - you would need to use the .GetEnumerator() method; in your case, however, enumerating the keys is sufficient.


    However, the simpler and more efficient solution is to test whether the $Item.Action value exists as a key in your hashtable, using the latter's .Contains() method:[1]

    foreach ($Item in $array) {
      if ($ActionId.Contains($Item.Action)) {
        $Item.Action = $ActionId[$Item.Action]
        $Item.Action  # diagnostic output
      }
    }
    

    You can further streamline this as follows, though it is conceptually a bit obscure:

    foreach ($Item in $array) {
      if ($null -ne ($value = $ActionId[$Item.Action])) {
        $Item.Action = $value
        $Item.Action  # diagnostic output
      }
    }
    
    • = is only ever PowerShell's assignment operator; for equality / non-equality comparison, -eq / -ne is required.

    • Here, an assignment to $value is indeed being performed and the assigned value then acts as the RHS of the -ne operation; in other words: you can use assignment as expressions in PowerShell.

    • If hashtable $ActionId has no key with value $Item.Action, $ActionId[$Item.Action] quietly returns $null.


    Finally - in PowerShell (Core) 7+ only - an even more concise (though not necessarily faster) solution is possible, using ??, the null-coalescing operator:

    foreach ($Item in $array) {
      $Item.Action = $ActionId[$Item.Action] ?? $Item.Action
      $Item.Action # diagnostic output
    }
    

    That is, the value of $ActionId[$Item.Action] is only used if it isn't $null; otherwise, $Item.Action, i.e. the current value, is used (which is effectively a no-op).


    [1] .ContainsKey() works too, and while this name is conceptually clearer than .Contains(), it is unfortunately not supported by PowerShell's [ordered] hashtables (System.Collections.Specialized.OrderedDictionary) and, generally speaking, not supported by other dictionary (hashtable-like types), given that the System.Collections.IDictionary interface only has .Contains()