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