Identity Operator / Comparison without Implicit Type Conversion - powershell

When using PowerShell, occasionally we compare objects of different types. A common scenario being $int -eq $bool (i.e. where 0 -eq $false, 0 -ne $true, and any non-zero value only equals true, but not false).
Mostly that's a good thing; sometimes it's a pain (e.g. if I'm looking for all properties on an object which have value 1, but I don't want all those which have value $true).
Question
Is there an operator which performs this comparison without conversion; i.e. which essentially does the same as JavaScript's identity operator (===);?
Bonus Question
If there is such an operator, is there any tolerance? i.e. whilst I don't want to treat 1 and true as being identical, it would (sometimes) be helpful if I could treat [int]1 and [long]1 as though they are.

PowerShell do not offer such operator directly, but you can use standard .NET method [Object]::Equals to do such comparison.
Note:
It allows to types to implement they own equality comparison by overriding virtual Object.Equals method, thus it possible to have some weird behavior, if some custom type implement it improperly. But all standard .NET types should fine and consider themselves equals only to the instance of the same type.

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.

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

What is the most efficient operator to compare any two items?

Frequently I need to convert data from one type to another and then compare them. Some operators will convert to specific types first and this conversion may cause loss of efficiency. For instance, I may have
my $a, $b = 0, "foo"; # initial value
$a = (3,4,5).Set; # re-assign value
$b = open "dataFile"; # re-assign value
if $a eq $b { say "okay"; } # convert to string
if $a == 5 { say "yes"; } # convert to number
if $a == $b {} # error, Cannot resolve caller Numeric(IO::Handle:D: );
The operators "eq" and "==" will convert data to the digestible types first and may slow things down. Will the operators "eqv" and "===" skip converting data types and be more efficient if data to be compared cannot be known in advance (i.e., you absolutely have no clue what you are going to get in advance)?
It's not quite clear to me from the question if you actually want the conversions to happen or not.
Operators like == and eq are really calls to multi subs with names like infix:<==>, and there are many candidates. For example, there's one for (Int, Int), which is selected if we're comparing two Ints. In that case, it knows that it doesn't need to coerce, and will just do the integer comparison.
The eqv and === operators will not coerce; the first thing they do is to check that the values have the same type, and if they don't, they go no further. Make sure to use the correct one depending of if you want snapshot semantics (eqv) or reference semantics (===). Note that the types really must be the exact same, so 1e0 === 1 will not come out true because the one value is a Num and the other an Int.
The auto-coercion behavior of operators like == and eq can be really handy, but from a performance angle it can also be a trap. They coerce, use the result of the coercion for the comparison, and then throw it away. Repeatedly doing comparisons can thus repeatedly trigger coercions. If you have that situation, it makes sense to split the work into two phases: first "parse" the incoming data into the appropriate data types, and then go ahead and do the comparisons.
Finally, in any discussion on efficiency, it's worth noting that the runtime optimizer is good at lifting out duplicate type checks. Thus while in principle, if you read the built-ins source, == might seem cheaper in the case it gets two things have the same type because it isn't enforcing they are precisely the same type, in reality that extra check will get optimized out for === anyway.
Both === and eqv first check whether the operands are of the same type, and will return False if they are not. So at that stage, there is no real difference between them.
The a === b operator is really short for a.WHICH eq b.WHICH. So it would call the .WHICH method on the operands, which could be expensive if an operand is something like a really large Buf.
The a eqv b operator is more complicated in that it has special cased many object comparisons, so in general you cannot say much about it.
In other words: YMMV. And if you're really interested in performance, benchmark! And be prepared to adapt your code if another way of solving the problem proves more performant.

Is there a PowerShell equivalent for C#'s default operator, and if so what is the syntax?

In C#, it's possible to get the default value of any type using the default operator:
var i = default(int); // i == 0
// in C# 7.1+
int j = default; // j == 0
Is there a similar construct in PowerShell, and if so what is it? As far as I've been able to determine in my Googling and testing, default is only recognized by PS when present in switch blocks.
PowerShell has no direct language construct for it because it doesn't need it -- due to its loose typing you are almost never required to produce a value of a specific type and there is no support for creating generic types or functions. Untyped variables start off as $null if you do nothing special. Typed variables start off as whatever value you explicitly give them, and that's generally sufficient due to PowerShell's liberal rules for conversion ([int] "" and [int] $null are both 0).
Only in rare cases does this fail, like attempting to declare a variable of type DateTimeOffset, as there is no default constructor and $null or "" won't convert. Arguably, the fix there is to just explicitly construct a value using whatever the type does offer ([DateTimeOffset] $d = [DateTimeOffset]::Now, [DateTimeOffset] $d = [DateTimeOffset]::MinValue, [DateTimeOffset] $d = "0001-01-01 00:00Z"). Only in the very rare case that you have a dynamic type, and you'd like to get what C# would give you with default, would you need some special code. You can do it in pure PowerShell (well, almost, we need to call a method available since .NET 1.0):
Function Get-Default([Type] $t) { [Array]::CreateInstance($t, 1)[0] }
And then [DateTimeOffset] $d = Get-Default DateTimeOffset works (there is no way to infer the type in this case, though you are of course free to omit it from the variable).
Of course this does create a garbage array on every invocation; it does not invoke any constructors of the type itself, however. There are more involved approaches that avoid array creation, but they all involve getting complicated with generic methods (relying on LINQ) or explicitly compiling C# and aren't really worth demonstrating as they're less general. Obviously, even the function above should be used only in the unusual case where it might be needed and not as a general way of initializing variables -- typically you know the type and how to initialize it, or you don't care about the type in the first place.