If I run the following, I get the following results.
[string[]]$p = Get-Process Explorer |Select-Object -Property ID,Name,Handles,MainWindowTitle
$p
foreach($item in $p)
{
Write-Output $item
}
#{Id=3560; Name=explorer; Handles=1454; MainWindowTitle=C:\}
#{Id=3868; Name=explorer; Handles=2787; MainWindowTitle=Y:\}
#{Id=9468; Name=explorer; Handles=1823; MainWindowTitle=Z:\}
Question 01: Is this considered to be a hashtable?
Question 02: How can I get the values of a key such as MainWindowTitle?
Question 03: How would I filter on ID field ending with 68?
Question 01: Is this considered to be a hashtable?
No: you're explicitly creating a string array ([string[]]), which means that the [pscustomobject] instances emitted by Select-Object, based on the [System.Diagnostics.Process] instances that Get-Process outputs, are stringified.
A stringified [pscustomobject] uses a hashtable-like representation that is unrelated to actual hashtables - see this answer.
Note:
This is a for-display, string representation that is not suitable for programmatic processing, notably because information about the data types of properties and boundaries around string property values with spaces are lost.
For that reason, in general, avoid dealing with string representations of objects in favor of processing objects as-is, which enables robust, property-based access.
Question 02: How can I get the values of a key such as MainWindowTitle?
Use the [System.Diagnostics.Process] objects output by Get-Process as-is, which allows you to access their properties (no need for an intermediate Select-Object call); the following example uses member-access enumeration to retrieve all .MainWindowTitle property values:
(Get-Process Explorer).MainWindowTitle
Question 03: How would I filter on ID field ending with 68?
Building on the above, using Where-Object with simplified syntax for filtering:
Get-Process Explorer | Where-Object ID -like *68
Related
I need to get the value of an environment variable from a kubernetes pod. I have my values listed in a hash table.
I call
$hash["service-testurl"].spec.template.spec.containers.env
And it returns a table:
name value
---- -----
ADDR https://test.com
TOKEN 123456789
CERT_PATH public-certs/test
ENVIRONMENT dev
I need to get https://test.com into a variable in my ps1 script, but i'm not sure how to get this value. (consider that for each deployment the url will be different, like abc.com, def.com, ghj.com... so i can't filter by the name test.com)
I was looking for something like $hash["service-testurl"].spec.template.spec.containers.env.name["ADDR"].value
Running $hash["service-testurl"].spec.template.spec.containers.env.PSTypeNames returns
System.Object[]
System.Array
System.Object
To complement your own effective solution:
Even though your display output of $hash["service-testurl"].spec.template.spec.containers.env looks like the representation of a (single) hashtable, the value is actually:
(a) an array, as your diagnostic output with .pstypenames demonstrates,
(b) whose elements are (presumably) [pscustomobject] instances that each have a a .name and a .value property (an easy way to tell is that the display output's column headers are name and value, whereas with hashtables they would be Name and Value).
Leaving aside that the identifier ADDR is a property value rather than a property / key name in your case, you fundamentally cannot use key-based index notation (['ADDR']) on an array - that generally only works on a (single) hashtable (or, more generally, dictionary).[1]
In your case, you need to find the array element whose .name property value is 'ADDR', which then allows you to return its .value property value.
For collections already in memory, the intrinsic .Where() method (as used in your own solution) is a more efficient - and more flexible - alternative to filtering a collection via the Where-Object cmdlet.
It will often not matter in practice, but you can optimize a .Where() call to stop filtering once the first match has been found, if you expect or are only interested in one match:
$hash["service-testurl"].spec.template.spec.containers.env.Where(
{ $_.name -eq 'ADDR' },
'First'
).value
Note that .Where() always returns an array-like collection, even if only a single value is matched - see this answer for details. As such, the .value property access is attempted on that collection, which, however, still works as intended, courtesy of the PowerShell feature known as member-access enumeration.
Note how using (...) around the arguments is now a syntactic necessity.
While with only a single argument - the filter script block ({ ... }) - you can get away with not using (...) - .Where{ $_.name -eq 'ADDR' } as shorthand for .Where({ $_.name -eq 'ADDR' }) - omitting the (...) is problematic for two reasons:
Given that the Where-Object cmdlet can also be referred to as Where (via a built-in alias), the two command forms could be confused, and given that Where-Object allows and is typically used with a space separating the command name from its script-block argument (e.g, 1..3 | Where { $_ -eq 2 }, it is tempting to also try to use a space with the .Where() method, which does not work:
# !! BROKEN, due to space before "{"
(1..3).Where { $_ -eq 2 }
If you add another argument later, you need to remember to use (...)
[1] The fact that key-based index notation does not work with member-access enumeration, i.e. doesn't work on an array of hashtables (only dot notation does, which PowerShell supports for hashtables too) could be considered an inconsistency; e.g. #( #{ foo=1 } ).foo works (dot notation), but #( #{ foo=1 } )['foo'] does not, due to the array wrapper.
However, this inconsistency was declared to be by design - see GitHub issue #17514.
I was able to do it with something similar that #iRon proposed:
$hash["service-testurl"].spec.template.spec.containers.env.where{$_.name -eq 'ADDR'}.value
Thanks!
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
The documentation for ForEach-object says "When you use the InputObject parameter with ForEach-Object, instead of piping command results to ForEach-Object, the InputObject value is treated as a single object." This behavior can easily be observed directly:
PS C:\WINDOWS\system32> ForEach-Object -InputObject #(1, 2, 3) {write-host $_}
1 2 3
This seems weird. What is the point of a "ForEach" if there is no "each" to do "for" on? Is there really no way to get ForEach-object to act directly on the individual elements of an array without piping? if not, it seems that ForEach-Object with InputObject is completely useless. Is there something I don't understand about that?
In the case of ForEach-Object, or any cmdlet designed to operate on a collection, using the -InputObject as a direct parameter doesn't make sense because the cmdlet is designed to operate on a collection, which needs to be unrolled and processed one element at a time. However, I would also not call the parameter "useless" because it still needs to be defined so it can be set to allow input via the pipeline.
Why is it this way?
-InputObject is, by convention, a generic parameter name for what should be considered to be pipeline input. It's a parameter with [Parameter(ValueFromPipeline = $true)] set to it, and as such is better suited to take input from the pipeline rather passed as a direct argument. The main drawback of passing it in as a direct argument is that the collection is not guaranteed to be unwrapped, and may exhibit some other behavior that may not be intended. From the about_pipelines page linked to above:
When you pipe multiple objects to a command, PowerShell sends the objects to the command one at a time. When you use a command parameter, the objects are sent as a single array object. This minor difference has significant consequences.
To explain the above quote in different words, passing in a collection (e.g. an array or a list) through the pipeline will automatically unroll the collection and pass it to the next command in the pipeline one at a time. The cmdlet does not unroll -InputObject itself, the data is delivered one element at a time. This is why you might see problems when passing a collection to the -InputObject parameter directly - because the cmdlet is probably not designed to unroll a collection itself, it expects each collection element to be handed to it in a piecemeal fashion.
Consider the following example:
# Array of hashes with a common key
$myHash = #{name = 'Alex'}, #{name='Bob'}, #{name = 'Sarah'}
# This works as intended
$myHash | Where-Object { $_.name -match 'alex' }
The above code outputs the following as expected:
Name Value
---- -----
name Alex
But if you pass the hash as InputArgument directly like this:
Where-Object -InputObject $myHash { $_.name -match 'alex' }
It returns the whole collection, because -InputObject was never unrolled as it is when passed in via the pipeline, but in this context $_.name -match 'alex' still returns true. In other words, when providing a collection as a direct parameter to -InputObject, it's treated as a single object rather than executing each time against each element in the collection. This can also give the appearance of working as expected when checking for a false condition against that data set:
Where-Object -InputObject $myHash { $_.name -match 'frodo' }
which ends up returning nothing, because even in this context frodo is not the value of any of the name keys in the collection of hashes.
In short, if something expects the input to be passed in as pipeline input, it's usually, if not always, a safer bet to do it that way, especially when passing in a collection. However, if you are working with a non-collection, then there is likely no issue if you opt to use the -InputObject parameter directly.
Bender the Greatest's helpful answer explains the current behavior well.
For the vast majority of cmdlets, direct use of the -InputObject parameter is indeed pointless and the parameter should be considered an implementation detail whose sole purpose is to facilitate pipeline input.
There are exceptions, however, such as the Get-Member cmdlet, where direct use of -InputObject allows you to inspect the type of a collection itself, whereas providing that collection via the pipeline would report information about its elements' types.
Given how things currently work, it is quite unfortunate that the -InputObject features so prominently in most cmdlets' help topics, alongside "real" parameters, and does not frame the issue with enough clarity (as of this writing): The description should clearly convey the message "Don't use this parameter directly, use the pipeline instead".
This GitHub issue provides an categorized overview of which cmdlets process direct -InputObject arguments how.
Taking a step back:
While technically a breaking change, it would make sense for -InputObject parameters (or any pipeline-binding parameter) to by default accept and enumerate collections even when they're passed by direct argument rather than via the pipeline, in a manner that is transparent to the implementing command.
This would put direct-argument input on par with pipeline input, with the added benefit of the former resulting in faster processing of already-in-memory collections.
I have a requirement where I need to compare JSON object from a file to the JSON message which comes into Anypoint MQ queue. I am able to get the message from the queue. I have used below script but it is not working. I did both -eq and Compare-Object but they are not working.
$po_ps_output = $filemessagecontent | ConvertFrom-Json
$po_python_output = $mqmessagecontent.body | ConvertFrom-Json
$result = $po_ps_output -eq $po_python_output
If you just want to know if the two JSON-originated objects differ, without needing to know how:
$contentEqual = ($po_ps_output | ConvertTo-Json -Compress) -eq
($po_python_output | ConvertTo-Json -Compress)
Note:
ConvertTo-Json defaults to a serialization depth of 2 - use -Depth <n> if your data is more deeply nested to avoid truncation (potentiall data loss) - see this post.
Converting back to JSON may seem like an unnecessary step, but the -Compress standardizes the output formatting to a single line with no extra whitespace, which ensures that incidental variations in formatting in the input (if you had used the input JSON text directly) are ignored.
If you want to know how the two JSON-originated objects differ:
Note: The following is only useful in the following, limited scenario -
a generic, robust solution would require much more effort:
The inputs have the same structure and differ only in property names / values.
The ordering of (equivalent) properties is the same.
Compare-Object (($po_ps_output | ConvertTo-Json) -split '\r?\n') `
(($po_python_output | ConvertTo-Json) -split '\r?\n')
The output will show the lines that differ, each representing a single property or primitive value; e.g.:
InputObject SideIndicator
----------- -------------
"DOB": "12-03-1994" =>
"DOB": "12-03-1999" <=
Note:
=> / <= indicate that the line is unique to the RHS / LHS.
Again, the explicit reconversion to JSON is done to ensure uniform formatting; in this case, a line-oriented pretty-printed format that enables property-by-property comparison.
Again, you may have to use -Depth to prevent truncation of data.
For interactive inspection of differences, you can try a difference-visualization tool, such as the one built into Visual Studio Code, by passing the two JSON strings in pretty-printed form via files to code --diff <file1> <file2>.
As for what you tried:
ConvertFrom-Json creates [pscustomobject] instances, so you're comparing two instances of that type:
If you use -eq, reference equality is tested for, because [pscustomobject] is a reference type and doesn't implement custom equality comparison.
Therefore, $po_ps_output -eq $po_python_output will only be $true if the two variables point to the very same object in memory - which is clearly not the case here, so you'll always get $false.
If you use Compare-Object, the two instances are compared by their .ToString() values.
As of PowerShell Core 7.0.0-preview.4, regrettably, calling .ToString() on an instance of [pscustomobject] yields the empty string (''), which should be considered a bug - see this GitHub issue.
Therefore, Compare-Object $po_ps_output $po_python_output (unhelpfully) considers the two instances equal and returns nothing, since equal objects are by default not output (use -IncludeEqual to include them).
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.