OrderedDictionary key name at specified index - powershell

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

Related

Select elements from powershell objects

Hi everybody!
Short question:
Why does this work:
(Get-CimClass Win32_Share).CimClassMethods['Create'].Parameters
But this doesn’t:
(Get-CimClass Win32_Share).CimClassMethods.Create.Parameters
The same result can be achieved by:
((Get-CimClass Win32_Share).CimClassMethods|where-object {$_.name -match 'create'}).parameters
With the same logic I would expect that the same applies for:
(Get-command *help*)['get-help']
Or
(Get-command *help*)['cmdlet']
Or
(Get-childitem)['test.txt']
But it doesn’t. Only the method with where-object is possible here
Other considerations:
I know that this is should be the default way to retrive items from an hashtable and not pscustomobject but I also would like to better understand where else I can use this method. I searched the whole day on google but didn’t find anything.
Thanks in advance
Franco
The string-based indexing (['Create']) works, because the Microsoft.Management.Infrastructure.Internal.Data.CimMethodDeclarationCollection type that it is applied to implements a parameterized Item property that has a [string]-typed parameter (abstracted; verify with Get-Member Item -InputObject Get-CimClass Win32_Share).CimClassMethods):
# {get;} indicates that the property is read-only.
Item ParameterizedProperty <T> Item(string key) {get;}
Note: Strictly speaking, for something like ['Create'] to be accepted, it is sufficient for the Item property parameter to be [object]-typed, which is the case for the non-generic [hashtable] type, for instance.
For types with such a method, PowerShell, like C#, supports syntax ['Create'] as syntactic sugar for .Item('Create'), and the implication is that a key-based item lookup is performed.
Typically, such types also implement the IDictionary interface (e.g., [hasthable]), but that isn't the case here.
CimMethodDeclarationCollection behaves similarly to an [ordered] hashtable, in that it supports both positional (e.g, [0]) and key-based indexing (e.g., ['Create']),[1] but with one crucial difference:
PowerShell does not enumerate IDictionary-implementing types in the pipeline (you'll have to call .GetEnumerator() to achieve that), whereas CimMethodDeclarationCollection - due to not implementing IDictionary - is enumerated, like other collections such as arrays; that is, its elements / entries are sent one by one through the pipeline.
As for why something like the following doesn't work:
(Get-command help)['get-help']
Get-command *help* outputs multiple objects, which (...) automatically collects in a regular PowerShell array, of type [object[]].
[object[]] (based on System.Array), does not have the requisite parameterized property that would support ['get-help'] syntax - arrays support only positional indexing (e.g. [0] to refer to the first element), based on a parameterized Item property whose parameter is [int]-typed, implemented as part of the IList interface (abstracted; verify with Get-Member Item -InputObject #()):
# Note: IList.Item indicates that the property is implemented
# as part of the IList interface.
# {get;set;} indicates that the property is read-write.
Item ParameterizedProperty <T> IList.Item(int index) {get;set;}
However, given PowerShell's member-access enumeration feature it is reasonable to expect ['get-help'] to then be applied to the individual elements of the array, given that's how it works with ., the member-access operator (e.g., (Get-Command *help*).Name).
As of PowerShell 7.2.4, however, only ., not also ["someKey"] performs this enumeration; this surprising asymmetry is the subject of GitHub issue #17514.
[1] An [int]-typed key doesn't necessarily imply positional semantics, but does in the context of implementing the IList or ICollection interfaces (or their generic equivalent).

Why can't I call IEnumerable.Sum() on an array in PowerShell? [duplicate]

I am trying to use LINQ in PowerShell. It seems like this should be entirely possible since PowerShell is built on top of the .NET Framework, but I cannot get it to work. For example, when I try the following (contrived) code:
$data = 0..10
[System.Linq.Enumerable]::Where($data, { param($x) $x -gt 5 })
I get the following error:
Cannot find an overload for "Where" and the argument count: "2".
Never mind the fact that this could be accomplished with Where-Object. The point of this question is not to find an idiomatic way of doing this one operation in PowerShell. Some tasks would be light-years easier to do in PowerShell if I could use LINQ.
The problem with your code is that PowerShell cannot decide to which specific delegate type the ScriptBlock instance ({ ... }) should be cast.
So it isn't able to choose a type-concrete delegate instantiation for the generic 2nd parameter of the Where method. And it also does't have syntax to specify a generic parameter explicitly. To resolve this problem, you need to cast the ScriptBlock instance to the right delegate type yourself:
$data = 0..10
[System.Linq.Enumerable]::Where($data, [Func[object,bool]]{ param($x) $x -gt 5 })
Why does [Func[object, bool]] work, but [Func[int, bool]] does not?
Because your $data is [object[]], not [int[]], given that PowerShell creates [object[]] arrays by default; you can, however, construct [int[]] instances explicitly:
$intdata = [int[]]$data
[System.Linq.Enumerable]::Where($intdata, [Func[int,bool]]{ param($x) $x -gt 5 })
To complement PetSerAl's helpful answer with a broader answer to match the question's generic title:
Note: The following applies up to at least PowerShell 7.2. Direct support for LINQ - with syntax comparable to the one in C# - is being discussed for a future version of PowerShell Core in GitHub issue #2226.
Using LINQ in PowerShell:
You need PowerShell v3 or higher.
You cannot call the LINQ extension methods directly on collection instances and instead must invoke the LINQ methods as static methods of the [System.Linq.Enumerable] type to which you pass the input collection as the first argument.
Having to do so takes away the fluidity of the LINQ API, because method chaining is no longer an option. Instead, you must nest static calls, in reverse order.
E.g., instead of $inputCollection.Where(...).OrderBy(...) you must write [Linq.Enumerable]::OrderBy([Linq.Enumerable]::Where($inputCollection, ...), ...)
Helper functions and classes:
Some methods, such as .Select(), have parameters that accept generic Func<> delegates (e.g, Func<T,TResult> can be created using PowerShell code, via a cast applied to a script block; e.g.:
[Func[object, bool]] { $Args[0].ToString() -eq 'foo' }
The first generic type parameter of Func<> delegates must match the type of the elements of the input collection; keep in mind that PowerShell creates [object[]] arrays by default.
Some methods, such as .Contains() and .OrderBy have parameters that accept objects that implement specific interfaces, such as IEqualityComparer<T> and IComparer<T>; additionally, input types may need to implement IEquatable<T> in order for comparisons to work as intended, such as with .Distinct(); all these require compiled classes written, typically, in C# (though you can create them from PowerShell by passing a string with embedded C# code to the Add-Type cmdlet); in PSv5+, however, you may also use custom PowerShell classes, with some limitations.
Generic methods:
Some LINQ methods themselves are generic and therefore require one or more type arguments.
In PowerShell (Core) 7.2- and Windows PowerShell, PowerShell cannot directly call such methods and must use reflection instead, because it only supports inferring type arguments, which cannot be done in this case; e.g.:
# Obtain a [string]-instantiated method of OfType<T>.
$ofTypeString = [Linq.Enumerable].GetMethod("OfType").MakeGenericMethod([string])
# Output only [string] elements in the collection.
# Note how the array must be nested for the method signature to be recognized.
PS> $ofTypeString.Invoke($null, (, ('abc', 12, 'def')))
abc
def
For a more elaborate example, see this answer.
In PowerShell (Core) 7.3+, you now have the option of specifying type arguments explicitly (see the conceptual about_Calling_Generic_Methods help topic); e.g.:
# Output only [string] elements in the collection.
# Note the need to enclose the input array in (...)
# -> 'abc', 'def'
[Linq.Enumerable]::OfType[string](('abc', 12, 'def'))
The LINQ methods return a lazy enumerable rather than an actual collection; that is, what is returned isn't the actual data yet, but something that will produce the data when enumerated.
In contexts where enumeration is automatically performed, notably in the pipeline, you'll be able to use the enumerable as if it were a collection.
However, since the enumerable isn't itself a collection, you cannot get the result count by invoking .Count nor can you index into the iterator; however, you can use member-access enumeration (extracting the values of a property of the objects being enumerated).
If you do need the results as a static array to get the usual collection behavior, wrap the invocation in [Linq.Enumerable]::ToArray(...).
Similar methods that return different data structures exist, such as ::ToList().
For an advanced example, see this answer.
For an overview of all LINQ methods including examples, see this great article.
In short: using LINQ from PowerShell is cumbersome and is only worth the effort if any of the following apply:
you need advanced query features that PowerShell's cmdlets cannot provide.
performance is paramount - see this article.
If you want to achieve LINQ like functionality then PowerShell has some cmdlets and functions, for instance: Select-Object, Where-Object, Sort-Object, Group-Object. It has cmdlets for most of LINQ features like Projection, Restriction, Ordering, Grouping, Partitioning, etc.
See Powershell One-Liners: Collections and LINQ.
For more details on using Linq and possibly how to make it easier, the article LINQ Through Powershell may be helpful.
I ran accross LINQ, when wanting to have a stable sort in PowerShell (stable: if property to sort by has the same value on two (or more) elements: preserve their order). Sort-Object has a -Stable-Switch, but only in PS 6.1+. Also, the Sort()-Implementations in the Generic Collections in .NET are not stable, so I came accross LINQ, where documentation says it's stable.
Here's my (Test-)Code:
# Getting a stable sort in PowerShell, using LINQs OrderBy
# Testdata
# Generate List to Order and insert Data there. o will be sequential Number (original Index), i will be Property to sort for (with duplicates)
$list = [System.Collections.Generic.List[object]]::new()
foreach($i in 1..10000){
$list.Add([PSCustomObject]#{o=$i;i=$i % 50})
}
# Sort Data
# Order Object by using LINQ. Note that OrderBy does not sort. It's using Delayed Evaluation, so it will sort only when GetEnumerator is called.
$propertyToSortBy = "i" # if wanting to sort by another property, set its name here
$scriptBlock = [Scriptblock]::Create("param(`$x) `$x.$propertyToSortBy")
$resInter = [System.Linq.Enumerable]::OrderBy($list, [Func[object,object]]$scriptBlock )
# $resInter.GetEnumerator() | Out-Null
# $resInter is of Type System.Linq.OrderedEnumerable<...>. We'll copy results to a new Generic List
$res = [System.Collections.Generic.List[object]]::new()
foreach($elem in $resInter.GetEnumerator()){
$res.Add($elem)
}
# Validation
# Check Results. If PropertyToSort is the same as in previous record, but previous sequence-number is higher, than the Sort has not been stable
$propertyToSortBy = "i" ; $originalOrderProp = "o"
for($i = 1; $i -lt $res.Count ; $i++){
if(($res[$i-1].$propertyToSortBy -eq $res[$i].$propertyToSortBy) -and ($res[$i-1].$originalOrderProp -gt $res[$i].$originalOrderProp)){
Write-host "Error on line $i - Sort is not Stable! $($res[$i]), Previous: $($res[$i-1])"
}
}
There is a simple way to make Linq chaining fluent, by setting a using statement to the Linq namespace, Then you can call the where function directly, no need to call the static Where function.
using namespace System.Linq
$b.Where({$_ -gt 0})
$b is an array of bytes, and I want to get all bytes that are greater than 0.
Works perfect.

Get-ItemProperty with Registry, returned object type

I can use Get-Item with folders, files and registry keys, and the type of the object I get back will make sense; [System.IO.DirectoryInfo], [System.IO.FileInfo] or [Microsoft.Win32.RegistryKey].
But with registry properties, what using Get-ItemProperty returns is a [System.Management.Automation.PSCustomObject]. Is this because there is no dedicated type for registry property? That seems odd. But my Google-Fu is not turning anything up.
My use case is this, I am doing a series of Copy and Move tasks, with all four item types potentially getting copied or moved, and I want to implement an option to rename an existing destination rather than overwriting or failing. And exactly what the rename options are depends on the object type. And from a readability standpoint, PSCustom Object or a simple else for RegistryProperty is a bit ugly. So, looking for a way to get the property back as a type with a more obvious name, so when I look at the code again in 12 months it makes some sense.
Get-ItemProperty returns what is conceptually a registry value object: a property of a registry key that has a name and a - uh... - value (the named value object's data).
The .NET registry API has no type to represent such a value object - instead, it allows access via the registry key type's .GetValue($valueName) (to get a specific value object's data[1]) and .GetValueNames() methods (to get the list of all value names).
The PowerShell implementers apparently chose not to implement their own .NET type, and chose to use PowerShell's general-purpose, dynamic "property-bag" type, [pscustomobject][2] to model these value objects.
If you want to avoid the [pscustomobject] instances that Get-ItemProperty returns, you can use Get-Item instead, which returns a Microsoft.Win32.RegistryKey instance, i.e. an instance of the .NET type representing a key, on which you can invoke the methods mentioned above.
As an aside: If you're just interested in a given value object's data, you can use the PSv5+
Get-ItemPropertyValue cmdlet (e.g.
Get-ItemPropertyValue HKCU:\Console -Name LineWrap directly returns the [int] data of the targeted value).
[1] Additionally, as js2010's answer shows, the .GetValueKind() method returns an enum value that identifies a given value object's registry-specific data type. These types imply what .NET types are used to represent them, as returned by .GetValue(), but in some cases have no direct equivalent (ExpandString, MultiString, Unknown) and require additional work to interpret them correctly.
[2] It is possible - but wasn't done in this case - to assign (one or more) self-chosen type names to [pscustomobject] instances, which PowerShell reflects as the type name in Get-Member output (only the first, if there are multiple) and which it respects for ETS type definitions and format-data definitions. However, such pseudo types are not accessible as type literals; e.g.: $obj = [pscustomobject] #{ PSTypeName = 'MyType'; prop = 'foo' } allows you test for this type name with $obj.pstypenames -contains 'MyType', but not with $obj -is [MyType]. That said, you can base parameter declarations on them, via the [PSTypeName()] attribute.
There is a way to get the type of the properties:
$key = get-item hkcu:\key1
$key.GetValueKind('value1')
DWord

Powershell - How-to use $List.convertAll() or alternate C# method to clone a list

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]]

Select item from PowerShell hashtable without using Foreach

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".