Lately I've been working on migrating a Windows 2008 Print Server from from 32 bit to Windows 2008 R2 64bit. During this I've been cleaning up unused printers and ports as my other post suggested. Considering this print sever had nearly 500 printers installed worked on automating the process. Using Powershell I noticed many people complaining of poor integration of Powershell for Printer support. While I agree there should of been better built-in functions I was able to write every feature I wanted once I figured out how.
There where many posts online but none of them seemed to handle any issues that arrised. For example if you try and create a printer using [WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_Printer").createInstance() and call .Put() you may get the following error.
Exception calling "Put" with "0" argument(s): "Generic failure "
At C:\Users\towlesd\Documents\My Dropbox\PrinterInformationfor2008.ps1:319 char:24 + [void] $newprinter.Put <<<< () + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : DotNetMethodException
This is caused because one of the pramaters you are trying to use isn't valid. It doesn't say that but thats what it means. Frustrating to say the least. The cause of this for me it was the print driver didn't exist on the server. Because of this my InstallPrinters function first checks that all printers and ports first exist on the remote server. Little things like this make the entire process possible.
You really need to break down how you want to use this script it self as there are alot of useful functions depending on what your working on. In all the script can gather all the printer information about a given server; Its printers, ports and drivers and export that to a csv-file. It can try to ping all the ports and detect which are offline or bad DNS entires.
Here are most of the Powershell functions
- Create Printer
- Create Port
- Install Array of Printers
- Get Installed Drivers
- Get Install Ports
- Match printers with their ports in a csv-file
- Create csv files for any of this information
- migrate printers from one serevr to another
Without further delay.
########################### # # Purpose: Printer API for Managing and migrating Printers # Created By: Chris Towles # Website: http://www.ChrisTowles.com # When: 04/08/2011 # Version: 1.0 # Change List # ########################### function Ping() { param ([string] $ComputerName) [bool] $Pingable = $false try { $ping = new-object System.Net.NetworkInformation.Ping $Reply = $ping.send($ComputerName, 1000) if ($Reply.status –eq “Success”) { $Pingable = $true } } catch { return "DNS Issue" } if ($Pingable) { return “Online” } else { return "Offline" } } function Get-Nslookup() { param ([string] $Name) $DNSInfo = "" | select "DNSName", "DNS_IP" $DNSInfo.DNSName = $null $DNSInfo.DNS_IP = $null try { #http://powershellmasters.blogspot.com/2009/04/nslookup-and-powershell.html $cmd = "nslookup " + $Name + " " + $ns + " 2>&1" $result = Invoke-Expression ($cmd) -ErrorAction SilentlyContinue -WarningAction SilentlyContinue $DNSInfo.DNSName = $result.SyncRoot[3] $DNSInfo.DNSName = $DNSInfo.DNSName.Split(":")[1] $DNSInfo.DNSName = $DNSInfo.DNSName.Trim() $DNSInfo.DNS_IP = $result.SyncRoot[4] $DNSInfo.DNS_IP = $DNSInfo.DNS_IP.Split(":")[1] $DNSInfo.DNS_IP = $DNSInfo.DNS_IP.Trim() } catch{ $DNSInfo.DNSName = $null $DNSInfo.DNS_IP = $null } return $DNSInfo } Function Get-Printers( ) { param ([string] $ComputerName = ".") #sets "." as the default param if none is supplied Write-Host "Connecting to $ComputerName by WMI to gather printer information." Write-Host " Warning this may take a few minutes depending how many printers there are." -ForegroundColor Red $colItems = get-wmiobject -class "Win32_Printer" -namespace "root\CIMV2" -computername $ComputerName $count = $colItems.Count $pos = 0 Write-Host "Found $count printers. Getting details on each now." $Printers = @() foreach ($objItem in $colItems) { write-progress -activity "Getting Information on each printer." -status "% Complete" -percentcomplete (($pos++/$count)*100); #creating a new object called $PrinterInfo $PrinterInfo = New-Object psobject $PrinterInfo | Add-Member NoteProperty Caption $objItem.Caption $PrinterInfo | Add-Member NoteProperty Comment $objItem.Comment $PrinterInfo | Add-Member NoteProperty "Default" $objItem.Default $PrinterInfo | Add-Member NoteProperty "DriverName" $objItem.DriverName $PrinterInfo | Add-Member NoteProperty "Local" $objItem.Local $PrinterInfo | Add-Member NoteProperty "Name" $objItem.Name $PrinterInfo | Add-Member NoteProperty "PortName" $objItem.PortName $PrinterInfo | Add-Member NoteProperty "PrinterStatus" $objItem.PrinterStatus $PrinterInfo | Add-Member NoteProperty "PrintProcessor" $objItem.PrintProcessor $PrinterInfo | Add-Member NoteProperty "Shared" $objItem.Shared $PrinterInfo | Add-Member NoteProperty "ShareName" $objItem.ShareName $PrinterInfo | Add-Member NoteProperty "Status" $objItem.Status $PrinterInfo | Add-Member NoteProperty "SystemName" $objItem.SystemName $PrinterInfo | Add-Member NoteProperty "WorkOffline" $objItem.WorkOffline $Printers += $PrinterInfo } write-progress -activity "Getting Information on each Port." -status "% Complete:" -percentcomplete 100 -Completed; return $Printers } function Get-PortInfo () { param ([string] $ComputerName = ".", [bool]$CheckPorts = $true) #sets "." as the default param if none is supplied Write-Host "Connecting to $ComputerName by WMI to gather printer port information." Write-Host " Warning this may take a few minutes because its pinging each port." -ForegroundColor Red $colItems = get-wmiobject -class "Win32_TCPIPPrinterPort" -namespace "root\CIMV2" -computername $ComputerName $count = $colItems.Count $pos = 0 Write-Host "Found $count printer ports. Getting details on each now." $Ports = @() foreach ($objItem in $colItems) { write-progress -activity "Getting Information on each Printer Port. " -status "% Complete:" -percentcomplete (($pos++/$count)*100); $PortInfo = New-Object psobject $PortInfo | Add-Member NoteProperty "ByteCount" $objItem.ByteCount $PortInfo | Add-Member NoteProperty "HostAddress" $objItem.HostAddress $PortInfo | Add-Member NoteProperty "HostAddressLink" ("http://" + $objItem.HostAddress + "/" ) $PortInfo | Add-Member NoteProperty "Name" $objItem.Name $PortInfo | Add-Member NoteProperty "PortNumber" $objItem.PortNumber $PortInfo | Add-Member NoteProperty "Protocol" $objItem.Protocol $PortInfo | Add-Member NoteProperty "Queue" $objItem.Queue $PortInfo | Add-Member NoteProperty "SNMPCommunity" $objItem.SNMPCommunity $PortInfo | Add-Member NoteProperty "SNMPDevIndex" $objItem.SNMPDevIndex $PortInfo | Add-Member NoteProperty "SNMPEnabled" $objItem.SNMPEnabled if($CheckPorts) { $pingstring = Ping -ComputerName $objItem.HostAddress $PortInfo | Add-Member NoteProperty "Ping" $pingstring $DNSInfo = get-nslookup -Name $objItem.HostAddress $PortInfo | Add-Member NoteProperty "DNSName" $DNSInfo.DNSName $PortInfo | Add-Member NoteProperty "DNS_IP" $DNSInfo.DNS_IP } $Ports += $PortInfo } write-progress -activity "Getting Information on each Port." -status "% Complete:" -percentcomplete 100 -Completed; return $Ports } function Get-PrintersWithPort { param ( [array] $Printers, [array] $Ports ) $count = $Printers.Count $pos = 0 $PrinterWithPortInfo = @() Foreach($Printer in $Printers) { write-progress -activity "Sorting Printers to their matching Ports." -status "% Complete" -percentcomplete (($pos++/$count)*100); $Port = $Ports | where {$_.Name -like ($Printer.PortName) } if($Port -ne $null) { $properties = $Port | Get-Member -MemberType NoteProperty foreach ($member in $properties ) { $PropName = $member.Name $PropValue = $Port | Get-Member -MemberType NoteProperty ($member.Name) $PropValue = $PropValue.ToString().Split("=")[1] if($PropValue -match "null") { $PropValue = ""} $Printer | Add-Member NoteProperty ("Port_" + $Propname) $PropValue } $PrinterWithPortInfo += $Printer } } write-progress -activity "Sorting Printers to their matching Ports. Phase 3 of 3." -status "% Complete" -percentcomplete 100 -Completed; return $PrinterWithPortInfo } function Get-PortsWithPrinterAndOnline { param ( [array] $Printers , [array] $Ports ) $PortsWithPrinterAndOnline = @() Foreach($Printer in $Printers) { $Port = $Ports | where { $_.Name -like ($Printer.PortName) } if(($Port -ne $null ) -and ($Port.Ping -like "Online")) { $PortsWithPrinterAndOnline += $Port } } return $PortsWithPrinterAndOnline } function Get-PortsWithoutPrinters { param ( [array] $Printers , [array] $Ports ) $PortsWithoutPrinters = @() Foreach($Port in $Ports) { $Printer = $Printers | where { $_.PortName -like ($Port.Name) } if(($Printer -eq $null ) ) { #-and ($Port.Ping -like "Online")) { #Port Has no Printer $PortsWithoutPrinters += $Port } } $PortsWithoutPrinters = $PortsWithoutPrinters | sort -Property "Name" return $PortsWithoutPrinters } function Get-PrintersWithPortsOffLine { param ( [array] $Printers, [array] $Ports ) $PrinterWithPortOffLine = @() Foreach($Printer in $Printers) { $Port = $Ports | where { $_.Name -like ($Printer.PortName) } if(($Port -ne $null ) -and ($Port.Ping -inotlike "Online")) { $PrinterWithPortOffLine += $Printer } } return $PrinterWithPortOffLine } function Get-PharosQueuesWithWInprintProcessor { param ( [array] $Printers ) $WinPrint = @() Foreach($Printer in $Printers) { if($Printer.PrintProcessor -ine "WinPrint" -and $Printer.Name -like '*Q') { $WinPrint += $Printer } } return $WinPrint | sort "Name" } Function Get-InstalledPrinterDrivers () { param ( [string] $ComputerName = "." ) $colItems = get-wmiobject -class "Win32_PrinterDriver " -namespace "root\CIMV2" -computername $ComputerName $PrintDrivers = @() foreach ($objItem in $colItems) { #creating a new object called $PrinterInfo $Driver = New-Object psobject $Driver | Add-Member NoteProperty Name $objItem.Name.Split(",")[0] $Driver | Add-Member NoteProperty Chip $objItem.Name.Split(",")[2] $PrintDrivers += $Driver } return $PrintDrivers } function Create-PrinterPort { param ( [string] $ComputerName = ".", #sets "." as the default param if none is supplied [string] $PortName , [string] $DNSName , [int] $Protocol = 1, #default of 1 for RAW [string]$Queue = $null, [string]$PortNumber = "9100", [bool] $SNMPEnabled , [string] $SNMPCommunity = $null) $newPort = ([WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance() $newport.Name = $PortName $newport.HostAddress= $DNSName #sometimes IP Address $newport.Protocol= $Protocol #Protocols possiable #1 = RAW, Printing directly to a device or print server. #2 = LPR, Legacy protocol, which is eventually replaced by RAW. if ($Protocol -eq 2 ) { $newport.Queue = $Queue } $newPort.PortNumber = "9100" $newport.SNMPEnabled = $SNMPEnabled if( $SNMPCommunity -ne $null ) { $newport.SNMPCommunity = $SNMPCommunity } Write-Host "Creating Port $PortName" -foregroundcolor "darkgreen" [void] $newport.Put() } function Create-CSVOutputFile { param ( [array] $CustomObject , [String] $filename ) #writing file to Write-Host $filename $CustomObject | Sort-Object "Name" | Export-Csv $filename #removes the top line as it just has #TYPE System.Management.Automation.PSCustomObject in it. get-content $filename | select -Skip 1 | set-content "$filename-temp" if (Test-Path "$filename-temp") { move "$filename-temp" $filename -Force } else { Remove-Item "$filename" Write-Host "$filename was deleted because nothing was in it." } } function Create-Printer { Param ( [string]$ComputerName = ".", [string]$PrinterName, [string]$DriverName, [string]$Location, [string]$ShareName, [String]$PortName, [string]$Comment ) $newprinter = ([WMICLASS]"\\$ComputerName\ROOT\cimv2:Win32_Printer").createInstance() $newprinter.Drivername = $DriverName $newprinter.PortName = $PortName $newprinter.Shared = $true $newprinter.Sharename = $ShareName $newprinter.Location = $Location $newprinter.Comment = $Comment $newprinter.DeviceID = $PrinterName Write-Host "Creating Printer $PrinterName" -foregroundcolor "darkgreen" [void] $newprinter.Put() #Exception calling "Put" with "0" argument(s): "Generic failure " # This Means their is something wrong with a paramter, Most likely its the driver name } function Install-PrinterPorts { param ( [string] $ComputerName , [array] $Ports ) Write-Host "Creating the following " $Ports.Count " printer ports on " $ComputerName foreach ($newport in $Ports) { [bool] $SNMPEnabled = $false if( $newport.SNMPEnabled -eq $true ) { $SNMPEnabled = $true } Create-PrinterPort -ComputerName $ComputerName ` -DNSName $newport.HostAddress.ToUpper() ` -PortName $newport.Name.ToUpper() ` -PortNumber $newport.PortNumber ` -Protocol $newport.Protocol ` -SNMPEnabled $SNMPEnabled ` -SNMPCommunity $newport.SNMPCommunity ` -Queue $newport.Queue } } Function Install-Printers { param ( [string] $ComputerName = ".", [array] $PrintersToCreate, [array] $PortsToCreate, [bool] $TestDriversAndPortsOnly = $false ) $PrintersToInstall = @() $MissingPort = @() $MissingDriver = @() #Create the Remote Ports Install-PrinterPorts -ComputerName $ComputerName -Ports $PortsToCreate #Check Print server for what ports and Printers are installed. $InstalledDrivers = Get-InstalledPrinterDrivers -ComputerName $ComputerName $InstalledPorts = Get-PortInfo -ComputerName $ComputerName -CheckPorts $false #I wished to have all the names in uppercase only. $Printer.PortName = $Printer.PortName.ToUpper() foreach($Printer in $Printers) { $DriverFound = $InstalledDrivers | where {$_.Name -eq $Printer.DriverName} $PortFound = $InstalledPorts | where {$_.Name -eq $Printer.PortName} if($DriverFound -eq $null) { #Write-Host ("The Print Driver isn't installed on the Destination Server" + $Printer.DriverName ) $MissingDriver += $Printer.DriverName } elseif($PortFound -eq $null) { #Write-Host ("The Printer Port " + $Printer.Port_Name + " didn't existon the destination Server" + $Printer.DriverName ) $MissingPort += $Printer.PortName } else{ $PrintersToInstall += $Printer } } Write-Host "" Write-Host ("The following printer drivers are missing from " + $ComputerName ) $MissingDriver | Sort | Get-Unique | ft -AutoSize Write-Host "" Write-Host ("The following printer port didn't exist on " + $ComputerName ) $MissingPort | Sort | Get-Unique | ft -AutoSize Write-Host "" if ( $TestDriversAndPortsOnly -eq $false ) { foreach($Printer in $PrintersToInstall) { Create-Printer ` -PrinterName $Printer.Name ` -ComputerName $DestinationHost ` -DriverName $Printer.DriverName ` -PortName $Printer.PortName.ToUpper() ` -ShareName $Printer.ShareName ` -Comment $Printer.Comment ` -Location "" } } } ################################################################################# # MAIN # ################################################################################# Clear $ComputerName = Read-Host "Enter the Print Server you wish to gather data about." #$DestinationHost = "dvprintserv1" [bool] $QueryData = $true [bool] $OutputFiles = $true if( $QueryData ) { #Gets all the information $Printers = @() | Get-Printers -ComputerName $ComputerName $Ports = @() | Get-PortInfo -ComputerName $ComputerName $PrinterWithPortInfo = @() | Get-PrintersWithPort -Printers $Printers -Ports $Ports $PortsWithoutPrinters = @() | Get-PortsWithoutPrinters -Printers $Printers -Ports $Ports $PrintersWithOffLinePort = @() | Get-PrintersWithPortsOffLine -Printers $Printers -Ports $Ports } #Used to migrate printers from one server to another. #Install-Printers -ComputerName $DestinationHost -PrintersToCreate $Printers -Ports $Ports #-TestDriversAndPortsOnly $true if($OutputFiles) { #Formated File Names $d = get-date $FileTime = "" + $d.Year + "-" + ("{0:D2}" -f $d.Month) + "-" + ("{0:D2}" -f $d.Day) + "-" + ("{0:D2}" -f $d.Hour ) + ("{0:D2}" -f $d.Minute) $PortsFilename = [String]::Format( "{0}_Ports_{1}.csv", $ComputerName, $FileTime ) $PrintersFilename = [String]::Format( "{0}_Printers_{1}.csv", $ComputerName, $FileTime ) $PrintersWithPortFilename = [String]::Format( "{0}_PrintersWithPort_{1}.csv", $ComputerName, $FileTime ) $PortsWithoutPrintersFilename = [String]::Format( "{0}_PortsWithoutPrinters_{1}.csv", $ComputerName, $FileTime ) $PrintersWithOffLinePortFileName = [String]::Format( "{0}_PrintersWithOffLinePort_{1}.csv", $ComputerName, $FileTime ) #writing file [Environment]::CurrentDirectory = (Get-Location -PSProvider FileSystem).ProviderPath Write-Host ("Saving CSV Files at " + [Environment]::CurrentDirectory + " Named the following.") Create-CSVOutputFile $Printers -filename $PrintersFilename Create-CSVOutputFile $Ports -filename $PortsFilename Create-CSVOutputFile $PrinterWithPortInfo -filename $PrintersWithPortFilename Create-CSVOutputFile $PortsWithoutPrinters -filename $PortsWithoutPrintersFilename Create-CSVOutputFile $PrintersWithOffLinePort -filename $PrintersWithOffLinePortFileName }Here an example of some output.
Connecting to dvprintserv1 by WMI to gather printer information. Warning this may take a few minutes depending how many printers there are. Found 123 printers. Getting details on each now. Connecting to dvprintserv1 by WMI to gather printer port information. Warning this may take a few minutes because its pinging each port. Found 355 printer ports. Getting details on each now. Saving CSV Files at C:\Users\towlesd\Documents Named the following. dvprintserv1_Printers_2011-04-08-1211.csv dvprintserv1_Ports_2011-04-08-1211.csv dvprintserv1_PrintersWithPort_2011-04-08-1211.csv dvprintserv1_PortsWithoutPrinters_2011-04-08-1211.csv dvprintserv1_PrintersWithOffLinePort_2011-04-08-1211.csv
too bad they make it difficult to leave a comment!
ReplyDeleteStill getting failures, doesn't seem to want to work on local machine.
ReplyDeletetry just putting "." for the machine when it asks.
DeleteAdded an Install-PrinterDriver function...
ReplyDeleteFunction Install-PrinterDriver {
param (
[string]$ComputerName = ".",
[string]$DriverName,
[string]$DriverPath,
[string]$INFFileName)
$InstalledDrivers = Get-InstalledPrinterDrivers -ComputerName $ComputerName
$DriverFound = $InstalledDrivers | where {$_.Name -eq $DriverName}
if($DriverFound -eq $null) {
Write-Host ("The Print Driver " + $DriverName + " isn't installed on " + $ComputerName ) -ForegroundColor Red
Write-Host ("Installing Driver " + $DriverName + " on " + $ComputerName) -ForegroundColor Green
$NewPrinterDriver = [wmiclass]"Win32_PrinterDriver"
$objDriver = $NewPrinterDriver.createinstance()
$objDriver.Name=$DriverName
$objDriver.DriverPath=$DriverPath
$objDriver.Infname= $DriverPath +"\" + $INFFileName
$NewPrinterDriver.AddPrinterDriver($objDriver)
return $NewPrinterDriver.Put()
}
Else{
Write-Host ("The Print Driver " + $DriverName + " is installed on " + $ComputerName) -ForegroundColor Green
}
}
Function Get-InstalledPrinterDrivers () {
param ( [string] $ComputerName = "." )
$colItems = get-wmiobject -class "Win32_PrinterDriver " -namespace "root\CIMV2" -computername $ComputerName
$PrintDrivers = @()
foreach ($objItem in $colItems) {
#creating a new object called $PrinterInfo
$Driver = New-Object psobject
$Driver | Add-Member NoteProperty Name $objItem.Name.Split(",")[0]
$Driver | Add-Member NoteProperty Chip $objItem.Name.Split(",")[2]
$PrintDrivers += $Driver
}
return $PrintDrivers
}
#Install-PrinterDriver `
#-ComputerName "." `
#-DriverName "HP Universal Printing PCL 6" `
#-DriverPath "C:\Universal Printer Drivers\HP\PCL 6 WIN 2003 64 BITS" `
#-INFFileName "hpcu083u.inf"
$Temp = Install-PrinterDriver "NLDCB-MIG01" "HP Universal Printing PCL 6" "C:\Universal Printer Drivers\HP\PCL 6 WIN 2003 64 BITS" "hpcu083u.inf"
Why don't you use simple commands.
ReplyDeleteYou could get the DNS easier like. [System.Net.Dns]::GetHostbyAddress($printer."IPv4 Address").HostName
Excellent Chris.. Thanks
ReplyDeleteWonderful, many thanks.
ReplyDeleteAwesome. Thanks!
ReplyDeleteyou makin it hard for these cats out here
ReplyDeleteIs it possible to use this script to export a list of printers and details on my local machine? I tried typing "localhost", but that didn't export anything.
ReplyDeletetry "." as your local machine. It worked for me.
DeleteThank's for this great script.
ReplyDeleteI added a few lines to fit my needs:
The location was missing:
Function Get-Printers( )
...
$PrinterInfo | Add-Member NoteProperty "Location" $objItem.Location
..
The port was hard coded:
function Create-PrinterPort
...
$newPort.PortNumber = $PortNumber
...
I needed to replace old drivers with new ones:
Function Install-Printers
$DriverFound = $InstalledDrivers | where {$_.Name -eq $Printer.DriverName}
if($DriverFound -eq $null) {
switch ($Printer.DriverName)
{
"Lexmark Universal" {$Printer.DriverName = "Lexmark Universal v2"; $DriverFound = $true}
"Datamax M-4206 Mark II" {$Printer.DriverName = "Datamax-O'Neil M-4206 Mark II"; $DriverFound = $true}
"HP LaserJet 4100 Series PCL" {$Printer.DriverName = "HP LaserJet 4100 Series PCL6"; $DriverFound = $true}
}
}
Thanks for sharing this script. actually i was searching for this script.
ReplyDeleteRaw material supplier for chemical toner
Trying to run this under WIN7 client. Am getting the following error:
ReplyDeleteEnter the Print Server you wish to gather data about.: .
Connecting to . by WMI to gather printer information.
Warning this may take a few minutes depending how many printers there are.
Found 4 printers. Getting details on each now.
Connecting to . by WMI to gather printer port information.
Warning this may take a few minutes because its pinging each port.
Found printer ports. Getting details on each now.
Attempted to divide by zero.
At C:\Scripts\test51.ps1:108 char:121
+ write-progress -activity "Getting Information on each Printer Port. " -stat
us "% Complete:" -percentcomplete (($pos++/ <<<< $count)*100);
+ CategoryInfo : NotSpecified: (:) [], RuntimeException
+ FullyQualifiedErrorId : RuntimeException
***
Is there something different in my client that would cause this or does the var need to be set differently or to a base value somewhere in the script?
Any assisting in helping me understand better is greatly appreciated ...
DT
Awesome work. Thank you
ReplyDelete