Earlier we mentioned that even variables are objects. In particular, string variables in .NET are extremely robust and have a number of methods and properties that you'll meet in Chapter 5. One of them is Split, which is a method that takes a string and creates an array (or list) out of it by breaking the list up on some character like a comma or a space. Try this in PowerShell:
PS:> "1,2,3,4".Split(",")
What you're telling PowerShell is "take this string and execute its Split method. Use a comma for the method's input argument." When PowerShell does this, the method returns an array of four elements that each contains a number. PowerShell gets that array and displays it in a textual fashion with one array element per line:
1 2 3 4
There are other ways to use this technique. For example, PowerShell has a cmdlet called Get-Member that displays the methods and variables associated with a given object instance. So, take a string like "Hello, World"-which, remember, is an instance of a String object-and pipe it to the Get-Member cmdlet to display information about that String object:
PS C:\> "Hello, World" | get-member TypeName: System.String Name MemberType Definition ---- ---------- ---------- Clone Method System.Object Clone() CompareTo Method System.Int32 CompareTo(Objec Contains Method System.Boolean Contains(Stri CopyTo Method System.Void CopyTo(Int32 sou EndsWith Method System.Boolean EndsWith(Stri Equals Method System.Boolean Equals(Object get_Chars Method System.Char get_Chars(Int32 get_Length Method System.Int32 get_Length() GetEnumerator Method System.CharEnumerator GetEnu GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() GetTypeCode Method System.TypeCode GetTypeCode( IndexOf Method System.Int32 IndexOf(Char va IndexOfAny Method System.Int32 IndexOfAny(Char Insert Method System.String Insert(Int32 s IsNormalized Method System.Boolean IsNormalized( LastIndexOf Method System.Int32 LastIndexOf(Cha LastIndexOfAny Method System.Int32 LastIndexOfAny( Normalize Method System.String Normalize(), S PadLeft Method System.String PadLeft(Int32 PadRight Method System.String PadRight(Int32 Remove Method System.String Remove(Int32 s Replace Method System.String Replace(Char o Split Method System.String[] Split(Params StartsWith Method System.Boolean StartsWith(St Substring Method System.String Substring(Int3 ToCharArray Method System.Char[] ToCharArray(), ToLower Method System.String ToLower(), Sys ToLowerInvariant Method System.String ToLowerInvaria ToString Method System.String ToString(), Sy ToUpper Method System.String ToUpper(), Sys ToUpperInvariant Method System.String ToUpperInvaria Trim Method System.String Trim(Params Ch TrimEnd Method System.String TrimEnd(Params TrimStart Method System.String TrimStart(Para Chars ParameterizedProperty System.Char Chars(Int32 inde Length Property System.Int32 Length {get;}
This output is truncated a bit to fit in this book. However, you can see it includes every method and property of the String, and correctly identifies "Hello, World" as a "System.String" type, which is the unique type name that describes what we informally call a String object. You can pipe nearly anything to Get-Member to learn more about that particular object and its capabilities.
The fact that PowerShell is built on and around .NET gives PowerShell tremendous power, which isn't always obvious. For example, in Chapter 1 we explained that any PowerShell variable can contain any type of data. This occurs because all types of data-strings, integers, dates, etc-are .NET classes that inherit from the base class named Object. A PowerShell variable can contain anything that inherits from Object. However, as in the previous example with a string, PowerShell can certainly tell the difference between different classes that inherit from Object.
You can force PowerShell to treat objects as a more specific type. For example, take a look at this sequence:
PS C:\> $one = 5 PS C:\> $two = "5" PS C:\> $one + $two 10 PS C:\> $one = 5 PS C:\> $two = "5" PS C:\> $one + $two 10 PS C:\> $two + $one 55
In this example we gave PowerShell two variables: one contained the number five, and the other contained the string character "5." Even though this might look the same to you, it's a big difference to a computer! However, we didn't specify what type of data they were, so PowerShell assumed they were both of the generic Object type. PowerShell also decided it would figure out something more specific when the variables are actually used.
When we added $one and $two, or 5 + "5," PowerShell said, "Aha, this is addition: The first character is definitely not a string because it wasn't in double quotes. The second character one was in double quotes but… well, if I take the quotes away it looks like a number, so I'll add them." This is why we correctly got ten as the result.
However, when we added $two and $one-reversing the order- PowerShell had a different decision to make. This time PowerShell said, "I see addition, but this first operand is clearly a string. The second one is a generic Object. So let's treat it like a string too and concatenate the two." This is how we got the string "55," which is the first five tacked onto the second five.
But what about:
PS C:\> [int]$two + $one 10
Same order as the example that got "55," but in this type we specifically told PowerShell that the generic object in $two was an Int, or integer, which is a type PowerShell knows about. So this time PowerShell used the same logic as in the first example. When it added the two, it came up with "10."
You can force PowerShell to treat anything as a specific type. For example:
PS C:\> $int = [int]"5" PS C:\> $int | get-member TypeName: System.Int32 Name MemberType Definition ---- ---------- ---------- CompareTo Method System.Int32 CompareTo(Int32 value), System.Int Equals Method System.Boolean Equals(Object obj), System.Boole GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() GetTypeCode Method System.TypeCode GetTypeCode() ToString Method System.String ToString(), System.String ToStrin
Here, the value "5" would normally be either a String object or, at best, a generic Object. But by specifying the type [int], we forced PowerShell to try and convert "5" into an integer before storing it in the variable $int. The conversion was successful, which you can see when we piped $int to Get-Member revealing the object's type: System.Int32.
Note that once you apply a specific type to a variable, it stays that way until you specifically change it. For example:
[int]$int = 1
This creates a variable named $int as an integer, and assigns it the value 1. The $int variable will be treated as an integer from now on, even if you don't include the type:
$int = 2
It is still using $int as an integer because it was already cast into a specific type. Once set up to be an integer, you can't put other types of data into it. Here's an example of an error that occurred when we tried to put a string into a variable that was already specifically cast as an integer:
PS C:\> [int]$int = 1 PS C:\> $int = 2 PS C:\> $int = "hello" Cannot convert value "hello" to type "System.Int32". Error: "Input string was not in a correct format." At line:1 char:5 + $int <<<< = "hello" PS C:\>
However, you can recast a variable by reassigning a new, specific type:
[string]$int = "Hello"
That works just fine, and $int will now be treated as a string by PowerShell.
PowerShell isn't a miracle worker: For example, if you try to force it to convert something that doesn't make sense, it will complain:
PS C:\> $int = [int]"Hello" Cannot convert "Hello" to "System.Int32". Error: "Input string was not in a correct format." At line:1 char:13 + $int = [int]" <<<< Hello"
This occurred because "Hello" can't sensibly be made into a number.
This one's even more fun because it illustrates some of the advanced data types:
PS C:\> $xml = [xml]"<users><user name='joe' /></users>" PS C:\> $xml.users.user name ---- joe
In this example we created a string, but told PowerShell it was of the type XML, which is another data type that PowerShell knows. XML data works sort of like an object: We defined a parent object named Users and a child object named User. The child object had an attribute called Name, with a value of Joe. So when we asked PowerShell to display $xml.users.user, it displays all the attributes for that user. We can prove that PowerShell treated $xml as XML data by using Get-Member:
PS C:\> $xml | get-member TypeName: System.Xml.XmlDocument Name MemberType Definition ---- ---------- ---------- ToString CodeMethod static System.Stri add_NodeChanged Method System.Void add_No add_NodeChanging Method System.Void add_No add_NodeInserted Method System.Void add_No add_NodeInserting Method System.Void add_No add_NodeRemoved Method System.Void add_No add_NodeRemoving Method System.Void add_No AppendChild Method System.Xml.XmlNode Clone Method System.Xml.XmlNode CloneNode Method System.Xml.XmlNode CreateAttribute Method System.Xml.XmlAttr CreateCDataSection Method System.Xml.XmlCDat CreateComment Method System.Xml.XmlComm CreateDocumentFragment Method System.Xml.XmlDocu CreateDocumentType Method System.Xml.XmlDocu CreateElement Method System.Xml.XmlElem CreateEntityReference Method System.Xml.XmlEnti CreateNavigator Method System.Xml.XPath.X CreateNode Method System.Xml.XmlNode CreateProcessingInstruction Method System.Xml.XmlProc ...
This demonstrates not only that variables are objects, but also that PowerShell understands different types of data, and provides different capabilities for the various types of data.
Curious about what object types are available? Here's a quick list of more common types (although there are more than this):
Array
Bool (Boolean)
Byte
Char (a single character)
Char[] (Character array)
Decimal
Double
Float
Int (Integer)
Int[] (Integer array)
Long (Long integer)
Long[] (Long integer array)
Regex (Regular expression)
Single
Scriptblock
String
XML
You will learn more about variables in Chapter 5. We'll also be popping in with details on these other types as appropriate throughout this book. Some of the types aren't frequently used in administrative scripting, so we will not arbitrarily hit you with all of them at once. Instead, we'll cover them in a context where they're used for something useful.
One thing to be careful of is PowerShell's ability to change the type of a variable if you haven't explicitly selected a type. For example:
Write-host $a.ToUpper()
This works fine if $a contains a string, as shown here:
PS C:\> $a = "Hello" PS C:\> write-host $a.ToUpper() HELLO PS C:\>
However, if $a was already set to an integer value you'll get an error:
PS C:\> $a = 1 PS C:\> write-host $a.ToUpper()
This occurs because, as an integer, $a doesn't have a ToUpper() method. You need to watch out for this when you're writing scripts that take input from other sources. For example, this might occur with a user or a file since this type of error can be tricky to troubleshoot. One way around it is to force PowerShell to treat the variable as the string you're expecting it to be:
PS C:\> $a = 1 PS C:\> $a = [string]$a PS C:\> write-host $a.ToUpper() 1 PS C:\>
You don't necessarily need to select a type up-front for every variable you use. However, you should be aware of situations that can make a variable contain a type of data other than what you originally expected.
PowerShell is built on and around the .NET Framework, which means everything in PowerShell has a distinctly .NET flavor to it. On one level, you can ignore this and use PowerShell at a more simple level. For example, you can let it treat everything as a generic Object. However, as you grow with PowerShell, and want to leverage more powerful features, you'll find yourself gradually learning more about .NET.
This chapter wasn't meant to be a comprehensive look at .NET-that's another book entirely! Instead, the purpose of this chapter is to provide a rather a quick look at how .NET impacts the way PowerShell is built and the way PowerShell works. You'll see a lot more details about these topics-especially variables and their capabilities-throughout the remaining chapters.