How does pipeline affect Select-Object? - powershell

I'm trying to make a table in PowerShell with custom headers, I am able to do this with
"var" | Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
First Second Third
----- ------ -----
1 2 3
However, without the initial object, there is no output
Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
I can't tell the difference between these other than one is after a pipeline while the other isn't. Why won't this work?
Why doesn't this work?

The cmdlet Select-Object (alias Select) has one required parameter which is -inputObject Which also happens to be the object that gets passed through the pipeline.
Select-Object -InputObject "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
Will have the output
First Second Third
----- ------ -----
1 2 3
While without -InputObject, it will not have an output because there is no input (Thanks #mklement0).
Select-Object "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
# No Output
and with the pipeline, it will
"Example" | Select-Object "Example" -Property #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}
First Second Third
----- ------ -----
1 2 3
The -inputObject Parameter will usually contain the table you would like to select columns from or other things (if you are not using expressions).

Although the answer from #Neko answers the exact question, I think it is important to mention that the Select-Object cmdlet is not mentioned to construct new custom objects:
Quote from the Get-Help Select-Object -Online:
The Select-Object cmdlet selects specified properties of an object
or set of objects.
In other words; the object needs to exist in order to select its properties.
For what you are doing with
"var" | Select #{n="First";e={"1"}}, #{n="Second";e={"2"}},#{n="Third";e={"3"}}`
you're creating a new pscustomobject by removing all the default properties from the "Var" (string) object and adding new properties with a hard-coded (static) expression which is a long-winded syntax and quiet expensive for building a new object.
To construct a new pscustomobject ("table"), you can simply use the constructor syntax:
[pscustomobject]#{First = '1'; Second = '2'; Third = '3'}
For legacy (prior PSv3) POwerShell versions:
New-Object PSObject -Property #{First = '1'; Second = '2'; Third = '3'}

Related

powershell - How refer custom expression property on single one select-object

can you help me with this simple example script?
How do I refer to "custom expression property" on single one "select-object"?
I would like: single "select-object"
Get-Process | Select-Object Id, 
    #{name="MyProp"; expression={$_.id}},
    #{name="MyPropRef"; expression={$_.MyProp}}
...but third property "MyPropRef" is not displayed!
Id MyProp MyPropRef
-- ------ ---------
3780 3780
While with double select-object piped, "MyPropRef" is displayed
Get-Process | Select-Object Id, 
    #{name="MyProp"; expression={$_.id}} | Select-Object *,
    #{name="MyPropRef"; expression={$_.MyProp}}
Id MyProp MyPropRef
-- ------ ---------
3780 3780 3780
Thanks
Calculated properties only operate on the input objects' original properties, they cannot refer to each other.
If you need to add additional properties based on previously calculated properties, you indeed need another (expensive) Select-Object call.
Your command:
Get-Process | Select-Object Id,
#{name="MyProp"; expression={$_.id}},
#{name="MyPropRef"; expression={$_.MyProp}}
creates a .MyPropRef property, but its value is $null, because the System.Diagnostics.Process instances that Get-Process outputs themselves do not have a .MyPropRef property.
If you want to make do with a single Select-Object call without repetition, you can define the script blocks used in the calculated properties in variables beforehand and reuse them across calculated property definitions; e.g.:
$myPropExpr = { $_.id }
Get-Process | Select-Object Id,
#{ name="MyProp"; expression=$myPropExpr },
#{ name="MyPropRef"; expression={ "!" + (& $myPropExpr) }}
This would yield something like:
Id MyProp MyPropRef
-- ------ ---------
3780 3780 !3780
...

Why is this not working? - Trying to save properties in a variable for use multiple times in a function

I am trying to find a way to save the properties for a select statement in PowerShell but it isn't working. I haven't found a way to make an entire statement a literal so that it isn't reviewed until the variable is opened.
Here is what works:
$wsus.GetSummariesPercomputerTarget($CurrentMonthUpdateScope, $ComputerScope) |
Select-Object #{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L='Computer';E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount |
Sort-Object -Property "Computer"
and I am trying to get the properties mentioned (starting just after the Select-Object statement and ending just before the last pipe) placed in a variable so that I can use the same properties multiple times with different scopes.
I have tried this:
$Properties = '#{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L="Computer";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount'
$wsus.GetSummariesPercomputerTarget($CurrentMonthUpdateScope, $ComputerScope) |
Select-Object $Properties |
Sort-Object -Property "Computer"
While this runs it doesn't give any data and I think it confuses PowerShell.
This gives the same response:
$Properties = "#{L=`"WSUSServer`";E={$Server}},
#{L=`"FromDate`";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString(`"MM/dd/yyyy`")}},
#{L=`"ToDate`";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString(`"MM/dd/yyyy`")}},
#{L=`"Computer`";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
DownloadedCount,
NotInstalledCount,
InstalledPendingRebootCount,
FailedCount,
Installedcount"
Any options, thoughts, etc.?
The -Property argument of Select-Object expects an array, not a string. So something like this:
$Properties = #(#{L="WSUSServer";E={$Server}},
#{L="FromDate";E={$($CurrentMonthUpdateScope.FromCreationDate).ToString("MM/dd/yyyy")}},
#{L="ToDate";E={$($CurrentMonthUpdateScope.ToCreationDate).ToString("MM/dd/yyyy")}},
#{L="Computer";E={($wsus.GetComputerTarget([guid]$_.ComputerTargetID)).FullDomainName}},
"DownloadedCount",
"NotInstalledCount",
"InstalledPendingRebootCount",
"FailedCount",
"Installedcount")
Note, you will need to turn the simple property names into strings within your array.

PowerShell: How to change table headers in loop?

I want to collect data remotely and adapt the table headers later with the help of an xml-file. This should happen in a loop, looking like that:
foreach($tableheader in $table) {
$table.$tableheader = $xmlFile.$tableheader
}
Amongst others I tried the following:
$x = 0
$sitesonfig = Get-ConfigSite -AdminAddress localhost
foreach($Prop in ($siteconfig |get-member -MemberType Property | select -Property name))
{
$x += 1;
$siteconfig = $siteconfig | Select-Object * | format-table #{l="Smile$x";e={$_.$Prop}}
}
Yes, I know this looks silly, but I've got really no idea, how to change the headers one by one without listing each time all the other headers, too.
One possibility is to use a loop to create the header map that you pass into Format-Table.
Here is your second example modified to demonstrate this concept. You should be able to adapt this to grab the header info from your XML file.
$x = 0
$siteconfig = Get-ConfigSite -AdminAddress localhost
$headerMap = #()
foreach($Prop in ($siteconfig |get-member -MemberType Property | select -ExpandProperty name))
{
$x += 1;
$headerMap += #{
l="Smile$x";
e={ $_.$Prop }.GetNewClosure()
}
}
$siteconfig | Format-Table $headerMap
Important Points
Select -Property name needed to be changed to Select -ExpandProperty name. The reason for this is that Select-Object in PowerShell will return an object filtered down to the selected member but you need a string for grabbing the property value by name. The -ExpandProperty parameter will expand this to be the string value instead.
The expression block needs GetNewClosure() called on it to capture the value of $Prop at the time of script block creation versus at the time of calling. This will probably be a little confusing if you are new to the concept of closures and PowerShell's scoping rules. Without this, due to PowerShell's scoping rules, $Prop will evaluate to the value of $Prop at the time it is used by Format-Table. By calling GetNewClosure(), the value of $Prop is captured when GetNewClosure() is called which is what we want in this case.

Extracting text from output

I am creating my first application with VS and PowerShell and need to get the name of a service from my listview. The output of the selection looks like this:
ComputerName Status Name DisplayName
------------ ------ ---- -----------
PC Running Appinfo Application Information
What I want to do is get the value Appinfo from the Name column and assign it to a variable. I've had no luck with regex, but then again I am a beginner so I could be doing something wrong. Is there an easy way to do this?
The output you're currently getting is a formatted (tabular) view on selected properties of an object (namely the properties ComputerName, Status, Name, and DisplayName). You can get the value of a particular property by expanding it via the Select-Object cmdlet:
$name = ... | Select-Object -Expand Name
You could also store the object in a variable and access the property via dot-notation:
$obj = ...
$name = $obj.Name
Beware that if a cmdlet outputs multiple objects the variable will contain an array:
PS C:\> $services = Get-Service
PS C:\> $services.GetType().FullName
System.Object[]
PS C:\> $services.Count
136
You can access properties of a member object by index:
$services[42].Name
or by selecting a specific object via its properties:
$services | Where-Object { $_.DisplayName -eq 'Application Information' } |
Select-Object -Expand Name
Since PowerShell v3 you can also use the dot-property notation to get the value of a particular property of all array members:
$services.Name
Prior to PowerShell v3 this would have thrown an error, because the array object doesn't have a property Name.

Powershell - When an object is placed on the pipeline, can it be used again later?

I have a collection of objects which I am trying to return multiple sets of data from. Each time I want to select one of the properties of the object and sort it uniquely.
The problem I'm getting is that I only get results the first time I use the collection. This leads me to wonder whether the objects are being "consumed" when I put them on the pipeline.
To illustrate with an example:
$results = getMyCollectionOfObjects
$results | select-object property1 | sort-object -unique property1
$results | select-object property2 | sort-object -unique property2
$results | select-object property3 | sort-object -unique property3
As far as I can tell this should result in 3 distinct lists of all the possible values for each of the three properties.
However for me it's like the $results value is being "used up" the first time it is selected from.
Is this the case and what should I do instead?
Hope this is clear.
That's how it rolls... you have to explicitly pipe them to Out-Default to get rid of that odd behavior. Otherwise it will try to display property1 for 2nd and 3rd set too. You removed it from $results, so it comes back blank.
HTH
Bartek
So this had me tripped up and scratching my head or a minute as well. The answer it turns out is really quite simple: the Select-Object cmdlet returns an object of type Selected.System.Management.Automation.PSCustomObject. That is then passed down the pipeline to following two selects, but since there are no longer any matching properties (they were discarded from the first select) - nothing is output. Consider the following example:
# declare an array and populate it
$results = #()
$results = $results + (New-Object PSobject -Property #{
P1 = "One"
P2 = "Two"
P3 = "Three"
})
$results = $results + (New-Object PSobject -Property #{
P1 = "Uno"
P2 = "Dos"
P3 = "Tres"
})
$results | select P1
$results | select P2
$results | select P3
As you described, I was only getting output from the first select. I them took the suggestion from BartekB to put | Out-Default at the end of each line and it started working. I investigated further by replacing that with | Get-Member to view the object the was being placed on the pipeline:
$results | select -Property P1 | get-member
TypeName: Selected.System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
P1 NoteProperty System.String P1=One
In a nutshell, the Out-Default is required to actually force it to display on the console instead of passing it along to the next statement. It would seem this behavior is implied upon completing a statement in the interactive shell, but behaves a bit differently when fully scripted.