Select elements from powershell objects - powershell

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

Related

How to reference PSCredential.Empty in powershell script

I am trying to use empty credentials for anonymous access. How to I set a variable to empty credentials. I am trying to use PSCredential.Empty Property
Santiago Squarzon has provided the solution:
[pscredential]::Empty
is the PowerShell equivalent of C#'s PSCredential.Empty, i.e. access to the static Empty property of the System.Management.Automation.PSCredential type.
PowerShell's syntax for accessing .NET types and their - instance or static - member:
In order to refer to a .NET type by its literal name in PowerShell, you must specify it as a type literal, which is the type name enclosed in [...]
[pscredential] works as-is, even in the absence of a using namespace System.Management.Automation statement, because it happens to be a type accelerator, i.e. a frequently used type that you can access by a short, unqualified name (which can, but needn't be the same name as that of the type it references; e.g., [xml] is short for System.Xml.XmlDocument).
Generally, you either need a using namespace statement if you want to use the unqualified name only, or you need to use the namespace-qualified type name; e.g., [System.Management.Automation.PSCredential] or - given that the System. component is optional - [Management.Automation.PSCredential]
For a complete discussion of PowerShell's type literals, see this answer.
While instance members of .NET types are accessed with . (member-access operator), just like in C#, accessing static members requires a different operator: ::, the static member-access operator.
A distinct operator is necessary, because type literals are objects themselves, namely instances of System.Reflection.TypeInfo, so that using . accesses the latter's instance properties; e.g. [pscredential].FullName returns the type's namespace-qualified name.
Also note that, in keeping with PowerShell's general case-insensitive nature, neither type literals nor member names need be specified case-exactly.

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 list inherited methods with Get-Member

I am exploring what pipeline functionality is also available as methods. For example...
#('1', '2', '3', '2') | where {$_ -in #('2')} will return the two '2'. But I can also do...
#('1', '2', '3', '2').Where({$_ -in #('2')}) and get the same result.
I also want to Group, and while
#('1', '2', '3', '2') | group {$_.Count -gt 1} doesn't do the actual grouping I want, it does... something. But
#('1', '2', '3', '2').Group({$_.Count -gt 1}) fails with Method invocation failed because [System.String] does not contain a method named 'Group'.
So that got me looking for what IS available as intrinsic methods.
#('1') | Get-Member -MemberType Method -Force | Format-Table doesn't even include Where and yet the Method is there. So I assume it is inheriting that method. But I don't know how to include inherited Methods. I though -Force would do it but it doesn't, nor does -View provide any more detail.
Is there a way to explore these intrinsic methods such as .Where()? And perhaps tangentially, is there a way to Group as a method rather than with the pipeline?
Dabombber's answer is helpful, but let me try to give a systematic overview:
The .Where() and .ForEach() array methods are instances of what are known as intrinsic members; no other such intrinsic array-processing (collection-processing) methods exist as of PowerShell 7.1
Intrinsic members are members (properties and methods) that the PowerShell engine exposes on objects of any type - except if the object has a native .NET type member of the same name, which takes precedence.
Discovery limitations, as of PowerShell 7.1 / late 2020:
Intrinsic members cannot be discovered via Get-Member
GitHub proposal #11798 aims to introduce this ability.
Tab completion works only for .Where() and .ForEach() (on collection values), but not for other intrinsic members, such as the .psobject property (see below).
However, they are documented in the conceptual about_Intrinsic_Members help topic.
In short: as of this writing, you cannot discover intrinsic members programmatically.
List of intrinsic members:
Intrinsic members that enable unified treatment of collections (arrays) and scalars:
PowerShell adds .Count and .Length properties (aliases of each other) even to scalars (assuming they don't have type-native properties of the same name), with an non-null scalar sensibly reporting 1 (e.g., (42).Count), and $null reporting 0 ($null.Count)
Pitfalls:
Accessing these properties while Set-StrictMode -Version 2 or higher is in effect unexpectedly causes statement-terminating errors, because the engine treats them as non-existent; this long-standing bug is discussed in GitHub issue #2798.
IEnumerable instances such as returned by LINQ methods aren't collections per se, and calling .Count on them triggers enumeration and returns a .Count property value from each enumerated element instead (which defaults to 1); e.g., [Linq.Enumerable]::Range(1,3).Count returns array 1, 1, 1 instead of 3.
There is a bug in Windows PowerShell (as of the latest and final version, 5.1), which has since been corrected in PowerShell (Core): [pscustomobject] instances unexpectedly do not have .Count and .Length properties (see GitHub issue #3671 for the original bug report) - see this answer.
Similarly, PowerShell allows you to index even into a scalar (again, unless preempted by a type-native indexer, such as that of XmlElement); e.g., (42)[0] and (42)[-1] both return 42, i.e. the scalar itself.
Collection-processing intrinsic members:
These are (only) the .Where() and .ForEach() methods discussed above.
Note that, in the the interest of unified treatment of collections and scalars, these methods also work on scalars; e.g., (42).ForEach({ $_ + 1 }) yields 43.
Pitfalls:
In Windows PowerShell (since fixed in PowerShell [Core] 7+), some types of scalars - notably [pscustomobject] and [xml] - do not provide these methods, which should be considered a bug.
The System.Collections.Generic.List<T> collection type has its own .ForEach() method that therefore shadows PowerShell's; this type-native method neither supports the use of $_ nor producing output.
Intrinsic members for reflection (not available on $null):
The .psobject property is a rich source of reflection on any object, such as its list of properties; e.g., (Get-Date).psobject.Properties lists metadata about all public properties of instances of System.DateTime.
.pstypenames (also available as .psobject.TypeNames) lists all ETS (Extended Type System) type names associated with an instance; by default, the property contains the full names of the object's .NET type and its base type(s).
The .psbase, .psextended, and .psadapted properties return categorized subsets of type members, namely .NET-native, ETS-added, and adapted members.
Adapted members are members that surface information from a different data representation as if they were native type members, notably in the context of CIM (WMI) and PowerShell's adaptation of the XML DOM.
For instance, [xml] (System.Xml.XmlDocument) instances have members in all three categories; try $xml = ([xml] '<someElement>some text</someElement>'); '-- .psbase:'; $xml.psbase; '-- .psextended'; $xml.psextended; '-- .psadapted'; $xml.psadapted
Your arrays are getting unrolled through the pipeline, so #('1') | Get-Member -MemberType Method -Force is showing members for a string.
You can bypass that by sending it as the second object of an array
,#('1') | Get-Member -MemberType Method
outputting it without enumeration
Write-Output -NoEnumerate #('1') | Get-Member -MemberType Method
or passing it as a parameter instead of through the pipeline.
Get-Member -InputObject #('1') -MemberType Method
Some of the methods may also be static, so you could use the -Static switch for Get-Member.
There is a list of array methods here, although I have no idea why not all of them show up, perhaps it's an intentional part of the Get-Member cmdlet or maybe they're part of the PowerShell language itself rather than proper methods.

Can't seem to use generic collection with a PowerShell class

I'm trying to invoke the List[T](IEnumerable) directly adding an item to the initial List like so, where T is a PowerShell class I've written (the below example uses the class name Thing:
$someObject = Get-Thing # returns a single object
$list = [List[Thing]]::new(#( $someObject ))
However, this yields an error suggesting it can't find the overload for this constructor:
Cannot find an overload for "List`1" and the argument count: "1".
Setting List[T] to the Object class works, however:
$someObject = Get-Thing
$list = [List[Object]]::new(#( $someObject ))
While this works, I'm unsure why I'm unable to use my PowerShell class as the type. My understanding is that only context-bound types and (by default) nested types are unable to be used with generics, but the following shows that my class is not a ContextBoundObject:
class Thing {
$Name
Thing($name) {
$this.Name = $name
}
}
$thing = [Thing]::new('Bender')
$thing -is [System.ContextBoundObject] # ==> False
I'm not certain if a PowerShell class would be a nested type of some sort, and about_Classes does not mention nested types.
I'm unsure why I'm unable to use my PowerShell class as the type
The array subexpression operator #() returns its results as [object[]] - a type which satisfies the argument type [IEnumerable[object]] - which is why it always works when you use [object] as the type parameter for the receiving collection type.
So, what to do about that?
If the array consists only of [Thing]'s, you can explicitly cast to a more specific collection type that implements [IEnumerable[Thing]]:
$list = [List[Thing]]::new([Thing[]]#( $someObject ))
To complement Mathias R. Jessen's helpful answer, which explains the problem well and offers an effective solution:
PowerShell's casts are not only syntactically more convenient, but also more flexible when it comes to on-demand type conversions.
Indeed, using a cast instead of calling a constructor, via the static ::new() method, does work:
using namespace System.Collections.Generic
class Thing { [string] $Name; Thing([string] $name) { $this.Name = $name } }
# Both of the following work:
# Single [Thing] instance.
$list = [List[Thing]] [Thing]::new('one')
# Multiple [Thing] instances, as an array, via the grouping operator, (...)
# #(...), the array subexpression operator, works too, but is unnecessary.
$list = [List[Thing]] ([Thing]::new('one'), [Thing]::new('two'))
PowerShell's automatic type conversions, as also used in casts:
Unfortunately, as of this writing the rules aren't documented, but a comment in the source-code provides a high-level overview, as does the (pretty low-level) ETS type converters documentation, which can be summarized as follows, in descending order of precedence:
First, engine-internal, fixed conversion rules may be applied (see source-code link above).
A notable internal rule concerns to-string conversions: while any .NET type supports it by an explicit call to its .ToString() method (inherited from the root of the object hierarchy, System.Object), PowerShell applies custom rules:
If a type has a culture-sensitive .ToString(<IFormatProvider>) overload, PowerShell passes the invariant culture deliberately, to achieve a culture-invariant representation, whereas a direct .ToString() call would yield a culture-sensitive representation - see this answer for details; e.g., in a culture where , is the decimal mark, [string] 1.2 returns '1.2' (period), whereas (1.2).ToString() returns '1,2' (comma).
Collections, including arrays, are stringified by concatenating their (stringified) elements with a space as the separator (by default, can be overridden with preference variable $OFS); e.g., [string] (1, 2) returns 1 2, whereas (1, 2).ToString() returns merely System.Object[].
Also, PowerShell converts freely:
between different number types (when possible).
between numbers and strings (in a culture-invariant manner, recognizing only . as the decimal mark when converting from a string).
and allows any data type to be converted to (interpreted as) as Boolean - see the bottom section of this answer for the rules.
Next, TypeConverter or (PSTypeConverter) classes that implement custom conversions for specific types are considered.
If the input type is a string ([string]), a static ::Parse() method is considered, if present: first, one with a culture-sensitive signature, ::Parse(<string>, <IFormatProvider>), in which case the invariant culture is passed, and, otherwise one with signature ::Parse(<string>).
Next, a single-argument constructor is considered, if the input type matches the argument's type or is convertible to it.
If an implicit or explicit conversion operator exists for conversion between the input and the target type.
Finally, if the input object implements the System.IConvertible interface and the target type is a supported-by-the-implementation primitive .NET type except [IntPtr] and [UIntPtr] or one of the following types: [datetime], [DBNull], [decimal].