2

Using piping or other sort of inline notation, I'd like to "select" certain values in a hash table straight through to the result, while changing or setting others.

Hash Table

I'm starting with a hash table (which, frankly is being used like an object), like the following:

>$hash

Name               Value
----               -----
System             server-1
Job                Microsoft.PowerShell.Commands.PSWmiJob
Result

Is there a way to use the Select-Object cmdlet (or anything like it) to set the value of certain properties and pass through others? I'd like to end up with this:

>$hash

Name               Value
----               -----
System             server-1
Job                Microsoft.PowerShell.Commands.PSWmiJob
Result             Good

If I didn't need/want it to be inline, I simply set the value of Result:

$hash.Result = "good"

For an inline solution, I could always use Foreach-Object like this:

$hash | Foreach-Object {@{System=$_.System;Job=$_.Job;Result="Good"}}

or just directly refer to $hash in a sub-expression:

${@{System=$hash.System;Job=$hash.Job;Result="Good"}}

However, it seems nonsensical to use Foreach-Object when I'm only processing the one hash table, and quite verbose to mention properties twice that I just want to pass through anyways, and then can be unwieldy if my $hash variable is $aSignificantlyLongerHashTableVariableName.

If I was using a Powershell Object...

If I were using an object like this:

>$obj

System        Job                                      Result
------        ---                                      ---
server-1      Microsoft.PowerShell.Commands.PSWmiJob             

I would use the select-object Cmdlet like this:

>$obj | Select-Object System, Job, @{Name="Result";Expression="Good"}

which would result in

>$obj

System        Job                                      Result
------        ---                                      ---
server-1      Microsoft.PowerShell.Commands.PSWmiJob   Good

Hashtable and PSObject have some similar aspects (something that contains other things in a named fashion), but AFAIK, they're incompatible - Is there an alternative closer to Select-Object for what I'm trying to do here?

Code Jockey
  • 6,611
  • 6
  • 33
  • 45

4 Answers4

2

Being strict about having this as a one-liner that has no setup code beforehand and doesn't modify the source hash, something like this could do the job:

($hash + @{ 'Result' = 'Good' }).GetEnumerator() | ? {$_.Key -in 'Result','System','Job'}

If you're going to be adding something Result = Good regularly, it may be handy to have your little extension bits saved:

$good = @{ 'Result' = 'Good' }

And then your one-liner becomes:

($hash + $good).GetEnumerator() | ? {$_.Key -in 'Result','System','Job'}

Edit: As you noted in the comments below, that one-liner will die if Result exists in the hash. This version is a little safer/more explicit, and more directly maps to the spirit of what you described with Select-Object on PSObjects:

(($hash).GetEnumerator() | ? {$_.Key -in 'System','Job'}) + @{'Result' = 'Good'}

Still not the prettiest thing in the world, but it does the job.

ajk
  • 4,473
  • 2
  • 19
  • 24
  • Without trying it in code, wouldn't this choke if there was already a key named `Result`? still, your answer is the closest to useful so far, so thanks! – Code Jockey Mar 03 '16 at 15:46
  • 1
    Closest to useful, ha. That's the dream! My example would definitely choke if `Result` already exists. In that case, you'd shuffle it around to something like `(($hash).GetEnumerator() | ? {$_.Key -in 'System','Job'}) + @{'Result' = 'Good'}` – ajk Mar 03 '16 at 15:50
  • 1
    I don't mind in theory if it modifies the hash, as long as it doesn't modify certain selected values. Obviously one drawback might be if it's 10 or more times slower to e.g., put it into an object, modify it, then put it back in a hash table. ---- My question did specify starting with a `Result` key, so I'd probably consider the expression in your comment to be an answer, if it was put into your answer body. – Code Jockey Mar 03 '16 at 15:54
  • 1
    I've edited the answer (but left the original bits in there because I think stepping through it is useful). After kicking it around with you and thinking about it a bit more, that last one-liner does feel like the right call. Not modifying the source is a good idea, and proactively avoiding key clashes just makes sense. – ajk Mar 03 '16 at 15:59
0

To parse single valued object use this:

$hash = @{}
$hash.Add("System",$obj.System)
$hash.Add("Job"=$obj.job)
$hash.Add("Result"="Good")

If you need more objects - iterate them foreach($o in $obj){$hash.Add()}, but announce the $hash before the cycle.

Ivan Temchenko
  • 814
  • 1
  • 9
  • 12
  • I'm not sure how this answers my question (maybe I didn't ask it clearly?) -- I have a hashtable that is frankly being used like an object. I want to set several values in that hashtable in an inline / pipeline way. Your suggestion seems to start with an object, it's not inline, and not particularly different compared to one of the methods I expressed interest in avoiding - I'll try to clarify in my question. – Code Jockey Mar 03 '16 at 15:30
  • oh yes. it will work in script, not in single liner =( – Ivan Temchenko Mar 03 '16 at 15:34
0

When you use Select-Object and add a column (property) that didn't exist you're essentially creating a new [PSObject] that wraps the original. You can do this explicitly and use a [hashtable] to initiate its properties, in two ways:

$obj = [PSCustomObject]$hash

or

$obj = New-Object PSObject -Property $hash

From there, you could use Select-Object as usual. You could also manipulate the hash first to have the properties you want and then convert to an object and skip Select-Object.

briantist
  • 45,546
  • 6
  • 82
  • 127
  • Thanks - I was aware of one of those constructs, but as far as using this strategy inline, and assuming I would need a hash table afterwards, is there then a way to get a hash table back from an object? – Code Jockey Mar 03 '16 at 15:41
  • @CodeJockey no single language construct to do that, no, but you can iterate over the properties of an object and create one: `$hash = $obj.PSObject.Properties | % -Begin { $h = @{} } -Process { $h.Add($_.Name,$_.Value) | Out-Null } -End { $h }` (this is from memory and might need tweaking). – briantist Mar 03 '16 at 15:47
0

If you just need to add or update a key in your hash table:

$hash.Result += 'Good'

If the Result key exists, it's value will be set to 'Good'. If it doesn't exist, it will be added with that value.

mjolinor
  • 66,130
  • 7
  • 114
  • 135
  • can this be done inline? -- I didn't think so...(?) - basically, I'm looking for an equivalent for select-object for hash tables, which will start with a hash table, possibly piped into a command or transform where one or more fields are modified, then come out different to be used in the same statement – Code Jockey Mar 03 '16 at 18:46
  • Not inline, but there doesn't seem to be any good answer for that, given that what you're trying to do is avoid letting the Powershell pipeline do what it was designed to do - work with objects. – mjolinor Mar 03 '16 at 19:42
  • in my case, that is what has been done. I still want to know the best answer, however, for those that for whatever reason can't change the whole script (or scripts) and must start and end with a hashtable. I suppose there is always the option of not doing something inline, though. – Code Jockey Mar 03 '16 at 19:45
  • 1
    Inline code is a convention that gets practiced a lot because it's generally how people learn to use Powershell from the command line. Once the task becomes complex enough that it needs to be scripted it doesn't really offer any advantage and generally just results in making the code harder to read and maintain or debug going forward. – mjolinor Mar 03 '16 at 20:02