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

'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 next
Dim objADSystemInfo, objUser, objMemberOf, objChildMemberOf, objGroup, objChildGroup, objGroupEnum, objNetwork, objShell, objPrinter
Dim i, bTroubleFlag

Set objShell = CreateObject("WScript.Shell")
Set objNetwork = CreateObject("Wscript.Network")

WScript.sleep 25000

'Get current user info from active directory
Set objADSystemInfo = CreateObject("ADSystemInfo")
'bind to current user in active directory
Set objUser = GetObject("LDAP://" & objADSystemInfo.UserName)
'Tests to see if verbose messages should be displayed or not by looking at the user's description
if objuser.description = "printer.trouble" then
    bTroubleFlag = true
    msgbox "Troubleshooting Printer Logon Script"
end if

'Deletes any currently installed network Printers
Set objPrinter = objNetwork.EnumPrinterConnections
'Test to see if we have any printers mapped
If objPrinter.Count > 0 Then
    'The Printer array is Printer name, printer path that is why it is step 2
    for i=1 to objPrinter.Count Step 2
        'test to make sure it is a network printer
        if instr(objPrinter.Item(i),"\\") <> 0 then
            if bTroubleFlag then
                msgbox "Deleting:" & vbcrlf & objPrinter.Item(i)
            end if
            objNetwork.RemovePrinterConnection objPrinter.Item(i),true,true
        end if
end if

'Get an array of group names that the user is a member of
objMemberOf = objUser.MemberOf
'Adds appropriate printers based on the user's group membership and sets default printer
for 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 function
        if bTroubleFlag then
            msgbox "Found Printer Group: " & objGroup
        end if
                                objChildMemberOf = (GetObject("LDAP://" & objGroup)).MemberOf
                                for Each objChildGroup in objChildMemberOf
                                                if (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 expected
                                                                AddPrinter objChildMemberOf
                                                                AddPrinter objChildGroup
                                                end if
                                'If the group is not in the Printer Groups OU, pass it to AddPrinter function for analysis
        if bTroubleFlag then
            msgbox "Found non Printer Group: " & objGroup
        end if
                                AddPrinter objGroup
                end if

'Sets default printer based on group membership within the Printer Defaults OU
for Each objGroup in objMemberOf
    if (instr(objGroup,"OU=Printer Defaults") <> 0) then
        set objGroupEnum = GetObject("LDAP://" & objGroup)
        if bTroubleFlag then
            msgbox "Setting Default:" & vbcrlf & "[" & & "]" & vbcrlf & objGroupEnum.description
        end if
        objNetwork.SetDefaultPrinter objGroupEnum.description
        set objGroupEnum = nothing
    end if

if bTroubleFlag then
    msgbox "Printer Logon Script Finished"
end if

Function 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 printer
                set objGroupEnum = GetObject("LDAP://" & objIn)
                if bTroubleFlag then
                                msgbox "Adding:" & vbcrlf & "[" & & "]" & vbcrlf & objGroupEnum.description
                end if
                objNetwork.AddWindowsPrinterConnection objGroupEnum.description
                set objGroupEnum = nothing
end if
end Function


Popular posts from this blog

Deleting Orphaned (AKA Zombie) VMDK Files

Clone a Standard vSwitch from one ESXi Host to Another

Orphaned VMDK Files