Powershell - Get object properties displayed in console - powershell

The point is to populate a variable with a Powershell object's property name displayed in a console.
Meaning, if I run Get-Process, I only want the eight object's properties returned in the console which are 'Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName'.
Get-Member command is not helpful here.
Can anyone help me with that?
Thank you all!

To get the column names - which may or may not be property names - of the table view that is presented for a given .NET type if it has predefined formatting data (that includes a table view) associated with it:
Note:
The following is a proper, but nontrivial and limited solution that derives the column names from the formatting data, using the first table-view definition found. It also has conceptual background information.
See the bottom section for a quick-and-dirty solution for getting the column names only, which uses text parsing to extract the column names directly from a given command's formatted output.
The middle section builds on this first section and extracts a list of property names and calculated properties mirroring the column definitions, which can be used with Select-Object, in order to create custom objects that have properties with the same values that the formatting data produces.
# Determine the .NET type of interest.
$type = (Get-Process)[0].GetType()
# Extract the names of the column headers from the *first* table-view definition.
Get-FormatData $type -PowerShellVersion $PSVersionTable.PSVersion |
ForEach-Object FormatViewDefinition |
Where-Object Control -is [System.Management.Automation.TableControl] |
Select-Object -First 1 |
ForEach-Object {
$i = 0
$rows = $_.Control.Rows
foreach ($colLabel in $_.Control.Headers.Label) {
if ($colLabel) { $colLabel } # Explicit label, with a calculated column value or renamed property
else { $rows.Columns[$i].DisplayEntry.Value } # Property name, with its value as the column value.
++$i
}
}
Caveat: The above limits output to the first table-view definition found - which may or may not apply to a given command. Which definition is chosen by default is potentially governed by criteria associated with the definitions that select based on runtime conditions, including selecting by specific input type, given that a single instance of formatting data can cover multiple types.
Also note that views may involve grouping (as you see in Get-ChildItem's formatted output, for instance), and the grouping criterion isn't covered by the command above.
Note that even for a single type multiple views may be defined, and in order to use a non-default one you must request it explicitly, via Format-Table's -View parameter, assuming you know the name,[1] e.g. Get-Process | Format-Table -View StartTime).
See also:
This answer for how to inspect formatting data in full.
You can alternatively pipe Get-FormatData output to Export-FormatData in order to export formatting data to an XML file, which has the disadvantage of being hard to read, but has the advantage of matching the XML schema used for authoring formatting data - see next point - whereas the in-memory types used to represent formatting data partially use property names that don't match the underlying XML elements.
As for authoring formatting data, which as of PowerShell 7.2.2 requires XML files (*.Format.ps1xml):
See this answer for an example of how to define your own table view.
Formatting File Overview and the Format Schema XML Reference
Note:
Using -PowerShellVersion $PSVersionTable.PSVersion with Get-FormatData is only needed in Windows PowerShell, for certain types, to work around a bug that is no longer present in PowerShell (Core) 7.1+
While column names typically correspond to the property names of the type instances being formatted, that isn't always the case, such as with the [System.Diagnostics.Process] instances output by Get-Process.
A general caveat, as zett42 notes, is that display formatting of types isn't part of the public contract regarding breaking changes, so formatting definitions are allowed to change over time.
If a given type has no predefined formatting data associated with it (in which case Get-FormatData is a quiet no-op):
The names of its (public) instance properties are used as column
names.
You only get a table view by default if there are 4 or fewer properties but you can request it explicitly with Format-Table (With 5 or more properties, Format-List is applied by default).
To get the names of all (public) instance properties of a given object, use the intrinsic .psobject property, which is a rich source of reflection; e.g.:
(Get-Process | Select-Object -First 1).psobject.Properties.Name
To create a list of property names and calculated properties usable with Select-Object that mirror the formatting-data's column definition:
# Determine the .NET type of interest.
$type = (Get-Process)[0].GetType()
# Get an array of property names / calculated properties from the
# formatting data, for later use with Select-Object
$props =
Get-FormatData $type -PowerShellVersion $PSVersionTable.PSVersion |
ForEach-Object FormatViewDefinition |
Where-Object Control -Is [System.Management.Automation.TableControl] |
Select-Object -First 1 |
ForEach-Object {
$i = 0
$rows = $_.Control.Rows
foreach ($colLabel in $_.Control.Headers.Label) {
if ($colLabel) { # Explicit label, with a calculated column value or renamed property
#{
Name = $colLabel
Expression = if ('ScriptBlock' -eq $rows.Columns[$i].DisplayEntry.ValueType) {
[scriptblock]::Create($rows.Columns[$i].DisplayEntry.Value)
} else {
$rows.Columns[$i].DisplayEntry.Value
}
}
}
else { # Property name, with its value as the column value.
$rows.Columns[$i].DisplayEntry.Value
}
++$i
}
}
# Sample call
Get-Process | Select-Object -Property $props | Format-Table | more
The sample call produces similar output to just Get-Process alone, as it uses the column definitions as (calculated) properties - albeit with default values for formatting attributes such as column width and alignment.
Note the explicit use of Format-Table to ensure tabular output; without it - given that the [pscustomobject] instances created by Select-Object have no formatting data associated with them - list formatting (implied Format-List) would result.
As Mathias points out, the calculated properties will be string-typed even for columns based on numeric properties, because their purpose in the formatting data is to created formatted string representations.
Quick-and-dirty solution for getting the column names only:
The following uses Out-String -Stream in conjunction with Select-String to extract the column names from a given command's formatted output, which relies on two assumptions:
The column names have no embedded spaces
The command actually produces table-formatted output by default; however, you can insert a Format-Table call before Out-String, if desired.
Get-Process | Out-String -Stream | Select-String -List '^\s*--+' -Context 1, 0 |
ForEach-Object { -split $_.Context.PreContext[0] }
Output:
NPM(K)
PM(M)
WS(M)
CPU(s)
Id
SI
ProcessName
Note: In Windows PowerShell an additional property shows, as the first one: Handles.
[1] While tab-completion does offer view names, they appear to be out of sync with the actually available ones, as of PowerShell 7.2.2. To see the latter, provoke an error with a dummy name, and the error message will list the available ones; e.g. Get-Process | Format-Table -View NoSuch lists the following available views in the resulting error message: process, Priority, StartTime

Related

What causes the output of select-object to be truncated?

I have the following silly PowerShell script:
$username = 'rny'
$null = mkdir "c:\Users\$username\YYY"
$null = mkdir "c:\Users\$username\YYY\TODO"
$null = mkdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"
$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
"C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
"C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"
foreach ($file in $files) {
$null = new-item $file
}
Get-ChildItem . -errorAction silentlyContinue -recurse -filter *.wxyz | select-object fullName
foreach ($file in $files) {
remove-item -literalPath $file
}
rmdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"
rmdir "c:\Users\$username\YYY\TODO"
rmdir "c:\Users\$username\YYY"
When I execute it, the output of the get-childItem ... | select-object pipeline is truncated:
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...
Note especially the last line. This behaviour was noted elsewhere on SuperUser and the accepted answer is to pipe the output into format-table with -autoSize. So far, so good.
However, If I comment the second file in the assignment of the $files array like so
$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
# "C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
"C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"
the output is not truncated anymore:
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
This puzzles me because the name of the file that was truncated is now fully visible and I have no explanation for this.
So, what exactly causes the truncation of the file in one case and not in the other case?
This isn't so much to do with Select-Object per se - it's more to do with how PowerShell converts values into string representations, and specifically in this case how it does that when it displays uncaptured output from cmdlets on the console.
PowerShell (Windows and Core) has a bunch of preconfigured "views" that define how some built-in types are rendered - e.g. whether they use Format-List or Format-Table, what properties to display, and in the case of tables, how wide to display each column - see about_Format.ps1xml.
For other types, PowerShell tries to make a best-guess on the fly. To do that it waits for the first N items in arrive from the input to make a decision on the formatting rules to apply. I can't find any definitive documentation that says how many items PowerShell waits for, so that might be a good follow-up question :-).
And you can obviously override these defaults by passing formatting parameters for Format-Table and Format-List.
In your case the top-level script has received pipeline output containing an array of PSCustomObject objects (i.e. the output from Select-Object) and it's decided to show them in a table with a column for the FullName property.
Example 1
In your first example, it's looked at the first two PSCustomObject items and decided to make the FullName column 54 characters wide since that's the length of C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz, and the third item gets truncated to that same width (if you include the ...) because it wasn't included in the decision-making process for column widths.
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 54 characters
Example 2
In your second example, PowerShell sees the longest FullName property in the first couple of PSCustomObjects is C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz and so uses a column width of 70.
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 70 characters
Example 3
Finally, if you do what #notjustme suggests in the comments and add -ExpandProperty FullName onto Select-Object you get an array of string values instead of an array of PSCustomObjects which is why you might see PowerShell apply different formatting rules - you don't get a FullName header for example because the values are strings not objects with properties, and it's using Format-List instead of Format-Table.
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
To add some background to mcclayton's helpful answer:
Specifically, you're seeing the effects of the infamous 300-msec. delay built into Format-Table formatting, which PowerShell implicitly applies to instances of .NET types that have 4 or fewer properties and do not have explicit formatting data associated with them.
See this answer for details (which is given in the context of a different symptom of the same problem, namely unexpected output ordering), but the short of it is: The delay is used to infer suitable column widths from the specific property values received within the delay period.
This means that objects with property values received after the 300-msec. delay may be truncated in their column display if their values happen to be wider than the widest among the values received during the delay period.
Specifically, your symptom implies that only the first two objects were received within the delay period, and that the longer among the two property values then locked in the column width; when the third object was received later, the column width was already locked in, and the longer value was truncated (indicated with trailing ... in Windows PowerShell (3 . chars.) and … in PowerShell (Core) 7+ (single char))
The only way to avoid truncating is to know the max. column width ahead of time and pass it to an explicit Format-Table call -
notably, this prevents using the output as data. See below.
Here's a simple way to provoke the problem:
Note: The Select-Object calls below aren't strictly needed, but are provided for symmetry with the question.
# Create blocks of two objects with strings of different length in their
# .Prop value: 10 chars. vs. 100 chars.
$count = 10000 # How often to repeat each object in a row.
$objs =
(, [pscustomobject] #{ Prop = ('x' * 10) } * $count) +
(, [pscustomobject] #{ Prop = ('y' * 100) } * $count)
# Depending on the value of $count - which translates into how
# long it takes until the second block of objects starts emitting -
# truncation will occur or not.
$objs | Select-Object Prop
With blocks of 10,000 objects, I do see the truncation: it takes long enough for the first block - with the short property value - to lock in the width of the display column, causing the objects in the second block to be truncated:
Prop
----
xxxxxxxxxx
...
yyyyyyyyy… # <- truncated, because width 10 was locked in during the delay
...
To prevent truncation, pass a calculated property to Format-Table specifying the max. width:
$objs | Select-Object Prop | Format-Table #{ n='Prop'; e='Prop'; width = 100 }

PowerShell : How can I set a variable from a single column in multi-column output?

I am using Get-PhysicalDisk | Format-Table DeviceID, UniqueID to get a listing of drive number and serial number of all drives on a Windows 2016 Server. I want to search for one serial number and capture only the drive number as a variable. I'm used to awk in UNIX and I'm totally stumped on how to achieve this in PowerShell.
Get-PhysicalDisk | Format-Table DeviceID, UniqueID
DeviceID UniqueID
-------- --------
5 624A937024897B4FF488CBF800027A4B
8 624A937024897B4FF488CBF800028A4D
7 624A937024897B4FF488CBF800027A59
0 {c4d394f5-509e-11e9-a834-806e6f6e6963}
1 {c4d394f6-509e-11e9-a834-806e6f6e6963}
2 {c4d394f7-509e-11e9-a834-806e6f6e6963}
3 {c4d394f8-509e-11e9-a834-806e6f6e6963}
4 {c4d394f9-509e-11e9-a834-806e6f6e6963}
6 624A937024897B4FF488CBF800027A56
I want to expand this command to find SerialNumber 624A937024897B4FF488CBF800027A56 then set a variable called $DriveNumber to the value of 6 as shown in the output.
I then plan to use this variable in Set-Disk to take the drive offline/online as a perform a volume overwrite. I don't want to hard code the drive number because upon reboot, the drive number could change.
NOTE I was using Get-Disk and piping the appropriate output to Set-Disk to perform my drive off/online. But, I have a mysterious issue of the virtual drives not displaying with Get-Disk, therefore I'm trying to find a workaround with Get-PhysicalDisk Thanks!
$driveNumber = (
Get-PhysicalDisk | Where-Object UniqueId -eq '{624A937024897B4FF488CBF800027A56}'
).DeviceId
Note the need to enclose the GUID string in {...}.
As all PowerShell cmdlets do, Get-PhysicalDisk outputs objects whose properties you can query.
Cmdlet Where-Object acts as a filter on the objects it receives from the pipeline and compares the value of property UniqueId to the specified literal GUID (string), which, by definition, matches (at most) one object.
(...).DeviceId returns the value of the target objects' DeviceId property and assigns it to variable $driveNumber.
A note re use of Format-* cmdlets such as Format-Table:
Only ever use Format-* cmdlets for display formatting.
If the intent is further programmatic processing:
either: simply access the input objects' intrinsic properties (whose availability is independent of whether they display by default or via a Format-* cmdlet call)
or: if you need to create simplified or transformed objects with only a subset of the original properties and/or transformed property property values (calculated properties, use Select-Object.

Undefined property name appears valid

The following two commands produce different output.
Get-ChildItem | Sort-Object -Property Length
Get-ChildItem | Sort-Object -Property Len
Len is not a member of System.IO.FileInfo. Is PowerShell matching Len to the Length member? If not, then why is there no error message saying that Len is not a property?
No, its not member of System.IO.FileInfo as you can see by adding the -Debugswitch:
Get-ChildItem | Sort-Object -Property Len -Debug
Output looks like:
DEBUG: "Sort-Object" - "Len" cannot be found in "InputObject".
I guess the reason for that is the defensive implementation of the cmdlet:
If an object does not have one of the specified properties, the
property value for that object is interpreted by the cmdlet as Null
and is placed at the end of the sort order.
To complement Martin Brandl's helpful answer with more general information:
While PowerShell's elastic syntax only applies to parameter names (e.g., specifying just -p for -Property) , not values (arguments), you do have options for completing values:
At edit time: use tab completion:
This works on the command line as well as in Visual Studio Code with the PowerShell extension installed (where you'll get IntelliSense as well), as long as PowerShell can statically infer the output type(s)[1]
of the command in the previous pipeline segment.
At runtime:
Sort-Object and several other cmdlets allow you to use a wildcard expression to match property names:
Get-ChildItem | Sort-Object -Property Len* # "Len*" matches "Length"
Note that multiple properties may match, and that a given parameter must be explicitly designed to support wildcards (unlike in POSIX-like shells, it is not PowerShell itself that resolves the wildcards).
When accessing a nonexistent property on an object directly, no error is reported by default, and $null is returned:
(Get-Item /).Foo # Outputs $null as the value of nonexistent property "Foo"
By contrast, if Set-StrictMode -Version 2 or higher is in effect, a (statement-terminating) error is reported in that case, but note that Set-StrictMode does not apply when passing property names as arguments, such as to Sort-Object above.
As for a possible motivation for why Sort-Object doesn't enforce the existence of specified properties:
PowerShell allows you to pass objects that are any mix of types as input through the pipeline, with the objects getting passed one at a time.
(Similarly, PowerShell's default array type is [object[]], which allows you to create mixed-type arrays such as 1, 'hi', $True)
Even with (potentially) homogeneous input (such as the [System.IO.FileInfo] instances emitted by Get-ChildItem -File, for instance), a receiving command cannot detect that case up front, because it only ever sees one object at a time.
In general, cmdlets should be able to handle a mix of types among the input gracefully, and treating nonexistent properties as $null is overall the better choice, especially given that:
a cmdlet may still be able to act meaningfully on the input if at least a subset of the input objects have the property of interest (see below).
a cmdlet cannot know in advance whether that subset is empty.
Example with heterogeneous input:
Send an array of custom objects through the pipeline and sort it by property val, which one of the objects lacks:
[pscustomobject] #{ n = 'o1'; val = 2 },
[pscustomobject] #{ n = 'o2' },
[pscustomobject] #{ n = 'o3'; val = 1 } | Sort-Object val
Output:
n val
- ---
o3 1
o1 2
o2
Sorting was performed among all the input objects that do have a .val property, whereas those that don't were placed at the end, as stated in the quote from Sort-Object's documentation in Martin's answer.
[1] This should be true of all built-in cmdlets; to ensure that it works with custom functions, define them with [OutputType(<type>)] attributes - see this answer of mine for more.

Is it possible to use a cmdlet as an input parameter for a dynamic PowerShell script?

Here's a test script I'm trying to use, and I'm calling it from a separate process and attempting to pass parameters to it. The idea is that I have a user interface that allows a user to select a CmdLet and then populate another dropdown with the properties/methods of that CmdLet.
My problem seems to be that the script is rendering the input parameter as a string, and is thusly creating a text file with the methods and properties of any arbitrary string to which you've applied a "Get-Member" to, such as "Clone", or "CompareTo". The only property as such is "Length".
Is there any way to have that input parameter be brought over as a usable CmdLet instead of a string? Perhaps I'm missing something, or perhaps what I'm attempting to do isn't possible.
param([string]$inputCmdLet = "Get-NetAdapter");
$wrkgDir = "D:\Distribution\Operational";
# Get Properties and Methods for CmdLet Input Parameter
$propertyNames = $inputCmdLet | Get-Member -MemberType Property;
$methodNames = $inputCmdLet | Get-Member -MemberType Method;
# Sort Arrays
$propertyNames = $propertyNames | Sort-Object Name;
$methodNames = $methodNames | Sort-Object Name;
# Output Results to Text Files
$propertyNames.Name | Out-File $wrkgDir\$inputCmdLet.Properties.txt;
$methodNames.Name | Out-File $wrkgDir\$inputCmdLet.Methods.txt;
EDIT FOR MORE INFO:
The output I'm hoping for, in the example of Get-NetAdapter, is the list of properties in one output file and methods in the other. What I'm getting now is this:
Left list is expected (partial) result, right list is actual result.
I'm uncertain how to achieve the result list on the left (in the image) programmatically. I'm able to get the proper output by typing it out statically:
$mbrNameStatic = Get-NetAdapter | Get-Member;
$mbrNameStatic.Name | Out-File $wrkgDir\$inputCmdLet.Strings.txt;
But when i use the input parameter, it merges the value in as a string, so it seems the actual runtime code looks more like this:
$propertyNames = "Get-NetAdapter" | Get-Member -MemberType Property;
So the addition of the quotes renders the cmdlet as a string (makes sense i suppose, since my input parameter is a string), which returns the properties and methods of a string instead of the cmdlet. Is there any way to have the cmdlet render out without the quotes?
Please do let me know if I'm not making sense with this, either with my description, or with the idea altogether.
Thanks!
In order to execute a command whose name (only) is stored in a variable or whose name is specified in single or double quotes, you must use &, the call operator.
# WRONG: The token is interpreted as an *expression* that outputs a *string*
"Get-NetAdapter" # outputs the [string] literal
# WRONG: ditto, via a variable
$name = "Get-NetAdapter"
$name # outputs the contents of the [string] variable
# OK: Use of & tells Powershell to interpret the next token as a *command* to *invoke*.
& "Get-NetAdapter"
& $name
As for your general approach:
Note that not all cmdlets produce output when invoked without parameters, so your current code (even with &) won't work with all cmdlets.
Conversely, those cmdlets that do produce output when given no parameters may produce a lot of them, which is unnecessary, so consider something like & $inputCmdlet | Select-Object -First 1.
Generically, you can use something like (Get-Command Get-NetAdapter).OutputType to obtain a cmdlet's output type(s), but note that:
Declaring output types is optional, so not all cmdlets may return a value.
If you start with a type rater than an instance of that type, you cannot use Get-Member to discover the instance members (you can only obtain the static members via -Static).

powershell desc analogy - list output columns and their types

If there anything like SQL's describe so I don't have to look through a "blanket" of | select * output before actually selecting the few columns that I need?
E.g. Get-Process's possible output columns include Id, Name,VirtualMemorySize as well as about three dozen others. I want to get that list of column names, preferrably with their types.
If this for human viewable output then use Format-Table e.g.
Get-Process | Format-Table Name,Id,PM -Auto
Note: if you have lots of output do not use the -AutoSize parameter as that buffers up all its input to determine the optimal size of each column. If you don't specify that parameter, PowerShell will split up the screen space evenly based on number of properties selected but it will display each object as soon as it is received.
For the "column" names, that is bit trickier. You can easily get the list of "ALL" the property names e.g.:
Get-Process | Get-Member -MemberType Properties
If you want just the properties that would normally be display by Format-Table then you need to inspect the View Definition for the System.Diagnostics.Process type in C:\Windows\System32\WindowsPowerShell\v1.0\DotNetTypes.format.ps1xml.