Programatically Installing Printers in Windows
Once again, I was at work and ran into a problem that I had never even considered before: How the heck do you programatically install printers in Windows?
We had a client that I needed to automate the installation of printers on multiple workstations for. Preferably, I needed those installations to be done silently. This seemed like a relatively trivial task to me…. at least at first it did.
I initially thought I could just grab up all the driver installers, put them in a folder, and then write a simple Windows BAT/CMD script file to kick each one off with arguments to install unattended. That idea proved to be unfeasible. Why? Well let me tell you a little story…..
The first thing I realized is that driver packages from different vendors (or even the SAME vendor) are not all created equally (duh). Some are packaged as Windows Installer files (.msi) and some are stand-alone executables (.exe) and some even come as a ZIP archive (.zip). The ZIP files are usually just a collection of files including a Windows setup information file (.inf). So at first, I thought, “Hey… I can just dig into the folder and look at the extension of each file and build a command string based on that go from there.” Yeah… not so much. For three reasons:
1) MSI switches change depending on which version of Windows Installer the package was built on.
2) You never know what you’re going to get with an .exe (some only perform an extraction).
3) I honestly don’t know how you would perform a proper installation using only the .inf from the command line.
So I got to thinking…. when doing a manual installation using the “Add Printer” wizard and the .inf file…. Could I automate that somehow? I need to hide it though…. and not display anything to the user if possible…. hmmmmm…..
So I spent the next three days searching with Google and trying to find any information I could about programatically installing printer drivers. I found a few code examples in different languages, a few different methods, and LOTS of discussion on the topic, but in the end…. no one had really come up with a solid or definitive solution to the problem. Either that, or not many people were interested in the problem. Afterall, you can do work like that these days in Active Directory and probably even with Group Policy Objects (GPO). The problem is, not all of our clients are using Active Directory. Eventually, I did come across this document and this document describing command-line usage of PRINTUI.DLL. As far as I can tell, PRINTUI is the “Add Printer” wizard.
Having read those documents, started writing a dead-simple CMD script like this:
@echo off cls setlocal enableextensions title LocalPrinterInstaller set _displayName="HP Color LaserJet 2840 PS" set _driverPath="%CD%\HP LaserJet 2840 PS\hpc2800d.inf" set _portName="HP_2840" set _driverName=%_displayName% set _setDefault=1 set _suppressErrors=0 set _baseCommand="RUNDLL32 PRINTUI.DLL,PrintUIEntry" set _exec= if exist %_driverPath% set _exec=%_baseCommand% /if /b %_displayName% /f %_driverPath% /r %_portName% /m %_driverName% if %_suppressErrors%==1 (set _exec=%_exec% /q) echo. echo Installing %_driverName% ... cmd /c %_exec% if ErrorLevel 0 ( if %_setDefault%==1 ( set _exec=%_baseCommand% /y /n %_displayName% if %_suppressErrors%==1 (set _exec=%_exec% /q) echo. echo Setting %_displayName% as default... cmd /c %_exec% if not ErrorLevel 0 ( echo. echo Failed to set default. ) ) ) else ( echo. echo Driver installation failed. ) ) else ( echo. echo %_driverPath% does not exist. ) endlocal pause exit
The above script makes some assumptions:
1) That there is an HP Color LaserJet 2840 driver in a folder under the folder that the script is running from.
2) That the driver name and display name are the same (you’ll probably have to look inside the INF file for that).
3) That you already have a port named “HP_2840″.
Note the _setDefault and _suppressErrors variables. If _setDefault is “1″ then after the installation completes, it will execute the command to make the newly installed printer the default. If _suppressErrors is “1″ then a totally silent installation will be performed and now error message dialogs will be generated.
Now… using the above logic, I decided to turn this into a UDF for AutoItV3. It looks like this:
Func _InstallPrinterDriver($sDisplayName, $sDriverPath, $sPortName, $iSetDefault = False, $iSuppressErrors = False) Local $fSuppress = "" Local $fSetDef = "" If Not IsBool($iSetDefault) Then $iSetDefault = False If Not IsBool($iSuppressErrors) Then $iSuppressErrors = False If $iSuppressErrors = True Then $fSuppress = " /q" If $iSetDefault = True Then $fSetDef = " /y /Gw" If StringLen($sDriverPath) = 0 Or Not FileExists($sDriverPath) Then Return SetError(1, 0, False) If StringLen($sPortName) = 0 Then Return SetError(2, 0, False) If StringLen($sDriverName) = 0 Then Return SetError(3, 0, False) If StringLen($sDisplayName) = 0 Then $sDisplayName = $sDriverName ;Execute command to install printer. RunWait('RUNDLL32 PRINTUI.DLL,PrintUIEntry /if /b "' & $sDisplayName & '" /f "' $sDriverPath & '" /r "' & $sPortName & '" /m "' & $sDriverName & $fSuppress) If @error Then Return SetError(4, 0, False) ;If true, execute command to set printer as default. ;The "/y" switch cannot be combined with the install command. ;It must be executed separately. If $iSetDefault = True Then Sleep(2000) RunWait('RUNDLL32 PRINTUI.DLL,PrintUIEntry /y /n "' & $sDisplayName & '"' & $fSuppress) If @error Then Return SetError(0, 1, True) EndIf Return True EndFunc
As before, this assumes that the port name specified by the $sPortName parameter already exists. But what about programmatically creating an IP printing port? Well, you can do that too using WMI.
Func _CreatePort($sPortName, $sIP) ;Create WMI Service object reference. Local $objWMIService = ObjGet("winmgmts:{impersonationLevel=Impersonate,(LoadDriver)}!\\.\root\cimv2") If @error Or Not IsObj($objWMIService) Then Return SetError(1, 0, False) ;Create Win32_TCPIPPrinterPort class reference object. Local $objNewPort = $objWMIService.Get("Win32_TCPIPPrinterPort").SpawnInstance_ If @error Or Not IsObj($objNewPort) Then Return SetError(2, 0, False) ;Check parameters. If Not IsString($sIP) Or StringLen($sIP) = 0 Then Return SetError(3, 0, False) If Not IsString($sPortName) Or StringLen($sPortName) = 0 Then Return SetError(4, 0, False) ;Create port. $objNewPort.Name = $sPortName $objNewPort.Protocol = 1 $objNewPort.HostAddress = $sIP $objNewPort.PortNumber = 9100 $objNewPort.SNMPEnabled = False $objNewPort.Put_ If @error Or $objNewPort.Status = "Error" Then Return SetError(4, 0, False) ;Dispose objects and return. $objNewPort = 0 $objWMIService = 0 Return True EndFunc
Its important to note the underscore at the end of the .SpawnInstance_ and .Put_ methods. The underscore is actually part of the method name and must be present. There is no space between the name and the underscore, like a line continuation.
Now, suppose you want to delete a printer…. I’ve got a UDF for that as well:
Func _DeletePrinter($sPrinterName, $iSuppressErrors = False) Local $fSuppress = "" If Not IsBool($iSuppressErrors) Then $iSuppressErrors = False If $iSuppressErrors = True Then $fSuppress = " /q /Gw" If Not IsString($sPrinterName) Or StringLen($sPrinterName) = 0 Then Return SetError(1, 0, False) RunWait('RUNDLL32 PRINTUI.DLL,PrintUIEntry /dl /n "' & $sPrintName & '"' & $fSuppress) If @error Then Return SetError(2, 0, False) Return True EndFunc
And thats pretty much it. Using these functions, you can wrap them up in a script and perform a fully-automated printer installation. Personally, I combined the whole thing into a compiled script (compiled as CUI) and then called it from another script that recursively discovered available driver packages and installed each one in batch fashion. I’ll be posting the project (compiled app + source + some documentation) in the Downloads section a little bit later. So keep your eye out for it.
I’m hoping to port the above code to a PowerShell script at some point. I’ll post it when I find the time. Hope this helps!

Leave a Reply
You must be logged in to post a comment.