.NET Tutorials, Forums, Interview Questions And Answers
Welcome :Guest
 
Sign In
Register
 
Win Surprise Gifts!!!
Congratulations!!!


Top 5 Contributors of the Month
ASPEvil
Ashutosh Jha
Jean Paul
satyapriyanayak
SP

Home >> Articles >> PowerShell >> Post New Resource Bookmark and Share   

 Subscribe to Articles

Working with PowerShell variables

Posted By:Manning       Posted Date: March 05, 2011    Points: 200    Category: PowerShell    URL: http://www.dotnetspark.com  

In this article author discusses creating variables, variable syntax, working with cmdlets, and a new variable notation called splatting in PowerShell
 

This article is taken from the book Windows PowerShell in Action, Second Edition. The author discusses creating variables, variable syntax, working with cmdlets, and a new variable notation called splatting.


Get
40% off any version of Windows PowerShell in Action with the checkout code dnspark40. Offer is only valid through www.manning.com.

 

PowerShell variables aren't declared; they're just created as needed on first assignment. There also isn't really any such thing as an uninitialized variable. If you reference a variable that does not exist, the system will return the value $null (although it won't actually create a variable).

PS (1) > $NoSuchVariable
PS (2) > $NoSuchVariable -eq $null

True
In the example, we're looking at a variable that doesn't exist and see that it returns $null.

NOTE

$null, like $true and $false, is a special constant variable that is defined by the system. You can't change the value of these variables.


You can tell whether a variable exists or not by using the Test-Path cmdlet as shown:
PS (3) > test-path variable:NoSuchVariable
False

This works because variables are part of the PowerShell unified namespaces. Just as files and the registry are available through virtual drives, so are PowerShell variables. You can get a list of all of the variables that currently exist by doing

dir variable:/
So how do we create a variable? Let's find out.

Creating variables

First off, a number of variables are defined by the system: $true, $false, and $null. User variables are created on first assignment, as we see in the next example.
PS (3) > $var = 1
PS (4) > $var
1
PS (5) > $var = "Hi there"
PS (6) > $var
Hi there
PS (7) > $var = get-date
PS (8) > $var

Sunday, January 29, 2006 7:23:29 PM


In this example, first we assigned a number, then a string, then a DateTime object. This illustrates that PowerShell variables can hold any type of object. If you do want to add a type attribute to a variable, you use the cast notation on the left of the variable. Let's add a type attribute to the variable $val.

PS (1) > [int] $var = 2
Looking at the result, we see the number 2.
PS (2) > $var
2

That's fine. What happens if we try to assign a string to the variable? Let's try it.
PS (3) > $var = "0123"
PS (4) > $var
123

First, there was no error. Second, by looking at the output of the variable, you can see that the string "0123" was converted into the number 123. This is why we say that the variable has a type attribute. Unlike strongly typed languages where a variable can only be assigned an object of the correct type, PowerShell will allow you to assign any object as long as it is convertible to the target type using certain rules. If the type is not convertible then you'll get a runtime type conversion error (as opposed to a "compile-time" error.)

PS (5) > $var = "abc"
Cannot convert "abc" to "System.Int32". Error: "Input string was no
t in a correct format."
At line:1 char:5
+ $var  <<<< = "abc"
In this example, we tried to assign "abc" to a variable with the type attribute [int]. Since "abc" can't be converted to a number, you see a type conversion error.

Variable name syntax

Now what about variable names? What characters are allowed in a variable name? The answer is: any character you want, with some caveats. There are two notations for variables. The simple notation starts with a dollar sign followed by a sequence of characters, which can include letters, numbers, the underscore, and the colon. The colon has a special meaning that we'll get to in a minute. The second notation allows you to use any character in a variable name. It looks like this:


${This is a variable name}

You can use any character you want in the braces. You can even use a close brace if you escape it, as we see in the next example.
PS (7) > ${this is a variable name with a `} in it}
PS (8) > ${this is a variable name with a `} in it} = 13
PS (9) > ${this is a variable name with a `} in it}
13

Earlier, we said that the colon character was special in a variable name. This is used to delimit the namespace that the system uses to locate the variable. For example, to access PowerShell global variables, you use the global namespace:
PS (1) > $global:var = 13
PS (2) > $global:var
13

This example set the variable "var" in the global context to the value 13. You can also use the namespace notation to access variables at other scopes.

 

This is called a scope modifier.

Along with the scope modifiers, the namespace notation lets you get at any of the resources surfaced in PowerShell as drives. For example, to get at the environment variables, you use the env namespace as shown:
PS (1) > $env:SystemRoot
C:\WINDOWS
In this example, we retrieved the contents of the SystemRoot environment variable. You can use these variables directly in paths. For example:
PS (3) > dir $env:systemroot\explorer.exe

  Directory: Microsoft.Management.Automation.Core\FileSystem::C:\
  WINDOWS

Mode  LastWriteTime  Length Name
----  -------------  ------ ----
-a---  8/10/2004  12:00 PM  1032192 explorer.exe

This retrieved the file system information for explorer.exe.


NOTE:

For cmd.exe or command.com users, the equivalent syntax would be %systemroot%\explorer.exe. There, the percent signs delimit the variable. In PowerShell, this is done with braces.

Many of the namespace providers are also available through the variable notation (but you usually have to wrap the path in braces). Let's look at an

 

example:


${c:old.txt} -replace 'is (red|blue)','was $1' > new.txt
The sequence ${c:old.txt} is a variable that references the file system provider through the C: drive and retrieves the contexts of the file named "old.txt". With this simple notation, we read the contents of a file. No open/read/close-we treat the file itself as an atomic value.

NOTE:

Using variable notation to access a file can be startling at first, but it's a logical consequence of the unified data model in PowerShell. Since things like variables and functions are available as drives, things such as drives are also available using the variable notation. In effect, this is an application of the Model-View Controller (MVC) pattern. Each type of data store (filesystem, variables, registry, and so forth) is a "model". The PowerShell provider infrastructure acts as the controller and there are (by default) two views: the "filesystem" navigation view and the variable view. The user is free to choose and use the view most suitable to the task at hand.


You can also write to a file using the namespace variable notation. Here's that example rewritten to use variable assignment instead of a redirection operator (remember, earlier we said that assignment can be considered a form of redirection in PowerShell.)

${c:new.txt} = ${c:old.txt} -replace 'is (red|blue)','was $1'
In fact, you can even do an in-place update of a file by using the same variable on both sides of the assignment operator. To update the file "old.txt"

instead of making a copy, do
${c:old.txt} = ${c:old.txt} -replace 'is (red|blue)','was $1'

All we did was change the name in the variable reference from "new.txt" to "old.txt". This won't work if you use the redirection operator, because the output file is opened before the input file is read. This would have the unfortunate effect of truncating the previous contents of the output file. In the assignment case, the file is read atomically; that is, all at once, processed, then written atomically. This allows for "in-place" edits because the file is actually buffered entirely in memory instead of in a temporary file. To do this with redirection, you'd have to save the output to a temporary file and then rename the temporary file so it replaces the original. Now let's leverage this feature along with multiple assignments to swap two files, "f1.txt" and

 

"f2.txt". Here we go:
${c:f1.txt},${c:f2.txt} = ${c:f2.txt},${c:f1.txt}

NOTE:

All of these examples using variables to read and write files cause the entire contents of files to be loaded into memory as a collection of strings. On modern computers, it's possible to handle moderately large files this way, but doing it with very large files is memory intensive, inefficient, and might even fail under some conditions. Keep this in mind when using these techniques.


When the filesystem provider reads the file, it returns the file as an array of strings.

NOTE:

When accessing a file using the variable namespace notation, PowerShell assumes that it's working with a text file. Since the notation doesn't provide a mechanism for specifying the encoding, you can't use this technique on binary files. You'll have to use the Get-Content and Set-Content cmdlets instead.


This provides a simple way to get the length of a file:
${c:file.txt}.length

The downside of this simple construct is that it requires reading the entire file into memory and then counting the result. It works fine for small files (a few megabytes) but it won't work on files that are gigabytes in size.

Working with the variable cmdlets


So far we've been using the PowerShell language features to access variables but we can also work with variables using the variable cmdlets. These cmdlets let us do a couple of things we can't do directly from the language.


Indirectly setting a variable


Sometime it's useful to be able to get or set a variable when we won't know the name of that variable until runtime. For example, we might want to initialize a set of variables from a .csv file. We can't do this using the variable syntax in the language because the name of the variable to set is resolved at parse time. Let's work through this example. First we need a .csv file:


PS (1) > cat variables.csv
"Name", "Value"
"srcHost",  "machine1"
"srcPath",  "c:\data\source\mailbox.pst"
"destHost", "machine2"
"destPath", "d:\backup"
As we can see, .csv file is simply a text file with rows of values separated by commas, hence CSV or comma-separated values. Now we'll use the Import-CSV cmdlet to import this file as structured objects.
PS (2) > import-csv variables.csv

Name  Value
----  -----
srcHost  machine1
srcPath  c:\data\source\mailbox.pst
destHost  machine2
destPath  d:\backup

We can see the cmdlet has treated the first row in the table as the names of the properties to use on the objects and then added the name and value property to each object. The choice of "name" and "value" was deliberate because these are the names of the parameters on the Set-Variable cmdlet. This cmdlet takes input from the pipeline by property name so we can simply pipe the output of Import-CSV directly into Set-Variable.


PS (3) > import-csv variables.csv | set-variable

And, it's as simple as that. If we wanted to see the full details, we could specify the -verbose parameter to the cmdlet and it would display each variable as it was set. Now use the normal variable syntax to verify that we've set up the things the way we planned.

PS (4) > $srcHost

Name  Value
----  -----
srcHost  machine1
OK, good. Of course, we can use the parameters on the cmdlet to directly set this variable:
PS (5) > set-variable -name srcHost -value machine3
PS (6) > $srcHost
machine3
Or use the (much) shorter alias sv to do it.
PS (7) > sv srcHost machine4
PS (8) > $srcHost
machine4
Now let's see what else we can do with the cmdlets.


Getting and setting variable options

If there is a cmdlet to set a variable, obviously there should also be a variable to get variables-the Get-Variable cmdlet:
PS (9) > get-variable -value srcHost
machine4

Notice that we specified the -Value parameter in this example. What happens if we don't do that? Let's find out.
PS (10) > get-variable srcHost | gm

  TypeName: System.Management.Automation.PSVariable

Name         MemberType Definition

----         ---------- ----------

Equals       Method     bool Equals(System.Object obj)

GetHashCode  Method     int GetHashCode()

GetType      Method     type GetType()

IsValidValue Method     bool IsValidValue(System.Object ...

ToString     Method     string ToString()

Attributes   Property   System.Collections.ObjectModel.C...

Description  Property   System.String Description {get;s...

Module       Property   System.Management.Automation.PSM...

ModuleName   Property   System.String ModuleName {get;}

Name         Property   System.String Name {get;}

Options      Property   System.Management.Automation.Sco...

Value        Property   System.Object Value {get;set;}

Visibility   Property   System.Management.Automation.Ses...

 

What Get-Variable returns if -Variable is not specified is the PSVariable object that PowerShell uses to represent this object. We can see the Name and Value properties on this object but there are a lot of other properties as well. Let's explore the Options property. This property allows us to set options on the variable including things like ReadOnly and Constant. The variables we've read from the .csv file are still changeable as we can see here:
PS (11) > $srcHost = "machine9"
PS (12) > $srcHost
machine9


But, if we're using them to configure the environment, we may not want them to be. To address this, we can set the ReadOnly option using Set-Variable and the -Option parameter.

PS (13) > set-variable -option readonly srcHost machine1
PS (14) > $srcHost = "machine4"

Cannot overwrite variable srcHost because it is read-only o
r constant.

At line:1 char:9

+ $srcHost <<<<  = "machine4"
  + CategoryInfo  : WriteError: (srcHost:String)
  [], SessionStateUnauthorizedAccessException
  + FullyQualifiedErrorId : VariableNotWritable
Now, when we try and change the value of this variable, we get an error.
PS (15) > get-variable -value srcHost

Name           Value
----                -----
srcHost        machine1


If we can't change it, then, how about removing it? We'll try just the remove command:


PS (16) > remove-variable srcHost


Remove-Variable : Cannot remove variable srcHost because it
 is constant or read-only. If the variable is read-only, tr
y the operation again specifying the Force option.


At line:1 char:16
+ remove-variable <<<<  srcHost
  + CategoryInfo  : WriteError: (srcHost:String)
  [Remove-Variable], SessionStateUnauthorizedAccessExce
  ption
  + FullyQualifiedErrorId : VariableNotRemovable,Microso
  ft.PowerShell.Commands.RemoveVariableCommand


And, this failed with the expected error. But, we can still force the removal of a readonly variable by using the -Force parameter on Remove-Variable.
PS (17) > remove-variable -force srcHost


When we specify -Force, the variable is removed and there is no error. If we really, really don't want the value to be changed, then we can use the Constant option.


PS (18) > set-variable -option constant srcHost machine1


When this option is specified, then even using -Force will fail.


PS (19) > remove-variable -force srcHost


Remove-Variable : Cannot remove variable srcHost because it
 is constant or read-only. If the variable is read-only, tr
y the operation again specifying the Force option.


At line:1 char:16


+ remove-variable <<<<  -force srcHost
  + CategoryInfo  : WriteError: (srcHost:String)
  [Remove-Variable], SessionStateUnauthorizedAccessExce
  ption
  + FullyQualifiedErrorId : VariableNotRemovable,Microso
  ft.PowerShell.Commands.RemoveVariableCommand


And, now, for one last trick-we've looked at how to use the name of a variable to access it indirectly. We can bypass the whole name-lookup process and use the variable reference directly. Here's how this works.


Using PSVariable objects as references


To use a PSVariable object as a reference, first we have to get one. Earlier we saw how to do this with Get-Variable (or its alias gv).


PS (21) > $ref = gv destHost


Now that we have a reference, we can use the reference to get the name:


PS (22) > $ref.Name


destHost


Or the value of this variable:


PS (23) > $ref.Value

Name              Value
----                  -----
destHost       machine2


Having the reference also allows us to set the variables value.


PS (24) > $ref.Value = "machine12"


When we check if the variable uses the language syntax, we see the change.


PS (25) > $destHost


machine12


Variable names vs. variable values


Here's a tip to keep in mind if you're ever trying to do these tricks. You need to keep the variable name and the variable value firmly separated in your thinking. If you don't think about what you're doing closely enough, trying to use '$name' to get the value of the variable seems reasonable:
PS (26) > gv $srcPath


Get-Variable : Cannot find a variable with name '@{Name=src
Path; Value=c:\data\source\mailbox.pst}'.
At line:1 char:3
+ gv <<<<  $srcPath
  + CategoryInfo  : ObjectNotFound: (@{Name=srcP
  ath;...ce\mailbox.pst}:String) [Get-Variable], ItemNot
  FoundException
  + FullyQualifiedErrorId : VariableNotFound,Microsoft.P
  owerShell.Commands.GetVariableCommand

But, it gives us a rather confusing error. This is because PowerShell resolved the token '$srcPath' and passed its value to the cmdlet, not the name. Even quoting it but still having the $-sign in the string is wrong:


PS (27) > gv '$srcPath'
Get-Variable : Cannot find a variable with name '$srcPath'.
At line:1 char:3
+ gv <<<<  '$srcPath'
  + CategoryInfo  : ObjectNotFound: ($srcPath:St
  ring) [Get-Variable], ItemNotFoundException
  + FullyQualifiedErrorId : VariableNotFound,Microsoft.P
  owerShell.Commands.GetVariableCommand


This error seems bizarre because we know that there is such a variable. The reason it fails is because '$' isn't actually part of the variables name. It's part of a token in the PowerShell language indicating that whatever follows the $ is the name of a variable.


The correct way to do this is to use the variable name without the leading '$'.


PS (28) > gv srcPath

Name   Value
----  -----
srcPath  @{Name=srcPath; Value=c:\...


Finally, here's why all of this works the way it does. Let's define a variable $n that has part of the path name.


PS (29) > $n = 'src'


Now we combine that variable with another fragment using string expansion and it works properly.


PS (30) > gv "${n}Path"

Name  Value
----  -----
srcPath  @{Name=srcPath; Value=c:\...

PS (31) >


This gives us a great deal of flexibility when dealing with variable names. It can be complex, but any situation where you need to do this is, by definition, complex. Having this facility doesn't complicate normal day-to-day activities but does make some more sophisticated scenarios possible. Now let's look at another set of potentially complex scenarios that can be solved by using variables in a special way.


Splatting a variable


The last topic that we're going to touch on in this article is something called variable splatting, which was added to PowerShell in version 2. This is a term taken from the Ruby scripting language and affects how argument variables are passed to commands.


Normally, when you have a variable containing an array or hashtable and you use this variable as a command argument, its value is passed as a single argument. What splatting does is turn each value in the collection into individual arguments. So, if you have an array of with three elements in it, then those elements will be passed as three individual arguments. If you have a hashtable, each name/value pair becomes a named parameter/argument pair for the command.


The way you do this is, when referencing the variable that you want to pass to the command, you use '@' instead of '$' as the prefix to the variable. Here's an example to show how this all works. First, we need a command to work with; we'll define a function that takes three arguments.


PS {1) > function s ($x, $y, $z) { "x=$x, y=$y, z=$z" }

This function uses string expansion to display the value of each of its parameters. Now, let's create an array to pass into this command.

PS {2) > $list = 1,2,3

The variable $list contains three integers. Let's pass this using the normal variable notation.

PS {3) > s $list
x=1 2 3, y=, z=

From the output, we can see that all three values in the argument were assigned to the $x parameter. The other two parameters didn't get assigned anything. Now, let's splat the variable. We do this by calling the function with @list instead of $list.

PS {4) > s @list
x=1, y=2, z=3


This time, the output shows that each parameter was assigned one member of the array in the variable. What happens if there are more elements that there are variables? Let's try it. First, we'll add some elements to our $list variable.

PS {5) > $list += 5,6,7
PS {6) > $list
1
2
3
5
6
7
Now, the variable contains 7 elements. Let's pass this to the function.
PS {7) > s @list
x=1, y=2, z=3


And, it appears that the last 4 arguments have vanished. In fact, what has happened is that they are assigned to the special variable $args. Let's redefine the function to show this.


PS {8) > function s ($x, $y, $z) { "$x,$y,$z args=$args" }
Now we'll print out the three formal arguments-$x, $, and $z-along with the special $args variable. When we run the new function, we see that the missing arguments have ended up in $args.


PS {9) > s @list
1,2,3 args=5 6 7


The most important use for splatting is for enabling one command to effectively call another. We'll see how this can be used to wrap existing commands and either extend or restrict their behavior in later chapters.


Now, that we understand how an array of values can be splatted, let's look at how we work with named parameters. In the example above, we could have used the explicit names of the parameters to pass things in instead of relying on position. For example, we can use the names to explicitly pass in values for -x and -y, in the reverse order:


PS {10) > s -y first -x second
second,first, args=
And, we see that second is in the first (x) position and first is in the second position. Now, how can we use splatting to do this? Well, parameters and their values are name/value pairs and, in PowerShell, the way to work with name/value pairs is with hashtables. Let's try this out. First, we'll create a

 

hashtable with the values we want:

PS {11) > $h = @{x='second'; y='first'}


Now, we'll splat the hashtable the same way we splatted the variable containing an array:

PS {12) > s @h
second,first, args=

And, as before, the "x" parameter gets the value "second" and the "y" parameter gets the value "first".  The next question you should have is-what happens if we also want to explicitly pass in -z? Let's try it:
PS {13) > s -z third  @h 1 2 3
second,first,third args=1 2 3


And, it works exactly the way we want. Now, if we specify the parameter both in the hash table and in explicitly on the command line, we'll get an error:


PS {14) > s -x boo  @h 1 2 3
s : Cannot bind parameter because parameter 'x' is specifie
d more than once. To provide multiple values to parameters
that can accept multiple values, use the array syntax. For
example, "-parameter value1,value2,value3".
At line:1 char:2
+ s <<<<  -x boo  @h 1 2 3
  + CategoryInfo  : InvalidArgument: (:) [s], Pa
  rameterBindingException
  + FullyQualifiedErrorId : ParameterAlreadyBound,s


Let's look at a practical example using this feature. The Write-Host cmdlet allows you to write strings to the screen specifying the foreground and background colors. This is great, but if you need to write a lot of strings or parameterize the colors that are used, repeatedly setting both parameters will get a bit tedious as we see here:


PS {16) > write-host -foreground black -background white Hi
Hi


Specifying the parameters takes up more space than the string we want to write! Using splatting, instead of passing in both parameters all the time, we can set up a hashtable once and pass that around instead:


PS {17) > $colors = @{foreground="black";background="white"}
PS {18) > write-host @colors "Hi there"
Hi there


This approach is more convenient and less error prone than having to explicitly pass both color parameters making it an effective way to "style" your output using a single variable.


Note:


By now I'm sure you're wondering why this technique is it called splatting. Here's the reasoning behind this term. Think of a rock hitting a car windshield. A rock is a solid object that remains intact after it bounces off your car. Next, think of a bug hitting the windshield instead of a rock. Splat! The contents of the bug are distributed over the windshield instead of remaining as a single object. This is what splatting does to a variable argument. It distributes the members of the argument collection as individual arguments instead of remaining a single intact argument. (The other rationale behind this term is that, in Ruby, the operator is '*', which is what the aforementioned insect looks like post impact. PowerShell can't use '*' because it would be confused with the wildcard character. Instead, we used '@' since splatting involves arrays and we use '@' for many array operations.) I submit that this is the most visceral mnemonic in the programming language field (at least that I am aware of).


Summary


PowerShell variable namespaces let you access a variety of Windows data stores, including environment variables and the file system using the variable notation. It's possible to use the variable cmdlets to set options on variables and do indirect variable accesses using either the variable name or a PSVariable object. PowerShell version 2 introduced a new variable notation called splatting that allows you to take collections of values-either arrays or hashtables-and distribute the members of these collections as individual arguments to a command.



Windows PowerShell in Action, Second Edition
EARLY ACCESS EDITION

Bruce Payette
MEAP Release: February 2009
Softbound print: February 2011 (est.) | 900 pages
ISBN: 9781935182139


 Subscribe to Articles

     

Further Readings:

Responses
Author: Harris C         Company URL: http://www.dotnetspark.com
Posted Date: May 25, 2011

Thanks for the insight.

Now I am having problems with passing SQL commands to my PS. It is treating the sql query as "splatting". Is there a way to turn off or override this feature?

.\Invoke-SQL -cmd "exec UTILS.GetEmployee @ID = '123', @SomeCommand = 'sadfasdf'"

Post Comment

You must Sign In To post reply
Find More Articles on C#, ASP.Net, Vb.Net, SQL Server and more Here

Hall of Fame    Twitter   Terms of Service    Privacy Policy    Contact Us    Archives   Tell A Friend