powershelltype-conversionnumber-literal

Unusual variable type mismatches


If I run the command:

Resize-VHD -ComputerName $VMhost -Path "D:\VMs\$VMname\Virtual Hard Disks\$vmname.vhdx" -SizeBytes 70GB

Powershell is clever enough to understand what 70GB is, accept the arguement and will resize the drive,

However, if I do :

$drivesize = "70GB"

Resize-VHD -ComputerName $VMhost -Path "D:\VMs\$VMname\Virtual Hard Disks\$vmname.vhdx" -SizeBytes $drivesize

I get the following error:

Resize-VHD : Cannot bind parameter 'SizeBytes'. Cannot convert value "70GB" to type "System.UInt64". Error: "Input string was 
not in a correct format."
At line:22 char:100
+ ... D:\VMs\$VMname\Virtual Hard Disks\$vmname.vhdx" -SizeBytes $drivesize
+                                                                ~~~~~~~~~~
+ CategoryInfo          : InvalidArgument: (:) [Resize-VHD], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.Vhd.PowerShell.Cmdlets.ResizeVhd

I think it's a variable type issue, the error says as much, I just have no idea how to fix it.

Edit:

If I write the value of $drivesize to the console, I get:

PS C:\Windows\system32> $drivesize
70GB

Solution

  • Instead of:

    $drivesize = "70GB" # WRONG: Quoting creates a STRING, but you want a NUMBER
    

    use:

    $drivesize = 70GB  # OK: 70GB is a NUMBER LITERAL, evaluating to 75161927680 
    

    To PowerShell, unquoted numeric tokens with binary-multiplier suffixes such as GB are numbers.
    Note that the resulting number's specific integer type varies: the smallest signed integer type equal to or larger than [int] (System.Int32) that can fit the number is used; e.g, 1GB creates an [int], whereas the above example, 70GB, creates a [long] (System.Int64).
    Typically, though, you don't have to worry about specific number types in PowerShell, because they are converted to each other on demand.

    Don't store such tokens in strings; while PowerShell is generally very flexible when it comes to converting strings that look like numbers to actual numbers, it typically does not recognize strings such as "70GB" as numbers - see below.


    Optional reading: To-number conversion of a string containing a numeric token with a suffix such as GB

    Perhaps surprisingly, PowerShell's binary-multiplier suffixes - kb, mb, gb, tb, pb - only work in number literals, and not when (implicitly) converting from a string.

    PS> 1gb  # produces an [int] whose value is equivalent to 1 * [math]::Pow(2, 30)
    1073741824
    
    PS> [int] '1gb' # !! From-string conversion FAILS
    Cannot convert value "1gb" to type "System.Int32". Error: "Input string was not in a correct format."
    
    # Workaround: Simply divide by 1, because PowerShell does
    #             recognize the suffix in the context of an *expression*.
    PS> '1gb' / 1
    1073741824
    

    Tip of the hat to PetSerAl for providing the workaround.
    [Since fixed in PowerShell (Core) 7+] The surprising discrepancy between recognizing suffixes when performing implicit to-number conversions in expressions vs. when parameter-binding is discussed in this GitHub issue.

    The reason that from-string conversion doesn't work is that suffixes are PowerShell-specific, whereas conversion of a string to a number type - whether implicitly during parameter binding or explicitly with a cast such as [int] - uses .NET methods which are unaware of these suffixes.

    Therefore, using a string that expresses the same value without a multiplier suffix would have worked, for instance:

    PS> $driveSize = '1073741824'; [UInt64] $driveSize
    1073741824
    

    Although if you know the value in advance, there is no reason to use a string to begin with, and the use of a number literal avoids the problem:

    $driveSize = 70GB # creates a [long] (System.Int64) with value 75161927680
    

    Note that PowerShell generally widens numeric types on demand (uses larger-capacity types as needed) and automatically performs signed/unsigned type conversions.

    Therefore, even though $driveSize is System.Int64-typed based on the statement above, PowerShell automatically converts it to System.UInt64 (unsigned) when binding to the SizeBytes parameter.