I have this list of PSObjects, each of which contains a Hashtable. Currently I can get it out like this:
foreach ($item in $myListOfItems) { $item.Metadata["Title"] }
However, I am wondering if I can do it somehow with piping and Select. Is this possible? For example:
$myListOfItems | Select $_.Metadata["Title"]
...which only outputs a whole bunch of blank lines :(
Any ideas? Thanks so much in advance!
What about
$myListOfItems | select #{ Label="Title";Expression={$_.Metadata["Title"]}}
The original idea was close but just got the syntax wrong. To use $_, the expression must be within a script-block parameter (Powershell V3+):
$myListOfItems | select { $_.Metadata["title"] }
Without the enclosing script-block, $_ is evaluated in the context of the whole pipeline instead of the pipeline element (Select-Object in this case). At this level, $_ is not defined so evaluates to $null. PowerShell is somewhat forgiving about using $null inappropriately. In particular, attempting to retrieve a property of $null is not an error, it just returns $null. So $null.Metadata["title"] → $null["title"] → $null. Select-Object $null just outputs an empty selection object for each input object thus resulting in the blank lines.
It should be noted that, strictly speaking, neither the originally suggested Select-Object (as fixed) nor the hash table version in the answer provide the same output as the ForEach loop. The loop produces a sequence of values from the .Metadata["Title"] members while Select-Object produces a sequence of selection objects [Selected.System.Collections.Hashtable] each containing a member (named $_.Metadata["Title"] and Title respectively) which holds the value. Whether this causes a problem depends on the usage.
For a really short version, try:
$myListOfItems.Metadata.Title
Since PowerShell V3, specifying a non-existent member of a collection causes Powershell to perform a member enumeration† whereby it would enumerate the collection and return a sequence of values of the specified member of each element of the collection (assuming there is one). If we assume left to right evaluation then the expression would be evaluated as:
($myListOfItems.Metadata).Title
Firstly enumerating the collection of items, as it has no Metadata member. Each item does have a Metadata member thus resulting in an Array of the Metadata members. Then this collection of Metadata hashtables, having no Title member, is enumerated to produce an Array of the Title elements of the hashtables.
† I think the term member enumeration is somewhat misleading. It is not the members which are being enumerated but the objects in the collection and an Array of the specified member of each object is then created. Indeed, even though the manual about_Hash_Tables states that the hash table has properties named after the keys with corresponding values, this is actually just Powershell shorthand for invoking the indexer [] (which actually appears to override member access so beware of hash tables with keys "Count", "Keys", "Values", ... if you want to access the actual hash table members of these names). So, strictly speaking, there is no 'Title' member. The term "enumerated collection member access" (ECMA ;) would better describe what is happening but after all "A rose by any other name".
Related
Given an ordered dictionary, I would like to get the key at index 0. I can of course do a loop, get the key of the first iteration and immediately break out of the loop. But I wonder if there is a way to do this directly?
My Google-Fu has not turned up anything, and some shots in the dark have failed too. I have tried things like
$hash[0].Name
and
$hash.GetEnumerator()[0].Name
I found this discussion about doing it in C#, which lead to this
[System.Collections.DictionaryEntry]$test.Item(0).Key
but that fails too. Is this just something that can't be done, or am I going down the wrong path?
Use the .Keys collection:
$orderedHash = [ordered] #{ foo = 1; bar = 2 }
# Note the use of #(...)
#($orderedHash.Keys)[0] # -> 'foo'
Note:
Use of #(...), the array-subexpression operator, enumerates the .Keys collection and collects its elements in an [object[]] array, which enables access by positional indices.
The .Keys collection itself implements only the System.Collections.ICollection interface, which supports enumeration, but not indexed access.
#(...) to enable indexing will no longer be necessary in PowerShell (Core) 7.3+ (.NET 7+), thanks to an improvement initiated by iRon via GitHub issue #56835: The .Keys collection will then implement the System.Collections.IList interface, which does support positional indexing. Note that this change will only apply to ordered hashtables ([ordered] #{ ... }, System.Collections.Specialized.OrderedDictionary), not also to regular (unordered) hashtables (#{ ... }) or other types implementing the System.Collections.IDictionary interface. However, iRon has created a follow-up proposal to make the .Keys property indexable for all (definition-)ordered and sorted dictionaries - see GitHub issue #63537.
.Name is an alias property provided by PowerShell; the type-native property containing each System.Collections.DictionaryEntry's key is .Key
Given a properly defined variable
$test = New-Object System.Collections.ArrayList
.Add pollutes the pipeline with the count of items in the array, while .AddRange does not.
$test.Add('Single') will dump the count to the console. $test.AddRange(#('Single2')) will be clean with no extra effort. Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Given that .AddRange requires coercing to an array when not using a variable (that is already an array) I am tending towards using [void]$variable.Add('String') when I know I need to only add one item, and [void]$test.AddRange($variable) when I am adding an array to an array, even when $variable only contains, or could only contain, a single item. The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
Why the different behavior? Is it just an oversight, or is there some intentional behavior I am not understanding?
Because many years ago, someone decided that's how ArrayList should behave!
Add() returns the index at which the argument was inserted into the list, which may indeed be useful and makes sense.
With AddRange() on the other hand, it's not immediately clear why it should return anything, and if yes, what? The index of the first item in the input arguments? The last? Or should it return a variable-sized array with all the insert indices? That would be awkward! So whoever implemented ArrayList decided not to return anything at all.
In C# or VB.NET, for which ArrayList was initially designed, "polluting the pipeline" doesn't really exist as a concept, the runtime would simply omit copying the return value back to the caller if someone invokes .Add() without assigning to a variable.
The [void] here isn't required, but I wonder if it's just best practice to have it, depending of course on the answer above. Or am I missing something there too?
No, it's completely unnecessary. AddRange() is not magically one day gonna change to output anything.
If you don't ever need to know the insert index, use a [System.Collections.Generic.List[psobject]] instead:
$list = [System.Collections.Generic.List[psobject]]::new()
# this won't return anything, no need for `[void]`
$list.Add(123)
If for some reason you must use an ArrayList, you can "silence" it by overriding the Add() method:
function New-SilentArrayList {
# Create a new ArrayList
$newList = [System.Collections.ArrayList]::new()
# Create a new `Add()` method, then return the list
$newAdd = #{
InputObject = $newList
MemberType = 'ScriptMethod'
Name = 'Add'
Value = {param($obj) $this.AddRange(#($obj))}
}
Write-Output $(
Add-Member #newAdd -Force -PassThru
) -NoEnumerate
}
Now your ArrayList's Add() will never make a peep again!
PS C:\> $list = New-SilentArrayList
PS C:\> $list.Add(123)
PS C:\> $list
123
Apparently I didn't quiet understand where you where heading to.
"Add pollutes the pipeline", at a second thought is a correct statement but .Net methods like $variable.Add('String') do not use the PowerShell pipeline by itself (until the moment you output the array using the Write-Output command which is the default command if you do not assign it to a variable).
The Write-Output cmdlet is typically used in scripts to display
strings and other objects on the console. However, because the default
behavior is to display the objects at the end of a pipeline, it is
generally not necessary to use the cmdlet.
The point is that Add method of ArrayList returns a [Int32] "The ArrayList index at which the value has been added" and the AddRange doesn't return anything. Meaning if you don't assign the results to something else (which includes $Null = $test.Add('Single')) it will indeed be output to the PowerShell Pipeline.
Instead you might also consider to use the Add method of the List class which also doesn't return anything, see also: ArrayList vs List<> in C#.
But in general, I recommend to use native PowerShell commands that do use the Pipeline
(I can't give you a good example as it is not clear what output you expect but I noticed another question you removed and from that question, I presume that this Why should I avoid using the increase assignment operator (+=) to create a collection answer might help you further)
Is there a way to pipe a whole object through a pipeline and process mentioned Object in one step? Put simply the $PSItem Variable on the other side of my pipeline should have the same value as the whole object which was put through the pipe.
I've found the following method to have a sort of anonymous functions in posh though this processes every item in the input object separately (As this is what the process block in advanced functions is meant for).
Therefor the code:
Get-Service | & {process {return $_.length}}
Returns:
1 1 1 1 1 1 1..
What I'm looking for is a way to access the full object with the $_/$PSItem variable after the pipeline and process it further / return properties of this object.
The Process Block in PowerShell can take single member arrays as its input which then leads it to use the whole member of the array to process rather then all members of the Object.
Using the comma operator one can create a single member array in a simple fashion.
Further information about Operators
The following code uses the comma operator to put the object array which is returned by Get-Process into a single member array.
,(Get-Process)
You are now free to use the object in the pipeline and access properties of it.
,(Get-Process) | & {process {if($_.length -ge 10) {return "Greater / equals 10"}else{return "Smaller than 10"}}}
Suppose I have a list:
$DeletedUsers = New-Object System.Collections.Generic.List[System.object]
So I can easily add and remove users from the collection.
I want to be able to pass this list to a function that does something, but without modifying the original list, and it must stay of the same generic list type.
convertAll() seems to do exactly what I want without having to script out the creation of a new list myself with foreach-object, but I don't understand how to utilize the overload definitions (or quite understand what they mean).
There are many examples in C#, but I haven't been able to find one that demonstrates it in PoSH.
Example Scenario:
Assume $DeletedUsers contains a list of User objects of PSCustomObject type. With typical "User" properties such as department or Employment status. This list should be be capable of being passed to functions that will change statuses of the users property that can then be added to a separate output list of the same Generic.List type.
Currently any changes by the example function.
Function ProcessUser {
[Cmdletbinding()]
Param($DeletedUsers)
begin{$DeletedUsersClone = $($DeletedUsers).psobject.copy()} #OR similar
process{
$DeletedUsersClone | foreach { $_ | Add-Member -NotePropertyName
"Processed" -NotePropertyValue "Processed:00"; $Outputlist.add($_)}
}
}
Impacts the original $DeletedUsers, erroneously adding processed information to a list that should stay static.
There are alternate ways to prevent this from impacting the ultimate objective of the script, but the question is:
How do I create a True, non-referenced clone of a System.Collections.Generic.List[System.object] using built-in C# methods.
The trick is to use a scriptblock with an explicit cast to the delegate type. This looks like:
$DeletedUsers.ConvertAll([converter[object,object]] {param ($i) <# do convert #> })
Note:
As became clear later, the OP is looking for a deep clone of the original list; i.e., not only should the list as a whole be cloned, but also its elements.
This answer only shows how to create a shallow clone (and how to pass a list read-only).
See Bruce Payette's helpful answer for a deep-cloning approach based on the .ConvertAll method; with [pscustomobject] instances, you'd use the following (but note that .psobject.Copy() only creates shallow copies of the [pscustomobject] instances themselves):
$DeletedUsers.ConvertAll([converter[pscustomobject, pscustomobject]] {param ($obj) $obj.psobject.copy() })
If you want to pass a shallow clone of your list to a callee:
Pass [Collections.Generic.List[object]]::new($DeletedUsers) (PSv5+ syntax)
Alternatively, if the type of the list elements isn't known or if you don't want to repeat it, pass: $DeletedUsers.GetRange(0, $DeletedUsers.Count)
If you just want to prevent accidental modification of your list by a callee:
Pass $DeletedUsers.AsReadOnly() - however, that does change the type, namely to [Collections.ObjectModel.ReadOnlyCollection[object]]
I can define an array as a generic list like this
$array = [Collections.Generic.List[String]]#()
And I can define an element in a hash table as an array like this
$hash = #{
array = #()
}
But I can't define an element in a hash table as a Generic List, like this
$hash = #{
array = [Collections.Generic.List[String]]#()
}
Instead I get this error
Cannot convert the "System.Object[]" value of type "System.Object[]"
to type "System.Collections.Generic.List`1[System.String]
I have been using Generic Lists to avoid the (minor in my case, to be sure) performance issue with regularly adding to a standard array. But this is the first time I have needed to create a hash table that contains a generic list (for a complex return value).
So, first question, is this is even possible? And second question, what is the difference under the hood between simply setting a variable and a hash table element?
EDIT: This is interesting. I CAN use
[System.Collections.ArrayList]#()
and it works. So, now I am curious what exactly is the difference between
[System.Collections.ArrayList]
and
[Collections.Generic.List[String]]
I guess this is the down side of being self taught. I found reference to [Collections.Generic.List[String]] on a BLOG, and maybe [System.Collections.ArrayList] is a much better answer? What I think I understand from this is that the former is specifically typed as a list of strings, while the latter is a list of generic objects, which then must be cast in use, which has potential bug and performance issues. Still, I wonder why the typed generic doesn't work in a hash table.