Hey, Scripting Guy! Question

Hey, Scripting Guy! I am tasked with the problem of documenting printer use on our client workstations. I must be able to determine whether someone prints directly to a printer, or if they are printing through our print servers. This is important because we have purchased a print monitoring software package that is used to back-charge the departments for their printer usage. This software runs on the printer servers. If someone prints directly to a network printer through the IP address, the print job does not go through the print server, and therefore we lose the billing. I would love to spend a week going around to each client in our office building, chatting with the users, and looking at their printer settings. Unfortunately, the boss nixed that idea. I told him the revenue we regained would pay my salary for the week, but he was not swayed. He suggested I contact you and ask for help with script ideas.

- LB

SpacerHey, Scripting Guy! Answer

Hi LB,

What a refreshing e-mail message. It sounds as if you are serious that you would enjoy a week spent talking to users, you would be surprised how many e-mail messages we receive from IT pros who seek to avoid users. Even your boss seems to be a wise and wonderful person. Last week was a rather eventful week at Tech·Ed. Ed gave a Windows PowerShell presentation on Friday. Both Ed and Craig talked to thousands of adoring fans and were hounded by autograph seekers all week (some of this sentence uses exaggeration). Needless to say, we are beat. So we are catching up on e-mail, and Ed is mellowing out sipping an excellent cup of Darjeeling tea perfectly brewed in my little tea pot with freshly drawn spring water. Two spoons of fresh leaves, seasoned with a stick of Ceylon cinnamon. Ed is also listening to Earth, Wind & Fire on his Zune. Life is good.

This week we will examine printing. There are lots of resources related to printing on the TechNet Script Center. There is a collection of Hey, Scripting Guy! articles that lists 24 articles relates to both server-side and client-side printing. We have almost 50 scripts in the printer section of the Script Center Script Repository. There are also almost 50 scripts in the printer section of the Community-Submitted Scripts Center. We have some good technical information about printing in the Scripting Guide. All those resources are in VBScript. To use them in Windows PowerShell, you would have to translate the scripts into PowerShell. The Microsoft Press book, Windows PowerShell Scripting Guide, has a whole chapter devoted to printing.

You can use WMI to get the printer information that you must have. We wrote a script named GetLocalAndNetworkPrinters.ps1 in Windows PowerShell for you. A VBScript that provides a similar capability is developed in the “How Can I List All the Printers on a Remote Computer?” Hey, Scripting Guy! article. The complete GetLocalAndNetworkPrinters.ps1 script is seen here:

$computer = "localhost"Write-Host -foregroundcolor Yellow "Printer report for $computer..."Get-WmiObject -class Win32_Printer -computername $computer|Foreach-Object -Begin {$NetworkPrn=$localPrn = @{"Name"="PortName"} } ` -Process {  if($_.Local)    {       $localPrn += @{ $_.name = $_.portname }    } #end if if(-not $_.Local)   {   $NetworkPrn += @{ $_.name = $_.portname } } #end if} #end foreach-object process# *** Generate Report ***if($localPrn.count -gt 1){ Write-Host -foreground cyan "There are $($localPrn.count -1) Local printers on $computer" $localPrn | Format-Table -auto -Hide -wrap}Else { "No local printers detected on $computer" }if($networkPrn.count -gt 1) {  Write-Host -foregroundcolor green "There are $($networkPrn.count -1) Network printers on $computer "  $networkPrn | Format-Table -auto -Hide -wrap }Else { Write-Host -foregroundcolor red "No network printers detected on $computer"} #end else

Are you still here? Good, let's dig into the GetLocalAndNetworkPrinters.ps1 script and see whether we can determine what makes it tick. The first thing we have to do is create a variable $computer to hold the name of the computer. We set the default value to localhost. This is a better name to use for the local computer than the period (".") that is frequently seen in WMI scripts. This is because, when the report is displayed, a printer report for localhost reads better than a printer report for a "period." We use the Write-Host cmdlet to print the text in yellow:

$computer = "localhost"Write-Host -foregroundcolor Yellow "Printer report for $computer..."

Now we use WMI to obtain the printer information. The Win32_Printer WMI class provides this information. To query from WMI, use the Get-WmiObject cmdlet. More information about WMI can be found in our new WMI Scripting Hub. The resulting management objects are pipelined to the ForEach-Object cmdlet:

Get-WmiObject -class Win32_Printer -computername $computer|

Next we use the ForEach-Object cmdlet to work our way through the detailed printer information that was returned from the Win32_Printer WMI class. We use the –Begin parameter from the ForEach-Object cmdlet to perform an action once for the entire pipeline. We create two hashtables. One is stored in the $networkPrn variable and the other in the $localPrn variable. The hashtables contain one key and one value: Name and PortName. We will use these for column headers in our report:

ForEach-Object -Begin {$networkPrn=$localPrn = @{"Name"="PortName"} } `

The tasks that will be performed once for each item on the pipeline go into the –process parameter of the ForEach-Object cmdlet. Inside the –process parameter, we examine the value of the Boolean property Local. If the Local property is set to true, it exists. This is shown here:

 -Process {  if($_.Local)    { 

We add the name of the printer and the port name to the hashtable stored in the $localPrn variable if the Local property is set to true. To add items to the end of the hashtable, we use the += operator. This is seen here:

      $localPrn += @{ $_.name = $_.portname }    } #end if

If the Local property does not exist, it means the printer is a network printer:

 if(-not $_.Local)   {

Again, we have to add the name of the printer and the port name to the hashtable. This time, the hashtable is stored in the $networkPrn variable. This is seen here:

   $networkPrn += @{ $_.name = $_.portname } } #end if} #end foreach-object process

After you create the two hashtables, it is time to produce the report. Each hashtable is seeded with the name and the portname strings in the first row of the hashtable. This means that if no printers are found, the hashtable will have a count of 1. If the hashtable has a count greater than 1, a printer is detected and we want to display the report. This is seen here:

if($localPrn.count -gt 1){

Then we use the count property from the hashtable object to provide information about how many printers are defined on the computer. We must use $localPrn.count-1 because of using the first row of the hashtable as a header for our report. We have to use a subexpression, which is a $() around the $localPrn.count to force the evaluation of the count property before trying to display it as a string. If we did not do this, we would be greeted with something that looks like this:

There are System.Collections.Hashtable.count -1 Local printers on localhost

This is also known as unraveling because the name of the object that is displayed in the variable is displayed instead of the value that is contained in the variable. The subexpression is one way to deal with the problem. This is seen here:

 Write-Host -foreground cyan "There are $($localPrn.count -1) Local printers on $computer"

The other way to deal with the problem of unraveling is to avoid it completely and use concatenation on a literal string instead of using the expanding string. The confusing aspect is that, because you are using the Write-Host cmdlet, you do not have to use the concatenation operator (+). This is seen here:

Write-Host -foreground cyan 'There are ' ($localPrn.count -1) ' Local printers on ' $computer

To display the printer information, we use a really cool trick. We pipeline the hashtable object to the Format-Table cmdlet. This will enable us a measure of control over the display. In particular, we want to automatically size it, hide the table headers, and wrap the long field printer port names to avoid cutting them off on the display. This is shown here:

 $localPrn | Format-Table -auto -Hide -wrap}

If no local printers were detected, we display a message telling the user that there are no local printers:

Else { "No local printers detected on $computer" }

Now we perform the identical steps for the network printers:

if($networkPrn.count -gt 1) {Write-Host -foregroundcolor green "There are $($networkPrn.count -1) Network printers on $computer " $networkPrn | Format-Table -auto -Hide -wrap }Else {Write-Host -foregroundcolor red "No network printers detected on $computer"} #end else

That is all the moving parts involved in the script. When you run it, you are greeted with a display that resembles the one shown here (of course, the display that is seen after you run the script depends on your printer configuration):

Image of the information displayed after running the script

 

Well, LB, that is all there is to producing a listing of local and network printers from your workstation. It should work on a remote computer as well as on a local computer. You just have to change the name of the destination computer. I have to go unpack, and then I plan to hit the treadmill. Too much good Tech·Ed food. See you tomorrow when Printer Week continues. Until then, peace.

 

Ed Wilson and Craig Liebendorfer, Scripting Guys