Use PowerShell to divide two decimals - powershell

This seems easy, but after searching, I have come up empty-handed.
How can I divide two decimals in powershell?
204.50 / 1,917.75
Throws the following error:
Method invocation failed because [System.Object[]] does not contain a method named 'op_Division'.
At line:1 char:1
+ 204.50 / 1,917.75
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (op_Division:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound

Try leaving the comma out. What you've typed looks like 204.50 divided by 1 and then 917.75

Walter Mitty's answer is an effective solution.
To explain why 204.50 / 1,917.75 failed:
PowerShell number literals (a) always use . as the decimal mark, irrespective of the current culture, and (b) do not support use of a thousands separator (such as ,).
This implies that 1,917.75 is not recognized as a number literal, which begs the question (kinda): how is it parsed?
, is the array-construction operator in PowerShell: that is, tokens separated by , constitute the elements of an array.
Thus, 1,917.75, is the equivalent of #( 1, 917.75 ): a 2-element array containing [int] 1 and [double] 917.75.
, has higher precedence than /, the division operator, so that 204.50 / 1,917.75 is the equivalent of:
204.50 / #( 1, 917.75 )
That is, PowerShell tries to divide [double] literal 204.50 by array #( 1, 917.75 )
Since PowerShell doesn't know how to divide anything by an array (generically represented in PowerShell as [System.Object[]]), you get the following error message:
Method invocation failed because [System.Object[]] does not contain a method named 'op_Division'.
That is, PowerShell looks for a way to apply division operator / (op_Division) to array operands, and since that isn't defined, an error occurs.
As an aside: PowerShell does overload some operators to work with arrays, but only if the array is on the LHS (the left-hand side operand).
Operators -eq / ne, -like / -notlike, -match / -notmatch (do let me know if I'm missing any) accept an array as the LHS and a scalar as the RHS, in which case the operator acts as a filter:
The operator is applied individually to the elements of the LHS (against the scalar on the RHS), and the subset of elements for which the operation returns $true is returned as a subarray of the input array; e.g.:
#( 'donald trump', 'hillary clinton', 'gary johnson' ) -notmatch 'trump'
outputs #( 'hillary clinton', 'gary johnson' ), the subarray of the input containing only those elements that do not contain the substring trump.

Related

Array not recognized by powershell parser when other operators are involved

Assigning an array looks like this:
PS> $x = "a", "b"
PS> $x
a
b
Now, i wanted to add a 'root string' ("r") to any element so I did this (actually i used a variable, but for the sakeness of simplicity let's just use a string here):
PS> $x = "r" + "a" , "r" + "b"
PS> $x
ra rb
Looking at the output, I didn't get the array that I expected, but a single string with a "space" (I checked: it's a 32 ascii char, so a space, not a tab or another character).
That is: the comma seems to be interpreted as a string join operator, which I couldn't find any reference to.
Even worst, I get the feeling of not understanding how the parser works here. I had a look at about_Parsing; what I found seems not to apply to this case.
Commas (,) introduce lists passed as arrays, except when the command
to be called is a native application, in which case they are
interpreted as part of the expandable string. Initial, consecutive or
trailing commas are not supported.
The first obvious fix that I came up with is the following:
PS> $x = ("r" + "a") , ("r" + "b")
PS> $x
ra
rb
Maybe there are others, and I am expecially intrested in the ones that reveal how the parser actually works. What I would like to fix the most is my knowledge of the parsing rules.
To flesh out the helpful comments on the answer:
tl;dr
Due to operator precedence, your command is parsed as "r" + ("a" , "r") + "b", causing array "a", "r" to be implicitly stringified to verbatim a r, resulting in two string concatenation operations yielding a single string with verbatim content ra rb.
Using (...) is indeed the correct way to override operator precedence.
"r" + "a" , "r" + "b"
is an expression involving operators.
Expressions are parsed in expression mode, which contrasts with argument mode; the latter applies to commands, i.e. named units of functionality that are called with shell-typical syntax (whitespace-separated arguments, quotes around simple strings optional). Arguments (parameter values) in argument mode are parsed differently from operands in expression mode, as explained in the conceptual about_Parsing help topic. Your quote about , relates to argument mode, not expression mode.
The conceptual about_Operator_Precedence help topic describes the relative precedence among operators, from which you can glean that ,, the array constructor operator has higher precedence than the + operator
Therefore, your expression is parsed as follows (using (...), the grouping operator, to make the implicit rules explicit):
"r" + ("a" , "r") + "b"
+ is polymorphic in PowerShell, and with a [string] instance as the LHS the RHS is coerced to a string too.
Therefore, array "a" , "r" is stringified, which uses PowerShell's custom array stringification, namely joining the (potentially stringified) array elements with a space.[1]
That is, the array stringifies to a string with verbatim content a r.
As an aside: The same stringification is applied in the context of string interpolation via expandable (double-quoted) strings ("..."); that is, "$("a", "r")" also yields verbatim a r
Therefore, the above is equivalent to:
"r" + "a r" + "b"
which yields verbatim ra rb.
(...) is indeed the appropriate way to ensure the desired precedence:
("r" + "a"), ("r" + "b") # -> array 'ra', 'rb'
[1] Space is the default separator character. Technically, you can override it via the $OFS preference variable, though that is rarely used in practice.
Another way to do it. The type of the first term controls what type of operation the plus performs. The first term here is an empty array. If you want the plus to do both kinds of operations, there's no getting around extra parentheses to change the operator precedence.
#() + 'ra' + 'rb'
ra
rb
Or more commonly:
'ra','rb' + 'rc'
ra
rb
rc

Who has seen this potential powershell object property treated as method call booby trap?

The following is not an actual question but a cautionary tale about some unexpected PowerShell syntax. The only real question is "Is this behaviour well known to many or only by a few PowerShell developers (i.e. those working ON PowerShell not just WITH PowerShell)?" Note: the examples are only to demonstrate the effect and do not represent meaningful code (no need to ask what the purpose is).
While playing with a PowerShell (5.1.18362.145) switch statement, I received the following error,
PS > $xx = gi somefile
PS > switch ($xx.directory) {
>> $xx.directory{6}
>> }
At line:2 char:17
+ $xx.directory{6}
+ ~
Missing statement block in switch statement clause.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingSwitchStatementClause
Given previous research on switch, I expected both the $xx.directory expressions to be evaluated and converted to (matching) strings. Clearly {6} would be expected to be the statement clause. Maybe there is some weird parsing happening. Try separating the expression from the statement,
PS > switch ($xx.directory) {
$xx.directory {6}
}
6
PS >
OK, so what happens if we try both,
PS > switch ($xx.directory) {
>> $xx.directory{5} {6}
>> }
Method invocation failed because [System.IO.FileInfo] does not contain a method named 'directory'.
At line:2 char:1
+ $xx.directory{5} {6}
+ ~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
What the??? I know braces kinda look like parentheses but what is happening here? Let's try it with an actual method,
PS > 'fred'.substring{1}
Cannot find an overload for "substring" and the argument count: "1".
At line:1 char:1
+ 'fred'.substring{1}
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodException
+ FullyQualifiedErrorId : MethodCountCouldNotFindBest
but String.Substring does have an overload with one argument, though it is supposed to be an int. What is this trying to pass? (Hint: what does it look like?) Let's find out,
PS > Add-Type #'
>> public class huh {
>> public void passing(object o)
>> {
>> System.Console.WriteLine(o.GetType().ToString());
>> }
>> }
>> '#
PS > $whatsit=New-Object huh
PS > $whatsit.passing{1}
System.Management.Automation.ScriptBlock
PS >
Who'da thunk it?
About the only other question would be, "Anybody know where this is described in the documentation (assuming it is still happening in 7.2+)?" (Seriously, I'd like to know if it is.)
As - perhaps unfortunate - syntactic sugar, PowerShell allows you to shorten:
$object.Method({ ... })
to:
$object.Method{ ... }
Note:
In both cases there mustn't be a space after the method name (whereas C# allows "foo".Substring (1), for instance).
Method in the above example merely has to be syntactically valid as a method name in order for both expressions to be treated as method calls - a method call is attempted even if no such method exists or if the name happens to refer to a property instead.
In other words:
Methods that accept exactly one (non-optional) argument of type script block ([scriptblock]; { ... }) allow invocation without parentheses ((...)).
Arguably, such a narrow use case wouldn't have called for syntactic sugar:
Limiting support to script blocks limits the syntactic sugar to PowerShell-provided/-targeted types and their methods, given that script blocks are a PowerShell-specific feature.
The requirement not to separate the name and the opening { with a space is at odds with how script blocks are customarily passed to cmdlets (e.g. 1, 2, 3 | ForEach-Object { $_ + 1 } vs. (1, 2, 3).ForEach{ $_ + 1 } - see below)
Having to switch back to (...) as soon as two or more arguments must be passed is awkward.
Presumably, this was introduced to cut down on the "syntactic noise" of one common scenario: the use of the PSv4+ .ForEach() and .Where() array methods, introduced for the DSC (Desired State Configuration) feature, which are typically invoked with only a script block; e.g.:
(1, 2, 3).ForEach({ $_ + 1 }) can be simplified to (1, 2, 3).ForEach{ $_ + 1 }
As for documentation:
The behavior is described only in the context of the aforementioned .ForEach() and .Where() methods in the context of the conceptual about_Arrays help topic:
The syntax requires the usage of a script block. Parentheses are optional if the scriptblock is the only parameter. Also, there must not be a space between the method and the opening parenthesis or brace.
Given that it applies to any method with the appropriate signature (irrespective of the .NET type and irrespective of whether it is an instance or a static method), it should arguably (also) be documented in the conceptual about_Methods help topic, which isn't the case as of this writing.
Even with coverage in about_Methods, however, the challenge is to even infer that a method call is being attempted when you don't expect that - at least in the switch case the error message doesn't help.
Design musings:
Note that the .ForEach() and .Where() methods are generally a somewhat awkward fit, given that most native PowerShell features do not rely on methods, and that even regular method-invocation syntax can cause confusion with the shell-like syntax used to invoke all other commands (cmdlets, functions, scripts, external programs) - e.g., $foo.Get('foo', bar') vs. Get-Foo foo bar.
This rejected RFC on GitHub proposed introducing -foreach and -where operators, which would have allowed the following, more PowerShell-idiomatic syntax:
1, 2, 3 -foreach { $_ + 1 }
1, 2, 3, 2 -where { $_ -eq 2 }, 'First'

Comma Before Parentheses in Variable Assignment

I'm using the S.DS.P PowerShell module in a PowerShell script of mine. In it, I have to create the following object:
$ADUserEntry = #{"distinguishedName"=$null;"objectClass"=$null;"sAMAccountName"=$null;"unicodePwd"=$null;"userAccountControl"=0};
In the documentation of the module, it's stated that I have to do the following assignment to the unicodePwd field of a variable created using this object:
$obj.unicodePwd = ,([System.Text.Encoding]::Unicode.GetBytes("$randomPassword") -as [byte[]]);
Notice how there's a comma before the first parentheses. What is that comma doing there?
As Lee_Dailey has pointed out, what you're seeing is the unary form of the (unfortunately named) "comma operator", i.e., PowerShell's array-construction operator.
The unary form creates a single-element array that wraps its (one and only) operand; the array's type is [object[]], as usual in PowerShell:
$arr = , 'foo' # wrap string 'foo' in a single-element array
$arr.GetType().Name # the array's type -> 'Object[]'
$arr[0].GetType().Name # the type of the array's one and only element -> 'String'
Note that while you can even wrap arrays that way, PowerShell's operator-precedence rules require a literal array operand to be enclosed in (...):
# OK - wraps array 1, 2 in a single-element array.
$arr = , (1, 2)
# !! DOES SOMETHING DIFFERENT:
# Creates a 2-element array whose 1st element is integer 1 wrapped in a
# single-element array
$arr = , 1, 2
The binary form constructs an array from the operands, as expected:
$arr = 1, 2, 3 # 3-element array whose elements are integers 1 and 2 and 3
As an aside, re the specific command shown:
,([System.Text.Encoding]::Unicode.GetBytes("$randomPassword") -as [byte[]])
Neither , nor -as [byte[]] are needed in this scenario, because
[System.Text.Encoding]::Unicode.GetBytes() directly returns a [byte[]] array.

PowerShell consumes GB of RAM when returning large byte array?

I'm having a bit of trouble understanding exactly what is going and why it is happening. I have a small function grabbing all the bytes of a large 133MB PNG image file, storing it in a byte array, and returning it. There is either some behavior I don't understand, or perhaps a bug in PowerShell?
$TestFile = 'C:\test.png'
function GetByteArray($InputFile) {
$ByteArray = [IO.File]::ReadAllBytes($InputFile)
Write-Host ( 'Type: ' + $ByteArray.GetType() )
Write-Host ( 'Size: ' + $ByteArray.Length )
return $ByteArray
}
$NewArray = GetByteArray -InputFile $TestFile
Write-Host ( 'Type: ' + $NewArray.GetType() )
Write-Host ( 'Size: ' + $NewArray.Length )
pause
I am expecting the function to return a [Byte[]] about 133MB in size, but it does not. Instead PowerShell consumes about 5GB RAM, prints out the error message below, and returns a [System.Object[]].
Type: byte[]
Size: 140151164
Array dimensions exceeded supported range.
At F:\test.ps1:10 char:10
+ return $ByteArray
+ ~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], OutOfMemoryException
+ FullyQualifiedErrorId : System.OutOfMemoryException
Type: System.Object[]
Size: 134217728
Press Enter to continue...:
What am I not understanding? Why is the byte array being converted to an object? Why is eating almost all my RAM?
PetSerAl is correct (as always), but perhaps a little more explanation is in order. When returning an array from a function PowerShell unrolls that array and returns the individual elements to the caller. There they are gathered into a regular array (System.Object[]).
To prevent this you need to wrap the array result in another array when returning it. PowerShell will only unroll the outer array and pass the nested array to the caller as a single element, thus preserving the type. Think of it as applying a kind of "transport encoding". Use the unary array construction operator (,) to do that:
return ,$ByteArray

Behaviour of PowerShell when Combining HashTables

Question
Why is $null + #{} valid, but #{} + $null not; even where null is cast to a hashtable (#{} + ([hashtable]$null)).
Example Code
[hashtable]$a = #{demo1=1;demo2='two'}
[hashtable]$b = #{demo3=3;demo4='Ivy'}
[hashtable]$c = $null
#combining 2 hashtables creates 1 with both hashes properties (would error if any properties were common to both)
write-verbose 'a + b' -Verbose
($a + $b)
#combining a null hashtable with a non-null hashtable works
write-verbose 'c + a' -Verbose
($c + $a)
#combing 2 null hashtables is fine; even if we've not explicitly cast null as a hashtable
write-verbose 'c + null' -Verbose
($c + $null)
#however, combinging a hashtable with null (i.e. same as second test, only putting null as the right argument instead of the left, produces an error
write-verbose 'a + c' -Verbose
($a + $c)
Output
Name Value
---- -----
demo3 3
demo4 Ivy
demo1 1
demo2 two
VERBOSE: c + a
demo1 1
demo2 two
VERBOSE: c + d
VERBOSE: a + c
A hash table can only be added to another hash table.
At line:19 char:1
+ ($a + $c)
+ ~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : AddHashTableToNonHashTable
Side Note
Incidentally, this led me to discover this useful trick for a null-coalescing operation for hashtables: ($c + #{}). e.g. ($a + ($c + #{})) avoids the error produced above / (($a + #{}) + ($c + #{})) gives us a completely safe way to add hashtables where either value may be null.
I tried to find out exactly why, but I can't for sure.
My original beliefs were:
There may be some PowerShell implicit type coercion happening, where the thing on the right is cast to match the type on the left. e.g. with "1"+1 the 1 on the right becomes a string and the output is "11", but 1+"1" the right becomes a number and the output is 2.
This definitely is not happening, or $null + #{} would either throw a cast error, or cast and do null + null = null, not output an empty hashtable as it does.
Addition of A + B for things other than numbers has some basic rules for things like array concatenation, but beyond that, it will fall down to the .Net Framework underneath and will try to do a method call like A.op_Addition(B) and it will be up to the thing on the left to say what happens when you try to add the thing on the right to it.
This does happen, but is not happening here. Consider #{} + 0 tells you that a hashtable can only be added to a hashtable. (Lies, you can add it to null). And 0 + #{} tells you that hashtables do not contain an op_Addition method. So the addition with null seems like it should give that error, but doesn't, because it's not falling back to this mechanism.
The PSv3 language spec (which I think is the latest version available), download here: https://www.microsoft.com/en-us/download/details.aspx?id=36389, mentions addition:
7.7.1 Addition Description: The result of the addition operator + is the sum of the values designated by the two operands after the usual
arithmetic conversions (§6.15) have been applied.
The usual arithmetic conversions say:
If neither operand designates a value having numeric type, then [..] all operands designating the value $null are converted to zero of type int and the process continues with the numeric conversions listed below.
That implies $null gets converted to 0 in both your examples. Although that cannot be happening because 0 + #{} and #{} + 0 are both errors.
There is an explicit mention of adding of two hashtables in section 7.7:
7.7.4 Hashtable concatenation Description: When both operands designate Hashtables the binary + operator creates a new Hashtable
that contains the elements designated by the left operand followed
immediately by the elements designated by the right operand.
OK, hashtable addition is handled by the PowerShell engine, but only adding two hashtables.
Section 7. introduction says:
Windows PowerShell: If an operation is not defined by PowerShell, the type of the value designated by the left operand is inspected to see if it has a corresponding op_ method.
And the operation $null + Hashtable doesn't seem to be defined by PowerShell from what I can see in the spec, and the corresponding method for + is op_Addition - a method which hashtables do not have, see error code earlier - and this is not throwing that error, and not only that but in the case of adding to 0 the error comes from the Right operand not the left one.
And the other interesting bit in the spec is:
4.1.2 The characteristics of the null type are unspecified.
So the summary appears to be:
#{} + $null - is triggering the PowerShell handling of adding two hashtables, even though the spec doesn't say that it will.
$null + #{} - it looks like there's an implicit $null + x = x rule, although the spec doesn't seem to mention it, and it might be implementation dependent.
[<type>]$null - casting $null to a numeric type results in 0 (6.15 arithmetic conversions), but casting it to anything else(?) appears to result in $null (not in the spec).
The comment and linked chain which says $null has no type are against the PowerShell spec 4.1.2 "the null type" which says "The null type has one instance, the automatic variable $null (§2.3.2.2), also known as the null value. The characteristics of this type are unspecified." so at least in terminology, it's described as a type in PowerShell even if you can't GetType() on it ..