Making PowerShell's Out-Printer Cmdlet Easier to Use
How to obtain the printer names that Out-Printer needs
December 25, 2007
Executive Summary:
If you read the Windows PowerShell documentation for the Out-Printer cmdlet, you'll learn that to send output to a nondefault printer, you need to specify the printer's name. However, the documentation doesn't tell you how to find that name. Here are several ways you can obtain printer names and make them available to your PowerShell code with no copying and pasting and minimal typing. |
Windows PowerShell lets you send output to a printer with its Out-Printer cmdlet. However, the printer names that Out-Printer uses to identify printers aren’t immediately obvious and aren't directly available to you in PowerShell. So, I'll show you how to obtain the printer names you need to send output to both default and nondefault printers. If you'd like to try the code while you're reading this article, you'll find many of the inline commands, along with the code in the listings, in the Code_for_Out-Printer_Article.txt file. You can download this file by clicking the Download the Code Here button in the upper left corner of this page.
Using Out-Printer with the Default Printer
Using the Out-Printer cmdlet to send output to the default printer is easy. For example, if you want to print your own personal cmdlet reference on your default printer, you simply run the command
Get-Command -Syntax | Out-Printer
You can make this command even shorter if you take advantage of PowerShell’s aliases and parameter-name completion feature. In this case, you just need to type
gcm -s | lp
Using Out-Printer with Nondefault Printers
Using Out-Printer to send output to a nondefault printer can be a frustrating experience. Here's why. If you run the command
Get-Help Out-Printer -full
PowerShell sends to your default printer the full version of its Help documentation. (Note that this is a long document.) If you read the documentation for Out-Printer, you'll learn that to send output to a nondefault printer, you need to specify the printer's name, following the syntax
Out-Printer -Name
where is the name of the nondefault printer. The documentation even lets you know that the Name parameter is optional so that you can shorten the command to
Out-Printer
What the documentation doesn't tell you is how to find the printer name. All it says about the Name parameter is "…specifies the alternate printer." There is a minor hint in the examples, which show a Universal Naming Convention (UNC) pathname for a server-based printer. The name for a printer share is usually the same as the display name shown in the Windows Printers folder. As it turns out, the display name is exactly what Out-Printer uses to identify printers. However, this presents us with another problem: Even if you look up the precise printer names, they're often long. Typing something like
Out-Printer "HP Color LaserJet Family Driver PCL5 (Copy 1)"
is not only slow and laborious but also prone to typos. (Note that although this code appears on several lines here, you would type it on one line in the PowerShell window. The same holds true for the other multiline commands in this article.)
If we can get PowerShell to find the printer names for us, we can use those names without having to type them. The first step in getting the names is to use Windows Management Instrumentation's (WMI's) Win32_Printer class to list the local printers. To do so, you can run the command
Get-WmiObject Win32_Printer
or
gwmi Win32_Printer
if you want to use aliases. The Name property of each Win32_Printer object returned from this command contains a printer's display name. You can extract the display names by piping the returned Win32_Printer objects to the ForEach-Object cmdlet with the code
Get-WmiObject Win32_Printer | ForEach-Object {$_.Name}
This command lists the printer names you can use with Out-Printer. Figure 1 shows sample output.
Note that the Get-WmiObject has a Property parameter that you could use to tell WMI to return only the printer names. However, when you run the command
Get-WmiObject Win32_Printer -Property Name
the names are embedded in Win32_Printer objects because PowerShell is getting class instances back from WMI. Since you still need to use ForEach-Object to extract the printer names, you don't gain anything by using Get-WmiObject's Property parameter. Generally, the Property parameter is useful only when you have a lot of data coming back from WMI and you want to reduce the amount of data being returned.
As I mentioned previously, Figure 1 shows sample output from using Get-WmiObject and ForEach-Object to obtain printer names. You could copy one of these printer names to your clipboard, then paste it as the Name parameter value of the Out-Printer cmdlet. However, copying and pasting the printer name probably isn't much faster than typing it. There are several ways you can avoid the hassle of copying and pasting printer names. You can store printer names in an array, in a sorted list, or in variables.
Storing Printer Names in an Array
A simple way to get the printer names is to use the Get-Printer function in Listing 1. If you want the printer names automatically available to you in the $lp global variable, you can insert the Get-Printer function into a PowerShell profile script and follow it with the line
$lp = Get-Printer
If you aren't familiar with PowerShell's profile scripts, see the sidebar "What You Need to Know to Start Using PowerShell's Personal Profile Scripts."
In the $lp = Get-Printer command, I named the variable $lp because this name is not only compact but also appropriate. In UNIX environments, you typically use lpx (where x is a number) to name printer ports. The acronym lp is short for line printer, which is why UNIX uses the name lp for the command that prints a file. It's no coincidence that PowerShell uses lp as an alias for Out-Printer; the alias is used because it's familiar to veteran UNIX users, a group that includes many members of the PowerShell development team.
The Get-Printer function makes finding printer names a breeze. You just type
$lp
to get a list of printer names like that in Figure 1.
Because $lp is an array, you can address each item by its index. The first item's index is $lp[0], the second item's index is $lp[1], and so on. So, for example, instead of having to type
Out-Printer "HP Color LaserJet Family Driver PCL5 (Copy 1)"
you can simply type
Out-Printer $lp[0]
You might be wondering why I created the Get-Printer function when I could have just used a one-liner such as
$lp = gwmi Win32_Printer | ForEach-Object{$_.Name}}
Using a function simplifies things if you need to add or remove a printer while PowerShell is running. To refresh the list of printers, all you need to do is reset $lp by entering the command
$lp = Get-Printer
instead of typing the entire command
$lp = gwmi Win32_Printer | ForEach-Object {$_.Name}
Storing Printer Names in a Sorted List
One disadvantage to using the Get-Printer function in Listing 1 is that it doesn't provide any visual cues about the index to use for a particular printer. For example, suppose you want to use Out-Printer to send output to the Microsoft Office Document Image Writer in the list in Figure 1. You'd have to count down from the top of the list, beginning with 0, to determine that the index is $lp[3].
If you don't want to manually figure out what index to plug in, you can use the version of Get-Printer that Listing 2 shows. You use this version of the Get-Printer function the same way you use the original version. Then when you enter
$lp
at a command prompt, you'll see output like that in Figure 2. Now it's easier to see that the index for the Document Image Writer is $lp[3].
If you write your own PowerShell scripts, you might find some of the techniques I used in the function in Listing 2 useful, so I'll explain how that code works. In callout A, I create a new variable as a hashtable, which is commonly called a hash for short. Hashes are so handy that PowerShell provides the @{} notation as a quick way to initialize a new hash. A hash is roughly equivalent to the Microsoft Scripting Runtime Library's Dictionary object in the Windows Script (WS) core. With a hash, you can create an arbitrary list of named items.
In callout B in Listing 2, I retrieve the printer names from WMI. The snippet
$p[$p.Count] = $_.Name
creates a new hash entry for each printer name, using a number for the item key and the printer name as the item value. Let's look at this technique more closely. If you have a hash named $p and want to add the key named password with the value of L3tM31n, you can use the code
$p["password"] = "L3tM31n"
In this case, numbers are used for the keys. Because the $p hash starts out with no entries in it, $p.Count is 0 when the first printer name comes in. The first printer that WMI returns is the one named HP Color LaserJet Family Driver PCL5 (Copy 1). For the first printer, then, the code snippet evaluates to
$p[0] = "HP Color LaserJet Family Driver PCL5 (Copy 1)"
Because $p.Count increases each time a new entry is added to the hash, you get a unique number for each key.
At this point, I could have returned the hash as output, but the output would have looked a little odd. The entries would have been in the order in which they were added, with the most recently added entry appearing first (entry 5, entry 4, entry 3, and so on). Sorting the entries by the key numbers (e.g., entry 0, entry 1, entry 2, and so on) is more user-friendly. So, I used the line of code in callout C in Listing 2 to sort the printer hash.
In PowerShell, you can convert one object type to another (called casting) by specifying the new type name. A hash can be converted into most of the collection types used by the Microsoft .NET Framework. One of those types, the System.Collections.SortedList object, automatically sorts name-value pairs by name. By casting the hash into a SortedList object, you instantly get an ordered collection of printer names, as shown in Figure 2. What you get back is not a hash. If you check the type name by using the command
Get-Printer | Get-Member
you'll see System.Collections.SortedList at the beginning of the output.
Storing Printer Names in Variables
The Set-PrinterVariable function in Listing 3 automatically assigns printer names to variables (e.g., $lp0, $lp1), which you use with Out-Printer. You simply insert Set-PrinterVariable into your profile script and execute the function (possibly from within that profile script) using the line
Set-PrinterVariable
You don't need to set any variables. If your printers change, you just run the Set-PrinterVariable function again. All the variable names begin with lp, so you can use the Get-Variable cmdlet to view the printer names with the code
Get-Variable lp*
As the sample output in Figure 3 shows, the result is a list of the printer names and the variables used to store them. Remember how I showed you how to print the cmdlet reference on the default printer at the beginning of this article? If you want to print this reference on the Adobe PDF printer, you'd run the command
Get-Command -Syntax | Out-Printer $lp5
As Figure 3 shows, the output from Get-Variable isn't sorted. If you want the list sorted, you can use the Sort-Object cmdlet. In case you aren't familiar with Sort-Object, this cmdlet takes objects as input and sorts them according to whatever property you specify. The output from the Get-Variable lp* command is a bunch of variable objects with the displayed properties of Name (the variable's name) and Value (the printer's name), so you can sort the list by either property. To sort by the variable's name, use
Get-Variable lp* | Sort-Object Name
To sort by the printer's name, use
Get-Variable lp* | Sort-Object Value
If this seems like a lot of typing, you can use the standard aliases to shorten the commands to
gv lp* | sort name
and
gv lp* | sort value
respectively.
Like the Get-Printer function in Listing 2, the Set-PrinterVariable function in Listing 3 demonstrates some helpful scripting techniques. As callout A in Listing 3 shows, Set-PrinterVariable begins by removing any existing variable names that point to printers. To do so, it uses the Get-Variable cmdlet to retrieve a list of all variables in PowerShell's global scope whose names begin with lp. To reduce the chance of accidentally deleting variables not used to refer to printers, this list is filtered with PowerShell's match operator. The pattern lp[0-9]+$ ensures that the names must be followed by numbers. A variable named lp903 would be deleted, but a variable named lp or lpointer wouldn't be touched.
In callout B in Listing 3, the Set-PrinterVariable function uses the Get-WmiObject cmdlet to retrieve the printer names from WMI, similar to the way in which the Get-Printer function retrieves those names in callout B in Listing 2. However, Set-PrinterVariable assigns its printer names to variables by initially setting the $i variable to a value of -1. Each time Set-PrinterVariable gets a printer name from WMI, it increments $i by 1, which means that $i increases to 0 for the first printer name. If the first printer is named Fax, the snippet
Set-Variable -Name "lp$i" -Value $_.Name -Scope Global
evaluates to
Set-Variable -Name "lp0" -Value Fax -Scope Global
which creates a global variable named $lp0 with the value of Fax. When you specify the variable name, you don't preface it with a dollar sign ($). If you were to use $lp0, PowerShell would try to name the variable with the value contained in the $lp0 variable, which doesn't exist yet. Setting a global scope is important because if you set the variables in the default local scope, they would be gone when you exit the function. Thus, you couldn't use those variables with Out-Printer at the command line.
Deciding Which Technique To Use
We've looked at several ways to expose printer names for use with the Out-Printer cmdlet. What’s the best approach to use? The answer depends on what works for you. Let's ignore the original version of the Get-Printer function in Listing 1; it's useful for explanation but offers negligible benefits over the version of the Get-Printer function in Listing 2.
If you like the idea of having only one variable assigned to printer names like I do, the best approach is to use the code in Listing 2. With this code, all you need to type for a printer name is $lp[x].
If you want clean, UNIX-like short names and don't mind having the printer definitions taking over all the variable names that begin with lp, the Set-PrinterVariable function in Listing 3 is the way to go.
If you predominantly use the default printer, either technique may be overkill. In my case, I mainly use the default printer, but I use the Microsoft XPS Document Writer on occasion. Thus, I don't use the Get-Printer or Set-PrinterVariable function. Instead, I just put the line
$lpxps = "Microsoft XPS Document Writer"
in my profile script. I then use the command
Get-Command -Syntax | Out-Printer $lpxps
when I want to print to the XPS Document Writer.
Another alternative you might prefer is having the Get-Printer and Set-PrinterVariable functions available as scripts; for convenience, I've incorporated them into Get-Printer.ps1 and Set-PrinterVariable.ps1, respectively. After you download these scripts by clicking the Download the Code Here button, you can use them like any other PowerShell script.
In an ideal world, we would have a PowerShell printer provider that gives us a PSDrive with an obvious name like Printer or PRN. Although the displayed names would no doubt be the long names shown in Windows Explorer, tab completion would work for the names. And as a bonus, we could probably see when a printer was offline. A full-fledged WMI provider would give us the same benefits. It's possible we'll see a provider in PowerShell 2.0 or in a community-extension project. Until then, the functions and scripts I've outlined here can help you obtain your nondefault printer names.
About the Author
You May Also Like