I spent some time diving deeper into what the commands are doing and if there was anything that I could do in the startup script that would make it more consistent...
Let's cut to the chase:I concluded that the most reliable way to make sure printers will be pushed when users logged in was to make sure relevant connection entries are created by my script under HKLM during startup, instead of relying printui.dll /ga switch.
How did I arrive at this conclusion?Well, I used Process Monitor (aka Procmon) from SysInternals,
watched what happens if I issue commands like the ones below manually
- rundll32 printui.dll,PrintUIEntry /ga /n\\PrintServer\Printer (add per-machine printer connection)
- rundll32 printui.dll,PrintUIEntry /gd /n\\PrintServer\Printer (remove per-machine printer connection)
A Watched sequence of events during boot (Procmon > Options > Enable Boot-Logging)
Here are a few things to note that will help us understand what is going on :
- Our Print Server is named PrintServer1
- Printers shared on it has sharenames Printer1.. PrinterN
- HKLM Connections key: HKLM\SYSTEM\CurrentControlSet\Control\Print\Connections
- HKCU Connections key: HKCU\Printers\Connections
- If there is no ",,PrintServer1,PrinterX" entry under HKLM Connections key, user does not get the PrinterX connection.
Q. What happens if we "add per-machine printer connection" in a user session?
A. Seemingly nothing, user does not see the new printer connections until Spooler service is started. In reality, a new registry key under HKLM Connections key is created.
Q. So, what happens if we re-start the spooler services?
A. Normally, user gets the printer connection. It does not show up immediately, spooler service takes its sweet time but unless something goes terribly wrong, new printers shows up in a minute or two. Here is how (shortened for relevancy):
Spooler Service checks the entries under HKLM Connections key and detects a new printer connection, which will be in the following form:
You probably noticed that \\PrintServer1\PrinterX has been transformed to ,,PrintServer1,PrinterX and became a key. Provider and LocalConnection always had the same value, "Server" was set to the name of the Print Server with '\\' prefix.
So far, so good. The rest is the tiring loop:
- Spooler starts going through SIDS
- Creates a new connection under HKCU\Printers\Connections,,PrinterServer1.PrinterX
- Clones the 'Server', 'Provider', 'LocalConnection' settings from HKLM Connections key
- Queries and creates a new port setting:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Ports\NeXX: (Reg_SZ ),
where Ne in my case went from Ne00 to Ne08
- It then created an entry under devices that linked the port to printer:
\\PrintServer1\PrinterX = Winspool,NeXX: (Reg_SZ)
- In the next step, spooler creater a PrinterPort entry in the following form:
\\PrintServer1\PrinterX = Winspool,NeXX:15,45 (Reg_SZ)
- In the actual flow, HKCU was the last. Spooler actually started with SID S-1-5-19 (HKU\S-1-5-19\)and cycled through all SIDS under HKU but you get the point.
- THEN, an important step comes: querying printer, getting a connection to it (I see a %windir%\System32\spool\PIPE file created), creating a registry key for printer and writing everything about that printer
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\LanMan Print Services\Servers\PrinterServer1\Printers\PrinterX
So, that's gist of what spooler seems to be doing.
What's the problem?
Well, the problem I identified was that during the startup Spooler service would something be slacking. The command I ran to add a per-machine printer connection would return 258.
Whenever rundll32 returned 0, that process ended up creating an HKLM connection entry and printer showed up just fine for user.
If, however, rundll32 returned 258, no connection was created.
So, what does return code 258 mean? Answer comes from Alan Morris of Microsoft Windows Printing team:
He goes on to explain:
"I'd check the number of printers and drivers as well as the drivers installed on a machine where this is failing. The spooler may not be fully online and printui does call into spooler for these methods."
Well, all good, but I had seen services.exe kicking off spooler service a full minute before any commands were issued to it. Additionally, spooler service would almost always map the first printer but the number of printers it connected was varying each time.
I was using the same XP VM and got considerably different results (sometimes tons of return code 258 errors, sometimes just 1 or two).
Techs in the field were reporting a similar pattern and a quick Google search also got many hits in terms of people complaining about success rate of using these commands in the startup scripts.
What do we do now?Well, as I said in the beginning, my approach was to help the spooler service a bit by creating the HKLM connections registry keys for it. In my tests, it worked like a charm (YMMV) every single time.
Below is modified sample script
on error resume next Const ForReading = 1, ForWriting = 2, ForAppending = 8, CanCreate=True ScriptVersion="2013-08-13-02" Set oShell = CreateObject("WScript.Shell") oShell.LogEvent 0, "WinXP_WSStartup.vbs: Script Version: " & ScriptVersion 'Get group membership for the machine Set objSysInfo = CreateObject("ADSystemInfo") Set objComputer = GetObject("LDAP://" & objSysInfo.ComputerName) Groups = objComputer.GetEx("MemberOf") ' If Above Command does not find any groups for computer, it will error if Err.Number <> 0 Then oShell.LogEvent 0, "WinXP_WSStartup.vbs: Computer is not member of any AD groups. Exiting." Wscript.Quit end if isFirst=vbTrue Count=0 For Each group In Groups group=Replace(group,"/","\/") ' escape groups with a slash in the object name Set objGroup = GetObject("LDAP://" & group) If ( Instr(LCase(objGroup.CN),"grp.prn." ) > 0 ) then If isFirst Then PrinterGroups = objGroup.CN isFirst=vbFalse Count=1 Else PrinterGroups = PrinterGroups & ";" & objGroup.CN Count=Count+1 End If End if 'AllGroups = AllGroups & ";" & objGroup.CN Next if (Count > 0) Then 'Take care of setting up Reg File Set oFSO = CreateObject("Scripting.FileSystemObject") sRegFile = oShell.ExpandEnvironmentStrings("%TEMP%") & "\WinXP_WSStartup_PrinterConnections.reg" If oFSO.FileExists(sRegFile) Then oFSO.DeleteFile(sRegFile) End If Set oRegFile = oFSO.OpenTextFile(sRegFile, ForAppending, CanCreate) oRegFile.WriteLine "Windows Registry Editor Version 5.00" & vbCrLF & vbCrLF ' Now let's process each printer grou[ arrPrinterGroups=Split(PrinterGroups,";") for each PrinterGroup in arrPrinterGroups arrGroup = Split(PrinterGroup,".") ' skipping first 2 grp and prn as groups are named grp.prn.ServerName.PrinterName Server=arrGroup(2) Printer=arrGroup(3) '' Update registry file oRegFile.WriteLine("[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Connections\,," & Server & "," & Printer & "]" ) oRegFile.WriteLine(chr(34) & "Server" & chr(34) & "=" & chr(34) & "\\\\" & Server & chr(34)) oRegFile.WriteLine(chr(34) & "Provider" & chr(34) & "=" & chr(34) & "win32spl.dll" & chr(34)) oRegFile.WriteLine(chr(34) & "LocalConnection" & chr(34) & "=dword:0000001" & vbCrLf) '' ' cmdConnectPrinter="%comspec% /c rundll32 printui.dll,PrintUIEntry /ga /n\\" & Server & "\" & Printer oShell.LogEvent 0, "WinXP_WSStartup.vbs: Connecting to Printer: " & Printer & " on Server: " & Server ' oShell.LogEvent 0, "WinXP_WSStartup.vbs: Running " & cmdConnectPrinter ' Hide Window and return immediately without waiting ' oShell.Run cmdConnectPrinter,0,false Next oRegFile.close ' Import Printer Connection file silently oShell.Run "regedit /s " & Chr(34) & sRegFile & Chr(34), 0, True End If oShell.LogEvent 0, "WinXP_WSStartup.vbs: Nothing else to do. Exiting" Set oShell=Nothing