With PowerShell, you can still use your favorite .vbs, .js, .bat, and .cmd files
|Migrating to the Windows PowerShell command line interface (CLI) shell and scripting language can be a simple transition.|
|In Windows PowerShell, you can run your preexisting batch (.cmd and .bat) and script (.vbs and .js) files.|
|You can also incorporate these files’ output into the Windows PowerShell pipeline.|
|Alternatively, you can integrate batch commands and script statements directly into your PowerShell code.|
You’ve read the articles. Tested the product. Created the scripts. And now you’re convinced. Windows PowerShell promises to be everything you’ve heard it would be. But you were also convinced when you plunged into VBScript—you were even convinced when you wrote all those batch files. So what do you do with your old script and batch files if you switch to PowerShell? You have two choices: Rewrite everything in the PowerShell scripting language, or leverage the power in PowerShell and go right on using that original code.
In PowerShell, you can run batch (.cmd and .bat) and script (.vbs and .js) files, incorporate the files’ output into the PowerShell pipeline, or integrate batch commands and script statements directly into your PowerShell code. This article demonstrates how to perform all these actions so you can make the most of your previous efforts, while allowing you to streamline your migration to PowerShell.
Running Batch Commands in PowerShell
In many cases, you can run a batch file simply by calling the file in the PowerShell window. For example, suppose you have a batch file named FileInfo.cmd that contains the following commands:
dir c:\scripts\text\*.txt /o-s
The batch file simply retrieves a list of text files and orders them by size. To run the file in PowerShell, type the path and filename at the PowerShell command prompt and press Enter. For instance, if the file is saved to the C:\Scripts folder, type
Figure 1 shows the type of results you’d receive from this command—exactly what you’d receive if you ran the batch file from within the command prompt shell or entered the dir command at the shell’s command prompt. (Note that for many of the examples in this article, the code will retrieve data about a set of test text files saved to the C:\Scripts\Text folder.)
As this example demonstrates, you can run batch files quite easily in PowerShell. In addition, the files can contain commands that are far more complex than the one in this example. (As you’ll see later in the article, there are some limitations on the batch file commands you can run in this way.) PowerShell lets you do a lot more than simply run batch files—you can incorporate the results returned by a file into the PowerShell pipeline. The PowerShell file FileInfo1.ps1, which Listing 1 shows, illustrates this capability. Note that you must set the correct execution policy to run script files in PowerShell. For more information about the execution policy, go to the PowerShell command prompt and type
The first statement in Listing 1 calls the batch file. This command is the same one used to run FileInfo.cmd in the previous example. The results from the command are then piped (indicated by the vertical pipe) to the where-object cmdlet, the next command in the pipeline. The where-object cmdlet filters the results so that they include only the lines that contain "*current*.txt". Notice the use of the $_ symbol in the where-object cmdlet. This symbol is used to reference the current input object in the pipeline.
From the where-object cmdlet, the filtered results are then piped to the sort-object cmdlet, which sorts the results in descending order by filename. Finally, the sorted results are piped to the foreach-object cmdlet, which outputs the filenames in uppercase. (The code uses the substring method to extract the filenames from the returned results.)
When you run FileInfo1.ps1 in PowerShell, the batch file runs just like it would if you ran the file at the PowerShell command prompt. The FileInfo1.ps1 script then runs the rest of the PowerShell statements. To run a PowerShell file, you simply enter a command such as the following at the PowerShell command prompt:
Note that you must include the full path name when you call the script file. FileInfo1.ps1 will return the results from the final pipeline output, similar to what Figure 2 shows.
When incorporating the results from a batch file into your pipeline, you can also use a variable to store those results. For example, the PowerShell code in the file FileInfo2.ps1, which Listing 2 shows, adds the data retrieved by the batch file to the $fileInfo variable. Note that the results of running the batch file are assigned to the variable, not the command statement itself. In other words, the list of text files is added to the pipeline, not the string c:\scripts\fileinfo.cmd.
The variable is then used to pipe the data to the where-object cmdlet, as in the preceding example. This will provide the same results as those in Figure 2.
As flexible as PowerShell is in terms of running batch files, it does have some limitations. Specifically, you can’t run a batch file that contains commands that set variables. For example, the file FileNames.cmd contains the following commands:
for %t in (c:\scripts\text\*.txt) do @echo %t
The second command uses a for loop to retrieve the path and filenames of all text files in the C:\Scripts\Text folder. If you try to run this file from the PowerShell command prompt, you’ll receive the following error:
\scripts\text\*.txt) was unexpected at this time
Although the error message isn’t particularly useful, it does point to the problem of trying to run a batch file that includes commands that set variables.
Fortunately, PowerShell provides a workaround to this issue. From the PowerShell command prompt, you can call the executable file cmd.exe directly and pass the batch command as an argument. For example, the following command, like FileNames.cmd in the previous example, retrieves the text files from the C:\Scripts\Text folder:
cmd /c "for %t in (c:\scripts\text\*.txt) do @echo %t"
This statement, which calls the Cmd program (cmd.exe), takes the /c switch, followed by the actual batch command within quotes. The switch terminates cmd.exe after it runs the batch command. The statement returns results similar to those in Figure 3.
You can also use this approach to run commands that are saved to a batch file. The key is to retrieve the contents of the batch file, then call cmd.exe to run those commands. For example, the file FileNames1.ps1, which Listing 3 shows, uses a get-content cmdlet to retrieve the contents of FileNames.cmd. The script then assigns the file content to the $cmd variable. After the content is saved to the variable, the script uses cmd /c to run the contents of $cmd.
When retrieving the contents of a batch file in this manner, you must consider whether the file includes an @echo off command, as this one does. If you try to run the file FileNames1.ps1 as is, you’ll receive the following error message:
off" "for %t in (c:\scripts\text\*.txt) do @echo %t
Once again, this message isn’t particularly useful, but it does point to the problem. The workaround is to add a PowerShell statement to remove the @echo off command, as the file FileNames2.ps1 shows in Listing 4. As you can see, the second statement pipes the contents of the $cmd variable to a foreach-object cmdlet. The cmdlet uses the -replace operator to replace the @echo off command with an empty string. This new value is then assigned back to the $cmd variable. If you then run the file, you’ll receive results similar to those in Figure 3.
You can also use the results generated by using cmd /c in the PowerShell pipeline. For example, the file FileNames3.ps1, which Listing 5 shows, saves the output from cmd /c to the $files variable. The contents in $files is then piped to a sort-object cmdlet, which sorts the contents in descending order. Next, the information is piped to a foreach-object cmdlet, which filters and formats the text. As a result, the FileNames3.ps1 script will return the same results as those in Figure 2.
As this and the other examples demonstrate, PowerShell lets you leverage all your old batch files and incorporate their output into your PowerShell pipeline, a process that will make your transition to PowerShell far less painful than if you were creating all your code from scratch. Now let’s take a look at how to use your VBScript and JScript files so you can leverage those efforts as well.
Running VBScript and JScript in PowerShell
The process of incorporating VBScript or JScript into the PowerShell environment is, for the most part, the same for either scripting language. Your first option is simply to run the script from the PowerShell command prompt. For example, suppose that you have a VBScript file named ProcessorInfo.vbs (shown in Listing 6). The script uses Windows Management Instrumentation (WMI) to retrieve processor-related information for the local computer.
This basic script creates a WMI object, runs the execQuery method on that object, then formats and prints the data retrieved by the query. To run the script in PowerShell, you call the CScript.exe program and specify the path and filename of the script, as the following example shows:
This command will run the script and display the contents in the command window. The script will return information similar to that in Figure 4. You can also use CScript.exe to run a JScript file. Simply call the file by its fully qualified name, as in the preceding example.
Although using this approach to run a script might be useful, you could just as easily run the script from a command prompt window. However, with PowerShell, you can also incorporate the output of the script into your pipeline. Listing 7, which shows the contents of the file ProcessorInfo1.ps1, is an example of how to do so. The PowerShell code in this file first uses the CScript command to run the file ProcessorInfo.vbs. The results from this command are saved to the $vbs variable. Once again, note that the results from running the VBScript code are stored in the $vbs variable, not the CScript command itself.
The PowerShell code then pipes the contents in $vbs to the where-object cmdlet. The cmdlet filters out nonessential data, which in this case is anything that contains the text “Microsoft.” (This action isn’t to slight Microsoft or to make any sort of statement, but simply so that the final display includes only processor information.) Filtering the content eliminates the first two lines in Figure 4.
The filtered data is then piped to a foreach-object cmdlet, which formats and prints the returned data. When you run the file ProcessorInfo1.ps1, your results will be similar to Figure 5.
In addition to using the CScript command to run a script file, you can incorporate VBScript and JScript functions directly in your PowerShell code. To understand how this works, see the file ProcessorInfo2.ps1, which Listing 8 shows.
As Callout A shows, the file first defines the VBScript function named formatInfo. The function takes the procInfo parameter, which lets you pass a value into the function when you call it. The function trims leading and trailing spaces from the string and changes the registered symbol from (R) to (r).
A couple of things about this function are worth pointing out. First, as you can see, the function is very basic. The function is used simply to demonstrate how to incorporate a VBScript function into PowerShell. However, you can define as complex a function as necessary. In fact, you can even define multiple functions within the same script block (i.e., the block enclosed in single quotes).
Second, you can use built-in PowerShell functions to perform the replace and trim operations shown within the VBScript function. Again, this VBScript function is used merely to demonstrate how to incorporate VBScript into PowerShell.
Returning to Callout A in Listing 8, you’ll notice that the VBScript function definition is assigned to the $vbs variable. You can then use the variable later in your PowerShell code to retrieve the definition. After you’ve created the variable, you’re ready to move to the next step, which is to create the objects necessary to incorporate the VBScript code into your PowerShell code.
The next step then, which Callout B shows, is to create a COM scriptControl object. To create the object, use the new-object cmdlet along with the -com parameter and scriptControl value. Then assign the object to a variable, which in this case is $sc. After you’ve defined the variable, you can use it to access the object’s properties and methods.
The first property that you need to set is language, which you should set to VBScript. Next, use the $false system variable to set the allowUI property to false. Setting this property to false prevents the script engine from displaying message boxes. After you set the allowUI property, use the addCode method to add the VBScript function definition to the scriptControl object. Because you assigned the definition to the $vbs variable, you can simply use that variable to pass the definition to the method.
The final step in setting up your code to handle VBScript is to use the codeObject property to create a code object that you’ll use to access the VBScript function. The code object exposes the VBScript function as a method to that object. As a result, you can use the code object to access the function later in your PowerShell code.
To better understand this approach, take a look at Callout C. This section of code creates a WMI object that is used to access processor information on the local computer. To create a WMI object, you use the get-wmiobject cmdlet and specify the class, namespace, and computer. (The period after the -computername switch refers to the local computer.) The WMI object is then assigned to the $wmiObject variable, which you can use to access the object’s properties. The properties contain the actual processor information.
The PowerShell code then uses a foreach statement to iterate through the WMI collection of processor information. Each statement in the foreach script block retrieves data about one of the processor properties. For example, the first line retrieves manufacturer information by using the $item variable along with the property name ($item.Manufacturer). However, notice that the property name is enclosed in the formatInfo function. The statement uses the $co code object to call the formatInfo method, which is, in fact, the VBScript function. The function then formats the string as though it were a PowerShell function.
You can use the function as often as necessary within your code. The file ProcessorInfo2.ps1 uses the function to format each property as the WMI object returns the property. If you run the PowerShell file, you’ll receive the same results (similar to those in Figure 5) as the results generated by ProcessorInfo1.ps1 in Listing 7.
You can also use your existing JScript functions in PowerShell. The file ProcessorInfo3.ps1, which Listing 9 shows, illustrates the same function (i.e., formatInfo) written in JScript. Notice that the language property for the scriptControl object is now set to JScript; however, the rest of the PowerShell code is essentially the same as it was for the VBScript function. (Not surprisingly, the primary difference is in the functions themselves.) The file ProcessorInfo3.ps1 will return the same results as ProcessorInfo1.ps1 (shown in Figure 5) and ProcessorInfo2.ps1.
Now that you’ve seen how to incorporate VBScript and JScript functions into your PowerShell code, there’s one more example worth looking at. The file ProcessorInfo4.ps1, which Listing 10 shows, contains many of the same elements that you saw in the previous two examples. However, ProcessorInfo4.ps1 incorporates both a VBScript function and a JScript function into its code.
Callout A defines the scriptControl object and code object for the VBScript function. One difference with this example from the previous two examples is that the function definition is included directly in the addCode method, rather than being called through a variable. As you can see, you can use either approach to assign your VBScript script to the scriptControl object. Callout B, which defines the scriptControl object for the JScript script, also calls the function from within the addCode method.
Now take a look at Callout C. As before, the PowerShell code creates a WMI object to retrieve the processor information, then uses a foreach statement to iterate through that information. However, notice that each statement within the foreach script block now uses both the VBScript function and the JScript function, with the JScript function imbedded in the VBScript function. The JScript function trims the spaces from either end of the string, then the VBScript function replaces the registered symbols. When you run the file ProcessorInfo4.ps1, you’ll receive the same results you received when running the other examples that used VBScript and JScript functions.
An Easy Transition
As the file ProcessorInfo4.ps1 shows, you can mix and match VBScript and JScript functions within your PowerShell code in the same way you use PowerShell functions. In fact, with PowerShell, you can leverage most of your existing batch and script files so that, as you migrate to PowerShell, you’ll be able to make an easy transition to the new environment. However, keep in mind that nearly anything you can do in a batch or script file, you can also do in PowerShell, and often with fewer lines of code. So as you transition to PowerShell, start creating your new script files in PowerShell and keep building up that PowerShell repository—at least until the next generation of technologies comes along.