Everything in PowerShell is represented by "objects" and "types". Let's take a look what "objects" and "types" really are and why you should care. Here is a simple example. Can you guess what it is supposed to do?
$value = Read-Host 'Enter Currency in USD($)'
$result = $value * 0.7
"$value Dollars equals $result EUR"
Right, it is a simple currency converter. Only, it doesn't work. Whatever you enter is just echoed back to you. No conversion. Why?
Looking at Types
Whenever you store something in a variable, PowerShell automatically chooses a "type". Simply use the method GetType() to find out the type, then retrieve the FullName property. As it turns out, anything you get from Read-Host is stored as a System.String type.
PS> $value.GetType().FullName
System.String
This is why the script failed. Whenever you calculate with a mixture of different types, PowerShell first will convert the types so they are all the same. To do this, it looks at the first expression which in our example is a System.String, PowerShell then converts the "conversion factor" 0.7 also to String.
And what happens when you multiply strings? PowerShell simply repeats them:
PS> "Hello" * 5
HelloHelloHelloHelloHello
PS> "Hello" * 1
Hello
PS> "Hello" * 0.7
Hello
PS> "Hello" * "0.7"
Hello
PS> "Hello" * "10"
HelloHelloHelloHelloHelloHelloHelloHelloHelloHello
The Order Matters
So what is happening is this: you enter a number which is stored as String. You multiply this string with a number, and PowerShell repeats the number you entered. Since your conversion factore rounds up to 1, the number you entered is repeated once. Try and change your conversion factor from 0.7 to 4.4, and you see the problem.
To fix the script, you need to make sure your script uses the correct types. You can either change type implicitly like this:
$value = Read-Host 'Enter Currency in USD($)'
$result = 0.7 * $value
"$value Dollars equals $result EUR"
PowerShell again looks at the first expression which this time is your "conversion factor", a numeric type. It then implicitly converts the second value ($value) to the same type. Now, your currency calculator works.
PS> (0.7).GetType().FullName
System.Double
Converting Types Explicitly
Or, you can change types explicitly. A more obvious (and readable) way is to do type conversion explicitly. To convert a type from one type to another, state the target type in square brackets like so:
PS> '1.1.2000'
1.1.2000
PS> [DateTime]'1.1.2000'
Saturday,January 1, 2000 00:00:00
PS> '10.67'
10.67
PS> [Int]'10.67'
11
So to fix your script, you could have explicitly forced PowerShell to convert the input received from Read-Host to the type you need for calculations: System.Double (short: Double):
$value = [Double] (Read-Host 'Enter Currency in USD($)')
$result = $value * 0.7
"$value Dollars equals $result EUR"
Important: Make sure you place Read-Host in parenthesis because what you want to convert is not the Read-Host cmdlet but instead its result. Parenthesis work in PowerShell like in math: they evaluate the parenthesized expression and then return its result.
Graceful Type Conversion
Once you explicitly convert values, you may run into type conversion errors. Of course, PowerShell can only convert types that can be represented by the target type. Have a look:
PS> [Double]10
10
PS> [Double]10.789
10.789
PS> [Double]"76878"
76878
PS> [Double]"This can't be a number"
Cannot convert value "This can't be a number" to type "System.Double". Error: "Inpu
t string was not in a correct format."
At line:1 char:9
+ [Double]" <<<< This can't be a number"
So your script needs a way to find out whether the user input really was a number or not. Incidentally, there is a PowerShell operator that almost acts like TryCast: it tries to convert one type into another, but it does not throw an exception if conversion fails. Here is how you do it:
$value = (Read-Host 'Enter Currency in USD($)') -as [Double]
if ($value -eq $null) {
"Invalid Input."
} else {
$result = $value * 0.7
"$value Dollars equals $result EUR"
}
Basically, the -as operator trys to convert a type to another type. If it fails, no exception is raised. Instead, the result simply is $null. A script can check to see whether the conversion was successful or returned $null and act accordingly.
Wrapping it up
Now that you know that types really do matter, let's try a quick and dirty definition:
Type (aka Class, "Brand"): Anything in PowerShell is derived from a type. A "type" can also be called "class" and is like the "brand" of an object, much like BMW or Chrysler for cars. The brand or type or kind really tells you a lot about its capabilities and characteristics. Just like in the real world, a brand is nice but won't get you from A to B. To actually do things, you need real-world objects that are derived from types.
Object (aka Instance): The actual variables or expressions in PowerShell are real-world objects. Where do objects come from? They are derived from a type. While each type is unique (there can only be one type like System.String or one brand like BMW), it can have an unlimited number of real-world objects derived from it (BMW can sell an unlimited number of cars. Cars are the actual objects made by BMW). Objects are sometimes called "instances of a type" or "instances of a class".
Waiver: Hey, I know there are fundamental and sometimes even philosophical discussions about the differences between types and classes. If you have a great (and practically relevant) definition at hand, please add as comment!
Use GetType().Fullname to find out the type of objects. This is a great start to get a better feeling for the different types out there.
(Get-Date).GetType().FullName
$a = 100; $a.GetType().FullName
(100).GetType().FullName
"Hallo".GetType().FullName
When you mix types (like in math expressions), PowerShell looks at the first expression and tries to convert the second expression implictly (except if both are numeric in which case PowerShell "widens" the expression to the most precise numeric type so 6 * 6.8 still returns a Double and not an Integer).
PS> "6" * 8
66666666
This can result in unexpected results so a safer way is to not trust on implicit type conversion but rather explicitly control types. When you specify the type in square brackets, PowerShell tries to convert it. An exception is raised if conversion fails:
PS> [int]"6" * 8
48
PS> [int]"Hello"
Cannot convert value "Hello" to type "System.Int32". Error: "I
nput string was not in a correct format."
At line:1 char:6
+ [int]" <<<< Hello"
Use the -as operator to do safe type conversion. Should the conversion fail is the result $null. No exceptions are raised.
PS> $test = "Hello" -as [Int]
PS> $test
PS> $test -eq $null
True
PS> $test2 = '1.1.2000' -as [DateTime]
PS> $test2
Saturday, January 1, 2000 00:00:00
Posted
Nov 17 2008, 10:26 AM
by
Tobias Weltner