Home > Back-end >  Replace PSObj property value based on a list
Replace PSObj property value based on a list

Time:12-02

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...

CodePudding user response:

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()

CodePudding user response:

In addition mklement0's helpful answer, I was just thinking outside the box (c.q. question):
This is typical situation where I would consider to use an enum except for the fact that that the keys do not (easily) accept spaces (as in your question).

Enum ActionTypes {
    Unknown
    Blocked
    Allowed
    NoAction
    Logged
    CommandScriptRun
    Corrected
    PartiallyCorrected
    Uncorrected
    Delayed
    Deleted
    Quarantined
    Restored
    Detected
    Exonerated
    Tagged
}

$PSObj = [PSCustomObject]@{
    ComputerName      = 'MyComputer'
    Time              = [DateTime]'08/11/2022 13:57:53'
    DetectionFile     = 'MyBadFile.exe'
    ThreatName        = 'WS.Reputation.1'
    Action            = 12
}
$PSObj.Action = [ActionTypes]$PSObj.Action
$PSObj

ComputerName  : MyComputer
Time          : 8/11/2022 1:57:53 PM
DetectionFile : MyBadFile.exe
ThreatName    : WS.Reputation.1
Action        : Restored

The advantage is that you won't lose the actual action id, meaning if you e.g. insert the object back into a database, it will automatically type cast to the original integer type:

$PSObj.Action
Restored
[int]$PSObj.Action
12
  • Related