Migrating printers while merging and removing queues – client side

Scenario:

You aquire a new network, and the naming schemes (yes, plural) are… not consistant. Also, the servers are running on Windows 2000/2003 still. So, you upgrade the servers to 2012 R2 and get their naming standardized. But what about those printers? To make matters worse, several departments cross-share printer usage in case of outages, and some managers have a printer or two dedicated just to themselves… as do some of the regular staff. You have been authorized to consolidate some of these, and just remove others. But with 500+ users on this network, you need to script it (or hire a small army of college students, but let’s leave that for another discussion).

Solution:

Now, your first task is to identify all the printers, and their ports. Then you assign new names to these printers matching your naming scheme. I like to put it all in an Excel spreadsheet, so I can see it and line things up easier. Once this is done, actually create and test each of the printers on the new server.

Once this is done, you need to point the users to these new queues. Start by exporting the columns for old and new printer names to two text files (I used “oldprinters.txt” and “newprinters.txt”), as shown below:

printer-1a
printer A
prn-2nd-from-left
prt by Jane
Mikes-printer
Bob
Timmy-queue
prn-maint-bw
prn-acct-bw
prn-acct-bw
REMOVE
prn-sales-bw
prn-sales-clr
prn-mgmt-clr
prn-dev-bw
prn-maint-bw

*Make sure that the columns line up, so that each line in the new column matches up with the column you want to change from in the old column!

Now, every script out there that I can find won’t handle changing print queue names, or removing/consolidating certain printers. We need to just delete the old printers from the users, and we don’t want to add multiple copies of the same printer. So I came up with this solution.


' --Initialize script variables--
strComputer    = "."
DefaultPrinter = 4
ReadOnlyFile   = 1
NumPosition    = 0
AddThisPrinter = "Yes"
'Change these four variables to match your environment
strOldServer   = "\\Print-Srv\"
strNewServer   = "\\SiteA-FPS-W12\"
strOldPrinters = "oldprinters.txt"
strNewPrinters = "newprinters.txt"

' --Initialize script objects--
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set objFSO = CreateObject("Scripting.FileSystemObject")

' Wait until the user is really logged in...
strUserName = ""
While strUserName = ""
   WScript.Sleep 100 ' 1/10 th of a second
   Set objNetwork = WScript.CreateObject("WScript.Network")
   strUserName = objNetwork.UserName
Wend

' --Generate a list of printers that this user has installed--
Set colInstalledPrinters = objWMIService.ExecQuery _
    ("Select * From Win32_Printer Where Network = True")

' --Generate a list of printer names from the old server--
Set objOldPrintersFile = objFSO.OpenTextFile(strOldPrinters, ReadOnlyFile)
strContents = objOldPrintersFile.ReadAll
colOldPrinterList = Split(strContents,vbNewLine)

' --Generate a list of printer names from the new server--
Set objNewPrintersFile = objFSO.OpenTextFile(strNewPrinters, ReadOnlyFile)
strContents = objNewPrintersFile.ReadAll
colNewPrinterList = Split(strContents,vbNewLine)

' --Iterate through the list of printers that this user has installed--
For Each objPrinter in colInstalledPrinters
'   --Since For Each doesn't track index, keep track of it yourself--
    NumPosition = 0
'   --Iterate through all printer names located on old server--
    For Each strOldPrinter in colOldPrinterList
'       --We fould a match, so update it--
        strOldPrinterID = strOldServer & strOldPrinter
        If (objPrinter.DeviceID = strOldPrinterID) Then
            Set objNetwork = CreateObject("WScript.Network")
'           --Use that index we made to assign the new printer to a variable--
            strNewPrinterUNC = strNewServer & colNewPrinterList(NumPosition)
'           --Test to see if the new printer has already been set up (since we are many-to-one)--
'           --Also skip to delete if new printer is marked REMOVE--
            If (colNewPrinterList(NumPosition) <> "REMOVE") Then
                Set colComparePrinters = objWMIService.ExecQuery _
                    ("Select * From Win32_Printer Where Network = True")
                AddThisPrinter = "Yes"
                For Each objExistingPrinter in colComparePrinters
                    If (objExistingPrinter.DeviceID = strNewPrinterUNC) Then
                        AddThisPrinter = "No"
                        Exit For
                    End If
                Next
'           --Since printer doesn't exist, let's add it now--
                If (AddThisPrinter = "Yes") Then
                    Set objNetwork = CreateObject("WScript.Network") 
                    objNetwork.AddWindowsPrinterConnection strNewPrinterUNC
                End If
'           --We want to assign the equivalent printer as default after updating it--
                If objPrinter.Attributes And DefaultPrinter Then
                    objNetwork.SetDefaultPrinter strNewPrinterUNC
                End If
            End If
'           --Now delete the old printer, since that server is going away--
            objNetwork.RemovePrinterConnection objPrinter.DeviceID, true, true
        End If
'       --And increment our index into the new printer names table
        NumPosition = NumPosition + 1
    Next
Next

“Awesome! That worked perfectly, Jim!… Wait… It worked when I tested it as Administrator. But when I put it in the logon script, the screen goes black for like 10 minutes and it doesn’t work!”.

You’ve just run into a little “feature” of the User Account Control (UAC) feature of Windows 7+. Users need elevated privileges to add a printer driver. Now, I obviously don’t think we should make all the users admins just to be able to run a script! So, I found a write-up by someone over on MSDN who explains how to work around this, in detail.

Distributed printer allocation script

Scenario:

You have been told that you need to relocate your users from a one-story building to a four-story building.  On the first floor will be one group that shares a printer and their manager.  The second floor will have two groups (each with their own printer) and a manager.  The third floor will be another group with their own printer, part of the accounting group (with their own printer) and the accounting manager.  Floor four will be the rest of accounting.  Each manager will have their own printer.

Solution:

Since the changes are purely physical (location), not a re-org, you will be maintaining your old AD OU structure, while adding a set of groups that are location-based and used purely for printer assignments, as follows:

  • First-Floor
  • Second-Floor
  • Third-Floor
  • Fourth-Floor

For the sake of this scenario, the existing groups we will also rely on are rather generically named (hey, get more creative!):

  • Second-Floor-Sub-Group ‘ Actually named something more descriptive, I hope!
  • Managers ‘ A security group for accessing certain folders, perhaps?  Or e-mail enabled?  Bad!
  • Accounting

I use this structure simply to demonstrate a few basic options for assigning printers in the logon script. This script is a modified version of one that I used for a scenario very similar to this.


' Variable Declarations

Dim WSHNetwork
Dim strUserName    ' Current user
Dim strUserDomain  ' Current User's domain name
Dim ObjPrinterList ' Dictionary of user printer mappings
Dim objGroupList
Dim objUser
Dim strOldServer
Dim strNewServer
Dim strCurrentPrinter
Dim strPrinterToDelete
Dim strDefaultPrinter

strOldServer = "old-print-2k"
strNewServer = "\\new-ps-2k12\"
'
' Wait until the user is really logged in...
'
Set WSHNetwork = WScript.CreateObject("WScript.Network")
strUserName = ""
While strUserName = ""
    WScript.Sleep 100 ' 1/10 th of a second
    strUserName = WSHNetwork.UserName
Wend

' Remove printer mappings that are no longer needed
Set ObjPrinterList = WshNetwork.EnumPrinterConnections
For i = 1 to ObjPrinterList.Count Step 2
    strCurrentPrinter = ObjPrinterList.Item(i)
    wscript.echo strCurrentPrinter
    ' check to see if the printer is on the old server, if so then delete it
    If (InStr(1, strCurrentPrinter, strOldServer, 1) > 0) Then
        strPrinterToDelete = strCurrentPrinter
        WshNetwork.RemovePrinterConnection strPrinterToDelete, true, true
    End If
Next

' Get the users group memberships
Set objGroupList = CreateObject("Scripting.Dictionary")
objGroupList.CompareMode = vbTextCompare
Set objUser = GetObject("WinNT://" _
    & WSHNetwork.UserDomain & "/" _
    & strUserName & ",user")
For Each objGroup In objUser.Groups
    objGroupList.Add objGroup.Name, " , "
Next

' and load the printers based on the users
' group memberships
If (CBool(objGroupList.Exists("First-Floor"))) Then
    ' This floor is just one group with a manager
    WshNetwork.AddWindowsPrinterConnection strNewServer & "1st-Floor-Printer"
    If (CBool(objGroupList.Exists("Managers"))) Then
        WshNetwork.AddWindowsPrinterConnection strNewServer & "1st-Floor-Mgr-Printer"
        WshNetwork.SetDefaultPrinter strNewServer & "1st-Floor-Mgr-Printer"
    Else
        WshNetwork.SetDefaultPrinter strNewServer & "1st-Floor-Printer"
    End If
End If
If (CBool(objGroupList.Exists("Second-Floor"))) Then
    ' This floor has a main group, a specialized group, and one manager
    strDefaultPrinter = "2nd-Floor-Printer"
    WshNetwork.AddWindowsPrinterConnection strNewServer & "2nd-Floor-Printer"
    If (CBool(objGroupList.Exists("Second-Floor-Sub-Group"))) Then
        WshNetwork.AddWindowsPrinterConnection strNewServer & "2nd-Floor-Printer-2"
        strDefaultPrinter = "2nd-Floor-Printer-2"
    End If
    If (CBool(objGroupList.Exists("Managers"))) Then
        WshNetwork.AddWindowsPrinterConnection strNewServer & "2nd-Floor-Mgr-Printer"
        strDefaultPrinter = "2nd-Floor-Mgr-Printer"
    End If
    WshNetwork.SetDefaultPrinter strNewServer & strDefaultPrinter
End If
If (CBool(objGroupList.Exists("Third-Floor"))) Then
    ' This floor has two groups, each with their own printer and manager
    ' The "Accounting" group is split between floors 3 and 4
    If (CBool(objGroupList.Exists("Accounting"))) Then
        WshNetwork.AddWindowsPrinterConnection strNewServer & "3rd-Floor-Acct-Printer"
        strDefaultPrinter = "3rd-Floor-Acct-Printer"
        If (CBool(objGroupList.Exists("Managers"))) Then
            WshNetwork.AddWindowsPrinterConnection strNewServer & "3rd-Floor-Acct-Mgr-Printer"
            strDefaultPrinter = "3rd-Floor-Acct-Mgr-Printer"
        End If
    Else
        WshNetwork.AddWindowsPrinterConnection strNewServer & "3rd-Floor-GroupA-Printer"
        strDefaultPrinter = "3rd-Floor-GroupA-Printer"
        If (CBool(objGroupList.Exists("Managers"))) Then
            WshNetwork.AddWindowsPrinterConnection strNewServer & "3rd-Floor-GroupA-Mgr-Printer"
            strDefaultPrinter = "3rd-Floor-GroupA-Mgr-Printer"
        End If
    End If
    WshNetwork.SetDefaultPrinter strNewServer & strDefaultPrinter
End If
If (CBool(objGroupList.Exists("Fourth-Floor"))) Then
    ' This floor is just the "Accounting" group overflow
    WshNetwork.AddWindowsPrinterConnection strNewServer & "4th-Floor-Acct-Printer"
    WshNetwork.SetDefaultPrinter strNewServer & "4th-Floor-Acct-Printer"
End If