Summary: Learn how to use Windows PowerShell to import and to export command history and avoid writing scripts.
Hey, Scripting Guy! I have worked with other systems where I was able to keep track of the commands that I typed into the console. The neat thing about that was that I could export my commands, and import them into another console and replay my command history. This was a great way to perform repetitive configuration tasks without having to write a script. Is this something that can be accomplished with Windows PowerShell?
— SW
Hello SW, Microsoft Scripting Guy Ed Wilson here. Windows PowerShell has great command history features. By learning how to use the command history features of Windows PowerShell, many repetitive tasks can be automated.
Portions of today’s Hey, Scripting Guy! Blog post are adapted from material in my Windows PowerShell 2.0 Best Practices book, published by Microsoft Press in December 2009.
Much administrative work with Windows PowerShell consists of typing a series of commands at the console. Whether it is editing the registry, stopping various processes and services, the configuration work has to be replicated to several different servers to make sure a consistent operating environment. In the past such duplication of effort would require creating scripts. If the commands to be duplicated are a series of commands typed at the console, you can use the command history mechanism to replace the need for a script. To do this, use the Get-History cmdlet and export the commands to an xml file as seen here.
Get-History | Export-Clixml -Path C:\fso\history.xml
The result is an XML file that represents all the commands which have been typed at the console. The resulting xml file is seen in the following figure.
After you have created a command history xml file, you can import the commands from the xml file by using the Import-Clixml cmdlet. You pipeline the results of the Import-Clixml cmdlet to the Add-History cmdlet to add the commands back to the command history. The trick is to use the –passthru switch so that the commands will go to both the Add-History cmdlet and to the ForEach-Object cmdlet as well. In the Foreach-Object cmdlet you use the Invoke-History cmdlet to run each command that is in the history. The commands are shown here, as are the results of running them.
PS C:\> Import-Clixml -Path C:\fso\history.xml | Add-History -Passthru |
>> ForEach-Object { Invoke-History }
>>
if(!(test-path -path c:\fso4)) { new-item c:\fso4 -ItemType directory }
Directory: C:\
Mode LastWriteTime Length Name
—- ————- —— —-
d—- 1/9/2009 12:33 AM fso4
Get-Command >> C:\fso4\commands.txt
notepad C:\fso4\commands.txt
This technique will work remotely by using the Invoke-Command cmdlet. The thing to keep in mind is that the path statement is relative to the computer that is the target, not the computer that is executing the command. If you do not keep this in mind, then an error such as the one seen in the following figure appears.
If you copy the file to the target machine first, and adjust your command line, the import and execute history technique works well. The good thing about Windows PowerShell, is that you can use an UNC path with the Copy-Item cmdlet. It is this feature that actually makes the technique worthwhile because it enables you to easily move a file to a remote computer. This is shown here.
PS C:\> Copy-Item C:\fso\history.xml \\berlin\c$\fso
PS C:\> Import-Clixml -Path C:\fso\history.xml | Add-History -Passthru | ForEach
-Object { Invoke-History }
if(!(test-path -path c:\fso4)) { new-item c:\fso4 -ItemType directory }
Directory: C:\
Mode LastWriteTime Length Name
—- ————- —— —-
d—- 1/9/2009 12:40 AM fso4
Get-Command >> C:\fso4\commands.txt
notepad C:\fso4\commands.txt
Fan out commands are commands that are launched from a central computer and run against a number of remote computers. One way to perform this technique is to use the Invoke-Command cmdlet as seen here.
PS C:\> Invoke-Command -Computer berlin,vista -Script `
>> {“$env:computername $(get-date)” }
>>
VISTA 01/09/2009 08:31:42
BERLIN 01/09/2009 08:31:47
You can use fan out commands by specifying an array of computer names for the computername parameter for many of the cmdlets. The problem with this approach is the results are almost useless. An example may show the issue that is involved. In the command below, the Get-Service cmdlet is used to obtain service configuration information from two computers. The first is a computer named Vista, and the second one is a server named Berlin. As you can see from the partial output, copied here, the results of the command are merged, and there is no column that shows the computer name that the result is associated with. The results are interesting because you can quickly look at the service name between two computers and easily see divergent configurations. The fan out command and a truncated result set is seen here.
PS C:\> Get-Service -ComputerName Vista, Berlin
Status Name DisplayName
—— —- ———–
Running 1-vmsrvc Virtual Machine Additions Services …
Running 1-vmsrvc Virtual Machine Additions Services …
Running AeLookupSvc Application Experience
Stopped AeLookupSvc Application Experience
Stopped ALG Application Layer Gateway Service
Stopped ALG Application Layer Gateway Service
Stopped Appinfo Application Information
Stopped Appinfo Application Information
Stopped AppMgmt Application Management
Stopped AppMgmt Application Management
Stopped AudioEndpointBu… Windows Audio Endpoint Builder
Stopped AudioEndpointBu… Windows Audio Endpoint Builder
Stopped Audiosrv Windows Audio
Stopped Audiosrv Windows Audio
Running BFE Base Filtering Engine
Running BFE Base Filtering Engine
Running BITS Background Intelligent Transfer Ser…
Stopped BITS Background Intelligent Transfer Ser…
Stopped Browser Computer Browser
Running Browser Computer Browser
>>> Results trimmed >>>
You can see from the results of the Get-Service command that the AeLookupSvc is running on the first computer, and stopped on the second computer. To check this, it is simple to use the Get-Service cmdlet to connect to each of the computers, and to check the status of the service.
PS C:\> Get-Service -Name AeLookupSvc -computer vista
Status Name DisplayName
—— —- ———–
Stopped AeLookupSvc Application Experience
PS C:\> Get-Service -Name AeLookupSvc -computer Berlin
Status Name DisplayName
—— —- ———–
Running AeLookupSvc Application Experience
You might think that the first instance of the service name belongs to the computer that is listed first. As you can see, the AeLookupSvc is running on Berlin, but is stopped on Vista. This is the same order that is seen in the original output. However, the Vista computer was listed first in the fan out command. Perhaps this means that the second computer results are listed first, and the first computer results are listed second—a Last In First Out (LIFO) operation. Before assuming such to be the case, you have to check another service. In the output from the original fan out command, the Bits service was listed first as running, and second as stopped. To see the status of the Bits service on Berlin and on Vista, you can use the following two commands.
PS C:\> Get-Service -Name Bits -computer berlin
Status Name DisplayName
—— —- ———–
Stopped BITS Background Intelligent Transfer Ser…
PS C:\> Get-Service -Name Bits -computer Vista
Status Name DisplayName
—— —- ———–
Running BITS Background Intelligent Transfer Ser…
You can see that the Bits service is stopped on Berlin, and is running on Vista. The results of using Get-Service as a fan out command by supplying an array of computer names to the computername parameter brings back interesting results, but results that are meaningless when it comes to checking the exact status of a service on a remote computer. As a best practice, you should pipeline the results of the fan out command to a Format-Table cmdlet and choose the machineName property. The value of displayName property is the same value that is seen in the Services MMC in the name column. The command and a truncated output are shown here.
PS C:\> Get-Service -ComputerName berlin,vista | format-table name, status, mach
inename, displayName -AutoSize
Name Status MachineName DisplayName
—- —— ———– ———–
1-vmsrvc Running vista Virtual Machine Additions…
1-vmsrvc Running berlin Virtual Machine Additions…
AeLookupSvc Running berlin Application Experience
AeLookupSvc Stopped vista Application Experience
ALG Stopped berlin Application Layer Gateway…
ALG Stopped vista Application Layer Gateway…
Appinfo Stopped berlin Application Information
Appinfo Stopped vista Application Information
AppMgmt Stopped vista Application Management
AppMgmt Stopped berlin Application Management
AudioEndpointBuilder Stopped berlin Windows Audio Endpoint Bu…
AudioEndpointBuilder Stopped vista Windows Audio Endpoint Bu…
Audiosrv Stopped berlin Windows Audio
Audiosrv Stopped vista Windows Audio
BFE Running vista Base Filtering Engine
BFE Running berlin Base Filtering Engine
BITS Stopped berlin Background Intelligent Tr…
BITS Running vista Background Intelligent Tr…
Browser Running vista Computer Browser
Browser Stopped berlin Computer Browser
Because the value of the displayName property is frequently quite long, it does not always easily fit within the confines of an 80 column display. If you have it selected early in the order of the properties to be selected by the Format-Table cmdlet, you will probably end up with several columns not being displayed. This is shown here.
PS C:\> Get-Service -ComputerName berlin,vista | format-table name, displayname,
status, machinename -AutoSize
WARNING: 2 columns do not fit into the display and were removed.
Name DisplayName
—- ———–
1-vmsrvc Virtual Machine Additions Services Application
1-vmsrvc Virtual Machine Additions Services Application
AeLookupSvc Application Experience
AeLookupSvc Application Experience
As you can see, this defeats the purpose of choosing the machineName property in the first place, if it is left off because it does not fit on the display. To correct this potential problem, it is a best practice to always select the property that uses the longest values to be displayed for the last position in the command. In this way, you allow Windows PowerShell to truncate the property value rather than filling up the screen with information, you could easily infer from a truncated display.
The other solution to the problem of the shrinking display output would be not to use the autosize parameter of the Format-Table cmdlet. You could use the wrap parameter instead. When this parameter is used, single line entries are allowed to wrap and to form multiple lines. Depending on the information that you are looking for, this output can be either helpful, or annoying. Here is an example of using the wrap parameter.
PS C:\> Get-Service -ComputerName berlin,vista | format-table name, displayname,
status, machinename -Wrap
Name DisplayName Status MachineName
—- ———– —— ———–
1-vmsrvc Virtual Machine Add Running vista
itions Services App
lication
1-vmsrvc Virtual Machine Add Running berlin
itions Services App
lication
AeLookupSvc Application Experie Running berlin
nce
AeLookupSvc Application Experie Stopped vista
nce
At this point in the discussion, you may be thinking that you can really resolve this problem of the truncated display output by using both the autosize and the wrap parameters. This would enable the output to maximize the display real estate (the function of autosize) and also to allow for multiline wrapping (the function of wrap). This never works, but does not generate an error. Windows PowerShell will give priority to the autosize parameter and ignore the wrap parameter. It does not matter in which order the two parameters are typed; the wrap parameter will be ignored.
SW, that is all there is to importing and exporting Windows PowerShell command history. To Script or Not to Script week will continue tomorrow when I will talk about how to work with Active Directory Domain Services.
I invite you to follow me on Twitter or Facebook. If you have any questions, send email to me at scripter@microsoft.com or post them on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments