i'm trying to make System.Memory[char] .
[System.Memory[char]]::Memory([char],0,10) * says it can't find System.Memory type .
Also tried *
[System.Memory`3+[[char],0,10]]#()
Solution: The issue seems to be the .NET version used by the Powershell .
It is the static pseudo method ::new(), introduced in PowerShell v5, that provides access to a type's constructors.[1]
# Initialize a [System.Memory[char]] instance with 10 NUL (0x0) chars,
# from a [char[]] array.
[System.Memory[char]]::new(
[char[]]::new(10)
)
Note: The two System.Memory`1 constructors both require a [char[]] array as an argument. The two additional arguments in the 3-parameter overload, start and length, must refer to a range of elements within that array.
The above simply creates a 10-element array to begin with (implicitly using NUL characters), obviating the need for the additional arguments.
If you wanted the input array to use a given character other than NUL, you could use something like , [char] 'x' * 10:
, [char] 'x' create a single-element array with char. 'x', which * 10 then replicates to return a 10-element array. Note that the array will be [object[]]-typed, not [char[]]-typed, but it still works.
Note:
[System.Memory[char]]#() does not work, because in order for PowerShell to translate this cast to a single-parameter constructor call, the operand must be a [char[]] array:
[System.Memory[char]] [char[]] #()
Fundamentally, the System.Memory`1 type is available only in .NET Core 2.1+ / .NET 5+.
The simplest way to check if the type is available in your PowerShell session is if [bool] $IsCoreClr returns $true - in other words: you need to be running PowerShell (Core) 7+, the modern, cross-platform, install-on-demand edition of PowerShell.
[1] In earlier PowerShell versions you need to use the New-Object cmdlet, which uses argument(-parsing) mode, as all cmdlets do. As such, its syntax doesn't map cleanly onto the expression-mode syntax that is familiar from method/constructor calls, especially with respect to passing a single argument that is an array, as in this case:
New-Object System.Memory[char] -ArgumentList (, (New-Object char[] 10))
Note the need to wrap the array constructed by , [char] 0) * 10 in another array, namely a transitory one that is needed to make New-Object treat the original array as a single argument for the target constructor.
Additionally, ::new() performs better, though that will often not matter. See this answer for details.
Related
When I study PowerShell scripting language, I try to use "Write-Output" command to display variable.
I use a different method to create variables.
Example:
$myvariable = 0x5555
Set-Variable -Name myvariable2 -Value 0x5555
The data type of these two variables is Int32.
When I use the command as below,
Write-Output $myvariable $myvariable2
the result is 21845 and 0x5555.
What's different between these two variables?
How can I display format result like printf %d %x?
PetSerAl, as many times before, has given the crucial pointer in a comment (and later helped improve this answer):
Written as of PowerShell Core 6.2.0.
PowerShell parses an unquoted literal argument that looks like a number as a number and wraps it in a "half-transparent" [psobject] instance, whose purpose is to also preserve the exact argument as specified as a string.
By half-transparent I mean that the resulting $myVariable2:
primarily is a number - a regular (unwrapped) [int] instance - for the purpose of calculations; e.g., $myVariable2 + 1 correctly returns 21846
additionally, it shows that it is a number when you ask for its type with .GetType() or via the Get-Member cmdlet; in other words: in this case PowerShell pretends that the wrapper isn't there (see below for a workaround).
situationally behaves like a string - returning the original argument literal exactly as specified - in the context of:
output formatting, both when printing directly to the console and generally with Out-* cmdlets such as Out-File (but not Set-Content) and Format-* cmdlets.
string formatting with -f, PowerShell's format operator (which is based on .NET's String.Format() method; e.g., 'The answer is {0}' -f 42 is equivalent to [string]::Format('The answer is {0}', 42)).
Surprisingly, it does not behave like a string inside an expandable string ("$myVariable2") and when you call the .ToString() method ($myVariable2.ToString()) and (therefore also) with Set-Content.
However, the original string representation can be retrieved with $myVariable2.psobject.ToString()
Note that specifying number literals as command arguments is inherently ambiguous, because even string arguments generally don't need quoting (unless they contain special characters), so that, for instance, an argument such as 1.0 could be interpreted as a version string or as a floating-point number.
PowerShell's approach to resolving the ambiguity is to parse such a token as a number, which, however, situationally acts as a string[1], as shown above.
The ambiguity can be avoided altogether by typing parameters so as to indicate whether an argument bound to it is a string or a number.
However, the -Value parameter of the Set-Variable and New-Variable cmdlets is - of necessity - [object] typed, because it must be able to accept values of any type, and these cmdlets don't have a parameter that would let you indicate the intended data type.
The solution is to force the -Value argument to be treated as the result of an expression rather than as an unquoted literal argument, by enclosing it in (...):
# Due to enclosing in (...), the value that is stored in $myvariable2
# is *not* wrapped in [psobject] and therefore behaves the same as
# $myvariable = 0x55555
Set-Variable -Name myvariable2 -Value (0x5555)
Conversely, if you don't apply the above solution, you have two choices for unwrapping $myvariable2's value on demand:
# OK: $myvariable isn't wrapped in [psobject], so formatting it as a
# hex. number works as expected:
PS> 'hex: 0x{0:x}' -f $myvariable
hex: 0x5555 # OK: Literal '0x' followed by hex. representation of the [int]
# !! Does NOT work as expected, because $myvariable2 is treated as a *string*
# !! That is, {0:x} is effectively treated as just {0}, and the string
# !! representation stored in the [psobject] wrapper is used as-is.
PS> 'hex: 0x{0:x}' -f $myvariable2
hex: 0x0x5555 # !! Note the extra '0x'
# Workaround 1: Use a *cast* (with the same type) to force creation of
# a new, *unwrapped* [int] instance:
PS> 'hex: 0x{0:x}' -f [int] $myvariable2
hex: 0x5555 # OK
# Workaround 2: Access the *wrapped* object via .psobject.BaseObject.
# The result is an [int] that behaves as expected.
PS> 'hex: 0x{0:x}' -f $myvariable2.psobject.BaseObject
hex: 0x5555 # OK
Note: That -f, the format operator, unexpectedly treats a [psobject]-wrapped number as a string is the subject of GitHub issue #17199; sadly, the behavior was declared to be by design.
Detecting a [psobject]-wrapped value:
The simplest solution is to use -is [psobject]:
PS> $myvariable -is [psobject]
False # NO wrapper object
PS> $myvariable2 -is [psobject]
True # !! wrapper object
(PetSerAl offers the following, less obvious alternative: [Type]::GetTypeArray((, $myvariable2)), which bypasses PowerShell's hiding-of-the-wrapper trickery.)
[1] Preserving the input string representation in implicitly typed numbers passed as command arguments:
Unlike traditional shells, PowerShell uses rich types, so that an argument literal such as 01.2 is instantly parsed as a number - a [double] in this case, and if it were used as-is, it would result in a different representation on output, because - once parsed as a number - default output formatting is applied on output (where the number must again be turned into a string):
PS> 01.2
1.2 # !! representation differs (and is culture-sensitive)
However, the intent of the target command may ultimately be to treat the argument as a string and in that case you do not want the output representation to change.
(Note that while you can disambiguate numbers from strings by using quoting (01.2 vs. '01.2'), this is not generally required in command arguments, the same way it isn't required in traditional shells.)
It is for that reason that a [psobject] wrapper is used to capture the original string representation and use it on output.
Note: Arguably, a more consistent approach would have been to always treat unquoted literal arguments as strings, except when bound to explicitly numerically typed parameters in PowerShell commands.
This is a necessity for invoking external programs, to which arguments can only ever be passed as strings.
That is, after initial parsing as a number, PowerShell must use the original string representation when building the command line (Windows) / passing the argument (Unix-like platforms) as part of the invocation of the external program.
If it didn't do that, arguments could inadvertently be changed, as shown above (in the example above, the external program would receive string 1.2 instead of the originally passed 01.2).
You can also demonstrate the behavior using PowerShell code, with an untyped parameter - though note that is generally preferable to explicitly type your parameters:
PS> & { param($foo) $foo.GetType().Name; $foo } -foo 01.2
Double # parsed as number - a [double]
01.2 # !! original string representation, because $foo wasn't typed
$foo is an untyped parameter, which means that the type that PowerShell inferred during initial parsing of literal 01.2 is used.
Yet, given that the command (a script block ({ ... }) in this case) didn't declare a parameter type for $foo, the [psobject] wrapper that is implicitly used shows the original string representation on output.
The first question is already answered by #PetSerAl in the comments. Your second question:
How can I display format result like printf %d %x
Use PowerShell string formatting to obtain the desired results.
I'm trying to get a handle on documentation/evidence around when PowerShell does conversions, and when a user needs to be explicit about what they want. A somewhat related question, here, has a broken link that possibly explained scenarios that the type would be adjusted. There are plenty of instances of similar problems though (namely, comparing a string to an int) - for example when you get a number via Read-Host, you're actually getting a string.
Specifically though I've found that certain mechanisms seem to handle a string representation of the number fine - but I want to understand if that is truly because they're handling it as a number, or if the output is correct in appearance, but wrong under the hood. Here's a specific example though, so the question is about Measure-Object and how it handles calculating the Sum of a string property.
PS > $Example = Import-Csv .\Example.csv
PS > $Example
Procedure_Code : 123456789
CPT_Code : J3490
Modifier :
Service_Date : 02/01/2020
Revenue_Code : 259
Units : -100.00
Amount : 55.00
Procedure_Code : 123456789
CPT_Code : J3490
Modifier :
Service_Date : 02/02/2020
Revenue_Code : 259
Units : 100.00
Amount : 55.00
PS > [Math]::Sign($Example[0].Units)
-1
PS > $Example | Measure-Object Amount -Sum
Count : 2
Sum : 110
Property : Amount
PS > $Example | ForEach-Object { $Sum = $Sum + $_.Amount} ; Write-Host "$($Sum)"
55.0055.00 #Plus sign concatenates strings
PS > $Example | ForEach-Object { $Sum = $Sum + [int]$_.Amount} ; Write-Host "$($Sum)"
110 #Expected result if casting the string into an integer type before adding
So basically it seems like [Math]::Sign() works (even though it doesn't accept strings as far as I can tell), and Measure-Object is smarter about strings than a simple plus sign. But is Measure-Object casting my string into an [int]? If I use an example with decimals, the answer is more precise, so maybe it is a [single]?
What is Measure-Object doing to get the right answer, and when shouldn't I trust it?
Measure-Object is a special case, because it is that cmdlet's specific logic that performs automatic type conversion, as opposed to PowerShell's own, general automatic conversions that happen in the context of using operators such as +, passing arguments to commands, calling .NET methods such as [Math]::Sign(), and using values as conditionals.
As your own answer hints at, Measure-Object calls the same code that PowerShell's general automatic type conversions use; these are discussed below.
Automatic type conversions in PowerShell:
In general, PowerShell always attempts automatic type conversion - and a pitfall to those familiar with C# is that even an explicit cast (e.g. [double] '1.2') may not be honored if the target type is ultimately a different one.[1]
Supported conversions:
In addition to supporting .NET's type conversions, PowerShell implements a few built-in custom conversions and supports user-defined conversions by way of custom conversion classes and attribute classes; also, it implicitly tries to call single-parameter target-type constructors and - for string input - a target type's static ::Parse() method, if present; see this answer for details.
To-number conversion and culture-sensitivity when converting to and from strings:
Typically, PowerShell uses culture-invariant conversion, so that, for instance, only . is recognized as the decimal mark in "number strings" that represent floating-point numbers.
String-to-number conversion, including how specific numeric data types are chosen, is covered in this answer.
To-string conversion (including from numbers) is covered in this answer.
Automatic type conversions by context:
Operands (operator arguments):
Some operators, such as -replace and -match, operate on strings only, in which case the operand(s) are invariably converted to strings (which always succeeds):
42 -replace 2, '!' yields '4!': both 42 and 2 were implicitly converted to strings.
Others, such as - and \, operate on numbers only, in which case the operand(s) are invariably converted to numbers (which may fail):
'10' - '2' yields 8, an [int]
Yet others, such as + and *, can operate on either strings or numbers, and it is the type of the LHS that determines the operation type:
'10' + 2 yields '102', the concatenation of string '10' with the to-string conversion of number 2.
By contrast, 10 + '2' yields 12, the addition of the number 10 and the to-number conversion of string '2'.
See this answer for more information.
Command arguments:
If the target parameter has a specific type (other than [object] or [psobject]), PowerShell will attempt conversion.
Otherwise, for literal arguments, the type is inferred from the literal representation; e.g. 42 is passed as an [int], and '42' as a [string].
Values serving as conditionals, such as in if statements:
A conditional must by definition be a Boolean (type [bool]), and PowerShell automatically converts a value of any type to [bool], using the rules described in the bottom section of this answer.
Method arguments:
Automatic type conversion for .NET-method arguments can be tricky, because multiple overloads may have to be considered, and - if the argument's aren't of the expected type - PowerShell has to find the best overload based on supported type conversions - and it may not be obvious which one is chosen.
Executing [Math]::Sign (calling this as-is, without ()) reveals 8(!) different overloads, for various numeric types.
More insidiously, the introduction of additional .NET-method overloads in future .NET versions can break existing PowerShell code, if a new overload then happens to be the best match in a given invocation.
A high-profile example is the [string] type's Split() method - see the bottom section of this answer.
Therefore, for long-term code stability:
Avoid .NET methods in favor of PowerShell-native commands, if possible.
Otherwise, if type conversion is necessary, use casts (e.g. [Math]::Sign([int] '-42')) to guide method-overload resolution to avoid ambiguity.
[1] E.g., the explicit [double] is quietly converted to an [int] in the following statement: & { param([int] $i) $i } ([double] '1.2'). Also, casts to .NET interfaces generally have no effect in PowerShell - except to guide overload resolution in .NET method calls.
I'm not sure this is the be-all and end-all answer, but it works for what I was curious about. For a lot of 'quick script' scenarios where you might use something like Measure-Object - you'll probably get the correct answers you're looking for, albeit maybe slower than other methods.
Measure-Object specifically seems to use [double] for Sum, Average, and StandardDeviation and will indeed throw an error if one of the string values from a CSV Object can't be converted.
I'm still a little surprised that [Math]::Sign() works at all with strings, but seemingly glad it does
if (_measureAverage || _measureSum || _measureStandardDeviation)
{
double numValue = 0.0;
if (!LanguagePrimitives.TryConvertTo(objValue, out numValue))
{
_nonNumericError = true;
ErrorRecord errorRecord = new(
PSTraceSource.NewInvalidOperationException(MeasureObjectStrings.NonNumericInputObject, objValue),
"NonNumericInputObject",
ErrorCategory.InvalidType,
objValue);
WriteError(errorRecord);
return;
}
AnalyzeNumber(numValue, stat);
}
Consider the following hashtable:
$table = #{
6 = '10.11.12.13', '10.11.12.14'
15 = 'domain.tld'
NameServers = '10.11.12.13', '10.11.12.14'
}
Normally when you have a hashtable, you can . reference the key name to work with it in a more object-oriented style vs. using the array-accessor syntax [key]. For example, invoking the NameServers key on the hashtable above with a . results in the value being returned, and I can work with members of that property's value as expected:
$table.NameServers # ========> 10.11.12.13
# ========> 10.11.12.14
$table.NameServers.Count # ========> 2
But if I try accessing the 6 key with a . which contains the same string content, it references the correct value fine, but I cannot invoke any members on the returned object. I must use the traditional array-accessor here:
$table.6 # =====> 10.11.12.13
# =====> 10.11.12.14
$table.6.Count # =====> ParserError:
# =====> Line |
# =====> 1 | $hi.6.Count
# =====> | ~
# =====> | Missing property name after reference operator.
$table[6].Count # =====> 2
Of course, ( $table.6 ).Count works around this issue but this is still a weird quirk of the syntax which I can't explain. Interestingly enough, converting the hashtable to a PSCustomObject yields the same issue. Is this perhaps a parser bug? Or is there something else going on here?
I tested this in Windows PowerShell and PowerShell 7.1 and it happens in both.
Note: This answer originally incorrectly claimed that $table.6 doesn't work, due to the hashtable key being integer-typed, but it works just fine, because the 6 in the .6 "property" access is parsed as an integer as well.
As Gyula Kokas' helpful answer points out, you're seeing problematic behavior in PowerShell's parser, present as of PowerShell 7.2, discussed in detail in GitHub issue #14036. The behavior is unrelated to hashtables per se:
What follows ., the member-access operator, is parsed as a number if it looks like a number literal, such as 6, 6l (a [long]) or even 6.0(!, a [double]).
See below for how that number is then used.
Any attempt to use another property access then triggers the error you saw:
$foo.6.bar # !! Error "Missing property name after reference operator."
In fact, following a property name that starts with a digit with a letter causes the error too - even though 6a can not interpreted as a number literal.
$foo.6a # !! Same error.
As an aside: PowerShell even allows you to use variable references, and even expressions (enclosed in (...)) as property names (e.g., $propName = 'Length'; 'foo'.$propName or ('foo'.('L' + 'ength')
Workarounds:
As you've demonstrated, enclosing the first property access in (...) - ($table.6).Count works, and so does $table.(6).Count
You've also demonstrated $table[6].Count, which works for hashtables.
For accessing an object's actual property, $obj.'6'.Count would work too (given that property names are always strings), as it would with hashtables with string keys (e.g. #{ '6'= 'six' }.'6'.Length)
Considerations specific to hashtables:
As a syntactic convenience, PowerShell allows property-access syntax ($obj.someProperty) to also be used to access the entries of a hashtable, in which case the property "name" is taken as the key of an entry in the hashtable.
The type-native syntax to access hashtable entries is index syntax ($hash[$someKey]).
While property names (referring to members of a .NET type) are invariably strings:
a hashtable's keys can be of any type.
on accessing an entry, the lookup key must not only have the right value, but must also be of the exact same type as the key stored in the hashtable.
Perhaps surprisingly, when defining a hashtable, unquoted keys situationally become either strings or numbers:
If the unquoted word can be interpreted as a number literal (e.g., 6), it is parsed as such, and you end up with a numeric key; in the case of 6, it will be [int]-typed key, because the usual number-literal typing applies (e.g., 1.0 would become a [double] key (not advisable, because binary floating-point values do not always have exact decimal representations), and 2147483648 would become a [long]).
Otherwise (e.g., NameServers), the key is [string]-typed.
Caveat: If the name starts with a digit (but isn't a number literal; e.g. 6a), an error occurs; use quoting ('6a') as a workaround - see GitHub issue #15925
That is, even though strings in expressions normally require quoting, for convenience you may omit the quoting when defining keys in hashtable literals - but the rules for recognizing number literals still apply.
Explicitly typing hashtable keys:
To ensure that a given key is interpreted as a [string], quote it (e.g., '6')
Generally, you may also use a cast to type your keys explicitly. (e.g. [long] 6) or, in number literals, the usual number-type suffix characters, such as L for [long] (e.g. 6L) - see this answer for an overview of the supported suffixes, whose number has grown significantly in PowerShell (Core) 7+.
An example, based on your hashtable:
It follows from the above that your 6 key is of type [int] (and would have to be defined as '6' in your hashtable literal if you wanted it to be a string).
Because the 6 in $name.6 is also parsed as an [int], the lookup succeeds, but note that it wouldn't succeed if different numeric types were at play:
# !! Output is $null, because the entry key is of type [long],
# !! whereas the lookup key is [int].
#{ 6L = '[long] 6' }.6
Considerations specific to property access:
With actual property access (accessing a native member of a .NET type), the fact that names that look like numbers are actually parsed as numbers first - before, of necessity, being converted to strings - can result in surprising behavior:
# !! Output is $null, because the 6L after "." is parsed as a
# !! ([long]) number first, and then converted to a string, which
# !! results in "6" being used.
([pscustomobject] #{ '6L' = 'foo' }).6L
# OK - The quoting forces 6L to be a string.
([pscustomobject] #{ '6L' = 'foo' }).'6L' # -> 'foo'
I think here is the official issue for your question:
https://github.com/PowerShell/PowerShell/issues/14036
I think the best workaround is $table.(6).count
Hashtables in powershell expect key names to be strings. When you type $table.6 based on your original code the 6 is interpreted as an integer when it's expecting it to be a string (a type conversion feature). The simplest way to resolve this would be to replace 6 with 'six', however you can make this work in a round about way.
If you make the alterations to the code like so:
$table = #{
'6' = '10.11.12.13', '10.11.12.14'
'15' = 'domain.tld'
NameServers = '10.11.12.13', '10.11.12.14'
}
You can reference it by implicitly instructing powershell to treat the 6 as a string by doing the following:
$table.'6'.count
2
It's the same principal as adding keynames with special characters, they have to be instantiated as strings to prevent interpolation or type conversion. For example:
$table = #{'bla $something' = 'anything'}
$table.'bla $something'
anything
In your case I'd do the following and move on to the next problem:
$table = #{
Six = '10.11.12.13', '10.11.12.14'
Fifteen = 'domain.tld'
NameServers = '10.11.12.13', '10.11.12.14'
}
$table.Six.count
2
For further investigation into type conversion follow this link: Powershell Type Conversion Reference
When I study PowerShell scripting language, I try to use "Write-Output" command to display variable.
I use a different method to create variables.
Example:
$myvariable = 0x5555
Set-Variable -Name myvariable2 -Value 0x5555
The data type of these two variables is Int32.
When I use the command as below,
Write-Output $myvariable $myvariable2
the result is 21845 and 0x5555.
What's different between these two variables?
How can I display format result like printf %d %x?
PetSerAl, as many times before, has given the crucial pointer in a comment (and later helped improve this answer):
Written as of PowerShell Core 6.2.0.
PowerShell parses an unquoted literal argument that looks like a number as a number and wraps it in a "half-transparent" [psobject] instance, whose purpose is to also preserve the exact argument as specified as a string.
By half-transparent I mean that the resulting $myVariable2:
primarily is a number - a regular (unwrapped) [int] instance - for the purpose of calculations; e.g., $myVariable2 + 1 correctly returns 21846
additionally, it shows that it is a number when you ask for its type with .GetType() or via the Get-Member cmdlet; in other words: in this case PowerShell pretends that the wrapper isn't there (see below for a workaround).
situationally behaves like a string - returning the original argument literal exactly as specified - in the context of:
output formatting, both when printing directly to the console and generally with Out-* cmdlets such as Out-File (but not Set-Content) and Format-* cmdlets.
string formatting with -f, PowerShell's format operator (which is based on .NET's String.Format() method; e.g., 'The answer is {0}' -f 42 is equivalent to [string]::Format('The answer is {0}', 42)).
Surprisingly, it does not behave like a string inside an expandable string ("$myVariable2") and when you call the .ToString() method ($myVariable2.ToString()) and (therefore also) with Set-Content.
However, the original string representation can be retrieved with $myVariable2.psobject.ToString()
Note that specifying number literals as command arguments is inherently ambiguous, because even string arguments generally don't need quoting (unless they contain special characters), so that, for instance, an argument such as 1.0 could be interpreted as a version string or as a floating-point number.
PowerShell's approach to resolving the ambiguity is to parse such a token as a number, which, however, situationally acts as a string[1], as shown above.
The ambiguity can be avoided altogether by typing parameters so as to indicate whether an argument bound to it is a string or a number.
However, the -Value parameter of the Set-Variable and New-Variable cmdlets is - of necessity - [object] typed, because it must be able to accept values of any type, and these cmdlets don't have a parameter that would let you indicate the intended data type.
The solution is to force the -Value argument to be treated as the result of an expression rather than as an unquoted literal argument, by enclosing it in (...):
# Due to enclosing in (...), the value that is stored in $myvariable2
# is *not* wrapped in [psobject] and therefore behaves the same as
# $myvariable = 0x55555
Set-Variable -Name myvariable2 -Value (0x5555)
Conversely, if you don't apply the above solution, you have two choices for unwrapping $myvariable2's value on demand:
# OK: $myvariable isn't wrapped in [psobject], so formatting it as a
# hex. number works as expected:
PS> 'hex: 0x{0:x}' -f $myvariable
hex: 0x5555 # OK: Literal '0x' followed by hex. representation of the [int]
# !! Does NOT work as expected, because $myvariable2 is treated as a *string*
# !! That is, {0:x} is effectively treated as just {0}, and the string
# !! representation stored in the [psobject] wrapper is used as-is.
PS> 'hex: 0x{0:x}' -f $myvariable2
hex: 0x0x5555 # !! Note the extra '0x'
# Workaround 1: Use a *cast* (with the same type) to force creation of
# a new, *unwrapped* [int] instance:
PS> 'hex: 0x{0:x}' -f [int] $myvariable2
hex: 0x5555 # OK
# Workaround 2: Access the *wrapped* object via .psobject.BaseObject.
# The result is an [int] that behaves as expected.
PS> 'hex: 0x{0:x}' -f $myvariable2.psobject.BaseObject
hex: 0x5555 # OK
Note: That -f, the format operator, unexpectedly treats a [psobject]-wrapped number as a string is the subject of GitHub issue #17199; sadly, the behavior was declared to be by design.
Detecting a [psobject]-wrapped value:
The simplest solution is to use -is [psobject]:
PS> $myvariable -is [psobject]
False # NO wrapper object
PS> $myvariable2 -is [psobject]
True # !! wrapper object
(PetSerAl offers the following, less obvious alternative: [Type]::GetTypeArray((, $myvariable2)), which bypasses PowerShell's hiding-of-the-wrapper trickery.)
[1] Preserving the input string representation in implicitly typed numbers passed as command arguments:
Unlike traditional shells, PowerShell uses rich types, so that an argument literal such as 01.2 is instantly parsed as a number - a [double] in this case, and if it were used as-is, it would result in a different representation on output, because - once parsed as a number - default output formatting is applied on output (where the number must again be turned into a string):
PS> 01.2
1.2 # !! representation differs (and is culture-sensitive)
However, the intent of the target command may ultimately be to treat the argument as a string and in that case you do not want the output representation to change.
(Note that while you can disambiguate numbers from strings by using quoting (01.2 vs. '01.2'), this is not generally required in command arguments, the same way it isn't required in traditional shells.)
It is for that reason that a [psobject] wrapper is used to capture the original string representation and use it on output.
Note: Arguably, a more consistent approach would have been to always treat unquoted literal arguments as strings, except when bound to explicitly numerically typed parameters in PowerShell commands.
This is a necessity for invoking external programs, to which arguments can only ever be passed as strings.
That is, after initial parsing as a number, PowerShell must use the original string representation when building the command line (Windows) / passing the argument (Unix-like platforms) as part of the invocation of the external program.
If it didn't do that, arguments could inadvertently be changed, as shown above (in the example above, the external program would receive string 1.2 instead of the originally passed 01.2).
You can also demonstrate the behavior using PowerShell code, with an untyped parameter - though note that is generally preferable to explicitly type your parameters:
PS> & { param($foo) $foo.GetType().Name; $foo } -foo 01.2
Double # parsed as number - a [double]
01.2 # !! original string representation, because $foo wasn't typed
$foo is an untyped parameter, which means that the type that PowerShell inferred during initial parsing of literal 01.2 is used.
Yet, given that the command (a script block ({ ... }) in this case) didn't declare a parameter type for $foo, the [psobject] wrapper that is implicitly used shows the original string representation on output.
The first question is already answered by #PetSerAl in the comments. Your second question:
How can I display format result like printf %d %x
Use PowerShell string formatting to obtain the desired results.
I'm finding it very hard to google the answer to what the difference is between these two way of executing method calls in powershell:
$member = "1.2.3.4:567" # IPaddress + port for demonstration
$vals1 = $member.Split(":") # typical .NET way of executing String.Split
$vals2 = $member.Split( (,":") ) # something else which ive seen in examples which I dont understand
In the above, both $vals1 and $vals2 appear to have the same result (an array with 2 elements). I would typically use the first way, but in examples (of using both Split and other method calls) I see the second used.
My question is what is the second one doing which is different, and is there any advantages to using it which I may not understand?
Edit: Please don't focus on the Split method - I'm not asking about overloads of Split!
The comma operator used as a unary is what you are seeing. It is a shorthand way to create an array. PowerShell will unroll array in pipelines which is usually desired and standard behavior. Where I see this commonly used is to mitigate that feature of PowerShell
What you would then do in some cases though you do not want PowerShell to unroll the complete array is through the comma unary operator in front of that array. Consider the difference in outputs
Using regular array notation
$array = 1,2,3
$array.Count
$array | ForEach-Object{$_.GetType().FullName}
3
System.Int32
System.Int32
System.Int32
Using the unary operator to create a jagged array
$commaArray = ,#(1,2,3)
$commaArray.Count
$commaArray | ForEach-Object{$_.GetType().FullName}
1
System.Object[]
In the second example the array gets passed as a whole. PowerShell still unrolled it from a single array with one element that was itself an array to that single array 1,2,3.
There are other cases for its use as well. I would more commonly see regular arrays declared statically with 1,2,3 or sometimes the #() is needed depending. Result is the same for both of those.
,";" is a trick/shorthand to create an array (try (,";").GetType()). Why would you need this? Well, let's try calling Split with a list of values directly:
"abc".Split('a','b')
Cannot convert argument "count", with value: "b", for "Split" to type
"System.Int32": "Cannot convert value "b" to type "System.Int32".
Error: "Input string was not in a correct format.""
Doesn't work because the parameters are passed separately, rather than as a char[]. So could we use the comma trick to fix this?
"abc".Split((,'a','b'))
Cannot convert argument "separator", with value: "System.Object[]",
for "Split" to type "System.Char[]": "Cannot convert the
"System.Object[]" value of type "System.Object[]" to type
"System.Char"."
No, because we still have a type mismatch. That's because this approach is too clever for its own good. A much more readable way to create an array is the #() operator:
"abc".Split(#('a', 'b'))
And this calls the desired overload of Split.
The Split method has multiple overloads. The second example will create an array of string which will be convertet to an char[] because its only one character in the double quotes. However if you want to split by two characters, the second example won't work thus I wouldn't use it.
However, the PowerShell way to split is using -split:
$vals1 = $member -split ':'