Remoting Quoting
The information in this article was written about the Community Technology Preview (CTP) of Windows PowerShell 2.0. This information is subject to change in future releases of Windows PowerShell 2.0.
On This Page
Remoting Quoting Quotation Marks in Windows PowerShell Quotation Marks in Remote Commands Try It For More Information
Remoting Quoting
By June Blender Windows PowerShell Documentation Team
I love quotes. Here's my current favorite:
"I am somehow less interested in the weight and convolutions of Einstein's brain than in the near certainty that people of equal talent have lived and died in cotton fields and sweatshops." -- Stephen Jay Gould
But quotes, and quotation marks, can be tricky in Windows PowerShell. And they can be even trickier in remote commands that use the Invoke-Expression cmdlet.
Fortunately, the interpretation of quotation marks in Windows PowerShell is governed by some very simple rules. Once you master the rules, they become your tools, instead of your obstacles.
You can quote me on that.
So, today, we'll examine the use of quotation marks in Invoke-Expression commands in Windows PowerShell 2.0 CTP. But first, the warning:
WARNING: Windows PowerShell 2.0 CTP is just a preview (That's the P in CTP). The final version of Windows PowerShell 2.0 might be significantly different. |
By the way, to understand this article, you need to be familiar with the basics of remote commands in Windows PowerShell 2.0 CTP. You can find that information here.
Quotation Marks in Windows PowerShell
First, let's review the basic distinction between single and double quotation marks in the Windows PowerShell language. It concerns the interpretation of variables.
Double quotes. When a command or expression that includes variables is enclosed in double quotes ("), the variables are replaced by their values before the command is executed.
Single quotes. When a command or expression that includes variables is enclosed in single quotes ('), the variables are not replaced by their values. Instead, they are interpreted literally.
There's actually a lot more to it – nested quotes and doubled quotes and escaped quotes and quotes in here-strings -- but these are the basics. For the rest, at the Windows PowerShell prompt, type:
- get-help about_quoting_rules
Quotation Marks in Remote Commands
Quotation marks are really important in remote commands, because the value of the Command parameter of Invoke-Expression must be a string. If the imbedded command includes spaces, parentheses or special characters, you need to enclose it in quotation marks.
And the type of quotation marks that you choose make all the difference.
The outermost quotation marks – the ones that make the value of the Command parameter a string -– are interpreted on the local computer and then discarded. The remote computer receives the command without the outermost quotation marks. That is, the remote computer receives a command, not a string that contains a command.
Double quotes. When the outermost quotation marks in an Invoke-Expression command are double-quotes ("), any variables in the command are replaced by their values on the local computer, before the command is sent to the remote computer.
Single quotes. When the outermost quotation marks in an Invoke-Expression command are single quotes ('), the variables are not replaced by their values on the local computer. Instead, they are sent to the remote computer to be interpreted on the remote computer.
Try It
Let's try some examples. In these examples, the remote computer is running Windows Vista with Service Pack 1 and the local computer is running Windows XP with SP2.
We'll start with some simple examples. When the embedded command (the value of the Command parameter) doesn't include spaces, parentheses, or a variable, no quotation marks are needed.
invoke-expression -computername Server01 -command get-process
In commands that don't include variables, double or single quotes have the same effect. These two commands are effectively the same.
invoke-expression -comp Server01 -command "get-process powershell"
invoke-expression -comp Server01 -command 'get-process powershell'
However, the quotation marks do matter when the command contains a variable. For example, let's display the value of the $home variable on the local and remote computers. $home is a Windows PowerShell "automatic variable" that stores the full path the current user's home directory, that is, %homedrive%\%homepath%. (You can find out about automatic variables by typing : "get-help about_automatic_variables".)
This command displays the value of $home on the local computer.
$home
Here's the output:
C:\Documents and Settings\user01
Now, let's run the same command on the Server01 remote computer.
invoke-expression -computername Server01 -command "$home"
Here's the output:
Invoke-Expression : The term 'C:\Documents' is not recognized as a cmdlet, function, operable program, or script file.
Verify the term and try again.
At line:1 char:18
+ invoke-expression <<<< -computername Server01 -command "$home"
Several things went wrong here, but one was that the $home variable was interpreted on the local computer. (There is no "Documents and Settings" directory on Windows Vista.) Also, because the local home directory path contains spaces, Windows PowerShell interpreted the substituted value as more than one string.
Let's try the remote command again. This time, we'll enclose the command in single quotes.
invoke-expression -computername Server01 -command '$home'
Here's the output:
C:\Users\user01.DOMAIN01
We should always verify that the output actually came from the remote computer. To do this, run the command again and save the results in the $output variable.
$output = invoke-expression -computername Server01 -command '$home'
Now, display the value of the PsipHostEntry property of the $output variable. PsIpHostEntry is a property added to remote results that contains information about the computer on which the command ran.
$output.psiphostentry
Here are the results. They confirm that the command in single quotes retrieved the value from the remote machine.
ComputerName Aliases Addresses
------------ ------- ---------
Server01 {} {172.30.181.234}
For most commands, you will use single quotes, because you usually want the variables to be evaluated on the remote computer. But if you want the variable to be evaluated on the local computer, use double quotes. For example, the first command in the following pair of commands stores a list of services in a variable; the second command gets those services on the remote computer. Because the variable exists on the local computer, you want the string of services to be substituted for the variable before you send it to the remote computer.
$services = "wmi*, winrm, netlogon, plugplay"
invoke-expression -computername Server01 -command "get-service $s"
Here's the output:
Status Name DisplayName
------ ---- -----------
Running Netlogon Netlogon
Running PlugPlay Plug and Play
Running WinRM Windows Remote Management (WS-Manag...
Stopped wmiApSrv WMI Performance Adapter
If you run the command with single quotes, the Get-Service cmdlet returns all of the services on the remote computer, because $s is null.
invoke-expression -computername Server01 -command 'get-service $s'
Now, don't generalize too much. If you tried to run this same command on the local computer without Invoke-Expression, it would fail, because all of the services are in a single string. The outermost quotes are not deleted from the value of a Get-Service command, like they are from an Invoke-Expression command. As a result, the Get-Service cmdlet would be looking for a service called "wmi*, winrm, netlogon, plugplay" and it wouldn't find one.
To run the same command on the local computer, each service would have to be in its own string, like this:
$services = "wmi*", "winrm", "netlogon", "plugplay"
get-service $services
Another helpful way to use quotation marks is to set the boundaries of the invoke-expression command. Consider the following commands:
invoke-expression -comp Server01, Server02 -command get-service | group-object Status
invoke-expression -comp Server01, Server02 -command 'get-service | group-object Status'
These commands are both valid and they produce different results. Which one is right? It depends on what you want.
The first command uses the Invoke-Expression cmdlet to run a Get-Service command on the Server01 and Server02 computers. This command does not require quotation marks, because "Get-Service" does not have any spaces or special characters. The pipeline character (|) sends the services retrieved by the Invoke-Expression command to the Group-Object cmdlet, which groups the services by status. The Get-Service command runs on Server01n and Server02. The Group-Object command runs on the local computer. Here's the command and the result:
invoke-expression -comp Server01, Server02 -command get-service | group-object Status
Count Name Group
----- ---- -----
141 Running {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
99 Stopped {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
The second command uses quotation marks to enclose the Get-Service and Group-Object commands. The quotes make the whole pipeline part of the value of the Command parameter of Invoke-Expression cmdlet. As a result, both Get-Service and Group-Object are run on Server01 and Server02. So, the services are grouped separately on each of the remote computers, not on the local computer.
Here's the command and the result:
invoke-expression -comp Server01, Server02 -command 'get-service | group-object Status'
Count Name Group
----- ---- -----
62 Running {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
47 Stopped {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
79 Running {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
52 Stopped {System.ServiceProcess.ServiceController, System.ServiceProcess.ServiceController, S...
The result is really more apparent when you use the Format-Table cmdlet to add the PsIPHostEntry property of the remote results to the table. Note that Format-Table runs on the local computer. You cannot format on the remote computer, at least not in Windows PowerShell 2.0 CTP.
invoke-expression -comp server01, server02 -comm 'get-service | group-object status' | format-table count, name, group, psiphostentry
PSIPHostEntry Count Name Group
------------- ----- ---- -----
server01 62 Running {System.ServiceProcess.Ser...
server01 47 Stopped {System.ServiceProcess.Ser...
server02 80 Running {System.ServiceProcess.Ser...
server02 51 Stopped {System.ServiceProcess.Ser...
Once you learn the rules, you can run very complex commands on remote computers without error. This command gets error events that were recorded in the System log on the Server01 remote computer today. Just remember to enclose the command in single quotes.
invoke-expression -computer Server01 -command 'get-eventlog -log system |
where {($_.Type -eq "Error") -and ($_.TimeWritten.date -eq ([datetime]::now.date)) `
-and ($_.message -like "*failed*")}'
The best way to learn how to use Invoke-Expression is to play with it. Try running your favorite commands on a remote computer. If you get unexpected results, consider changing the quotation marks.
Oh, and did I mention my other favorite quote:
"One of the symptoms of an approaching nervous breakdown is the belief that one's work is terribly important." -- Bertrand Russell
Hmmm. Might be time to take a break.
For More Information
To read about remoting and quoting, see the following Windows PowerShell 2.0 CTP help files:
about_remoting
about_runspace
invoke-expression
about_quoting_rules
For information about automatic variables, like the $home variable, see:
- about_automatic_variables