Per User Printer Assignment Logon Script in VMware View
One of my customers has been trying to figure out how they are going to deal with printer assignment in their View environment. In truly stateless desktops (without Persona Management, even), you have to completely depend on external printer assignments, either through a logon script or through Active Directory. I prototyped both solutions for this customer. In order to minimize our logon times, we preinstalled our printer drivers in the parent image (for both tests).
We found that a normal Windows 7 logon onto a stateless desktop (where it’s always the first logon and so has to create a local profile and apply group policy) takes about 20 seconds from the time the user hits “connect”. Using Group Policy to distribute printers increased that logon time to about 45 seconds… which was a bit too much for my liking. Using a logon script, on the other hand, does not delay the logon process (even if the printers are not actually present immediately upon logon), and so this is the direction that I recommended that the customer should go.
I’ve said it before and here it is again: I’m not a Windows administrator. That said, I’ve seen my share of logon scripts. Many of them have basically been a big Case structure, where it checks each user’s group membership against each case and, if your group membership matches the case, you get the appropriate printer installed. This certainly works, but it scales poorly. As you get more and more printers (a single print server at this customer has about 100 printers on it), your case structure gets larger and larger, which means that the script gets slower and slower. Another drawback is that when printers are added or removed from the environment, the script must be updated to reflect those changes. That introduces the potential for human error to break the entire script, every time a printer is changed. It also requires that one of the customer’s scripting gurus (let’s be honest, if they’re not a guru, they don’t want to touch such a high visibility script) make the time to update it whenever the otherwise basic task of adding a printer occurs. All in all, a bad solution.
I know that I’m not particularly insightful and so there’s no way that I’m the first person to identify those problems. What I may lack in insight, I make up in google search syntax. Some poking around uncovered a printer deployment logon script by Robert Gransbury, on Microsoft’s technet. His approach is awesome, as it does away with that Case structure and instead allows admins to add/remove printers by directly manipulating Active Directory groups. Here’s how it works:
First, you need to prepare your Active Directory a little bit. Create an OU called “Printer Paths” (or at least, one with that string in its name). Add a new Security Group within that OU – for organization’s sake, I named that group after one of the printers in the environment. In that group’s description, enter the UNC Path that the client would use to access that printer (so the classic \\Print-Server\Server-Share-Name syntax). Next, add an OU (not a Child of the prior one!) called “Printer Defaults” and create another security group, this time named “Default-<printer name>” with the same UNC Path description.
What Robert’s script basically does is check all of the groups of which the user is a member. If those groups are not in the “Printer Paths” or “Printer Defaults” OUs, it ignores them. If the groups are in the “Printer Paths” OU, it adds a printer, using that group’s description as the printer string. It then does the same thing to find that user’s default printer, based on their membership of a “Printer Defaults” group. It’s simple and genius. It means that the script does not need to be changed when printers are changed (or print servers, for that matter). It means that administrators can use a GUI (although, admittedly, a very basic one) to manage their environment’s printers. It also means that the script doesn’t have to compare every one of a user’s groups against a giant Case structure of every printer in the organization. Instead, the group has the printer’s information inbuilt. Nice.
Of course, Robert’s script depends on the assumption that a user is a direct member of a “Printer Paths” group (or, in practice, several of them). It’s not a bad assumption, but my customer has printer zones defined where every user in a given zone will be given access to a whole list of printers. As such, I modified Robert’s script to support that scenario. I just added another bit of logic into the group check. If the group is part of the “Printer Groups” OU, it checks to see what groups that group is a member of and adds those groups’ descriptions as printers (since my Security Groups in the “Printer Groups” OU are all members of one or more “Printer Paths” groups).
Anyway, the prototype for this logon script has been successful, and with the printer drivers preinstalled in the desktop parent, they install nice and swiftly. I figured that I’d go ahead and post my modified version of Robert’s script, in case it proves useful to anyone. As always, this is a “use at your own risk” sort of affair (and please beware of unintended line breaks due to blog width issues!). I’ve got it in place and it’s working for me, but I have no idea about the intricacies of your environment and can offer no guarantee that this script will do anything useful or beneficial for you.
'Dynamic Printer Deployment based on AD Group Membership
'Author: Robert Gransbury
'Source: Microsoft Technet (http://gallery.technet.microsoft.com/scriptcenter/bf160908-93e3-484c-944f-1c95004c5498)
'Modified By: Jason Coleman, 3/27/2012'This script adds printers upon user login based on their AD Group membership.'It looks for groups in the "Printer Paths" OU and attempts to install the printers in those groups' descriptions.'It also looks for groups in the "Printer Groups" OU and installs printers based on what "Printer Paths" groups those groups are in.'It assigns default printers by membership in the "Printer Defaults" OU groups. If an account is a member of more than one "Printer Defaults" group, unexpected results may occur.'===========================================on error resume nextDim objADSystemInfo, objUser, objMemberOf, objChildMemberOf, objGroup, objChildGroup, objGroupEnum, objNetwork, objShell, objPrinterDim i, bTroubleFlagSet objShell = CreateObject("WScript.Shell")Set objNetwork = CreateObject("Wscript.Network")WScript.sleep 25000'Get current user info from active directorySet objADSystemInfo = CreateObject("ADSystemInfo")'bind to current user in active directorySet objUser = GetObject("LDAP://" & objADSystemInfo.UserName)'Tests to see if verbose messages should be displayed or not by looking at the user's descriptionif objuser.description = "printer.trouble" thenbTroubleFlag = truemsgbox "Troubleshooting Printer Logon Script"end if'Deletes any currently installed network PrintersSet objPrinter = objNetwork.EnumPrinterConnections'Test to see if we have any printers mappedIf objPrinter.Count > 0 Then'The Printer array is Printer name, printer path that is why it is step 2for i=1 to objPrinter.Count Step 2'test to make sure it is a network printerif instr(objPrinter.Item(i),"\\") <> 0 thenif bTroubleFlag thenmsgbox "Deleting:" & vbcrlf & objPrinter.Item(i)end ifobjNetwork.RemovePrinterConnection objPrinter.Item(i),true,trueend ifnextend if'Get an array of group names that the user is a member ofobjMemberOf = objUser.MemberOf'Adds appropriate printers based on the user's group membership and sets default printerfor Each objGroup in objMemberOf'Test to see if it is a printer group. all printer groups should be in this OU.if (instr(objGroup,"OU=Printer Groups") <> 0) then'If the group is in the Printer Groups OU, find that Group's parents and pass them to AddPrinter functionif bTroubleFlag thenmsgbox "Found Printer Group: " & objGroupend ifobjChildMemberOf = (GetObject("LDAP://" & objGroup)).MemberOffor Each objChildGroup in objChildMemberOfif (objChildGroup = "") then'if it is blank, it's because objChildMemberOf is a string because there was only 1 group that objGroup is a member of and so it did not return an array as expectedAddPrinter objChildMemberOfelseAddPrinter objChildGroupend ifnextelse'If the group is not in the Printer Groups OU, pass it to AddPrinter function for analysisif bTroubleFlag thenmsgbox "Found non Printer Group: " & objGroupend ifAddPrinter objGroupend ifnext'Sets default printer based on group membership within the Printer Defaults OUfor Each objGroup in objMemberOfif (instr(objGroup,"OU=Printer Defaults") <> 0) thenset objGroupEnum = GetObject("LDAP://" & objGroup)if bTroubleFlag thenmsgbox "Setting Default:" & vbcrlf & "[" & objGroupEnum.name & "]" & vbcrlf & objGroupEnum.descriptionend ifobjNetwork.SetDefaultPrinter objGroupEnum.descriptionset objGroupEnum = nothingend ifnextif bTroubleFlag thenmsgbox "Printer Logon Script Finished"end ifFunction AddPrinter(objIn)'Function expects to be passed an LDAP string for a group'Validates that an object is in the correct OU and attempt to find a UNC path in the description to install the printer.if (instr(objIn,"OU=Printer Paths") <> 0) then'Bind to the group to get is description. The description contain the path to the printerset objGroupEnum = GetObject("LDAP://" & objIn)if bTroubleFlag thenmsgbox "Adding:" & vbcrlf & "[" & objGroupEnum.name & "]" & vbcrlf & objGroupEnum.descriptionend ifobjNetwork.AddWindowsPrinterConnection objGroupEnum.descriptionset objGroupEnum = nothingend ifend Function