Deploying Printers via GPO/GPP - is there a programmatic option?
For reasons beyond my control, I've been tasked with setting up GPO/GPPs to deploy our 100+ printers to our 1000+ clients.
The good news is that we have over a dozen sites, and for the most part, I'm allowed to push out all printers at site X to all client PCs at site X.
The bad news is that the two ways I know how to do it ("Deploy with Group Policy...", from the print server" and using GPP/Group Policy Preferences) involve vastly more manual work than I'm willing to for this many printers. I can't even seem to select all the printers on a print server and use the Deploy with Group Policy...
option, for example - it expects me to do that one by one, which isn't going to happen. The GPPs are even worse, as it expects me to select a printer's path from the print server and then manually punch in a bunch of information (such as printer IP) that it should be able to get from the printer connection.
My Google-Fu for a script to add all printers on a print server to a GPO/GPP came up empty, and I can't seem to see another way to do this in even a semi-automated fashion, but I'm sticking with the belief that I'm missing something, because there's no way any sane person would chose to manually add hundreds of printers into GPOs.
Ideally, I'd want to find a programmatic way to use the GPPs, but under the circumstances, any solution that doesn't involve dozens of hours manually adding printers would be just great.
Does anyone have a way to do this, or am I going to need to build a PowerShell script and/or trick a subordinate into doing this?
Solution 1:
I googled pretty hard, and even toyed with backup-GPO
in hopes of being able to hack the resultant XML file and reimport it, but I suspect that a PowerShell script is in your future.
It's not that bad. You can generate the printer list from the nearest server and then loop through that and map them.
Something like this:
$net = New-Object -COMObject WScript.Network
$printserver = 'yourserver'
$printerlist = Get-WMIObject -class Win32_Printer -computer $printserver | Where-Object {$_.ShareName -ne $null} | Select-Object ShareName
foreach ($printer in $printerlist)
{
$printerpath = '\\' + $printserver + '\' + $printer.ShareName
#echo $printerpath
$net.AddWindowsPrinterConnection($printerpath)
}
If the printers are named logical things, and there's some logical way to identify the machines, you might be able to refine it more. I used to pick the nearest server based on pulling up the client IP address, for example. If IP address like 10.20.*, go to server1. Etc.
I hope that helps.
Edit:
Looking at @EvanAnderson's documentation, I'm pretty sure that XML is hackable.
Relevant bit of my exported file (with redactions):
<DSObject bkp:Path="CN={GUIDHERE},CN=PushedPrinterConnections,%GPO_MACH_DSPATH%" bkp:SourceExpandedPath="CN={13B9B596-452C-4652-A05D-78EF06610134},CN=PushedPrinterConnections,CN=Machine,CN={44A99FBA-0DB3-484C-808E-3DDAE9932A2B},CN=Policies,CN=System,DC=Domainname,DC=extension" bkp:ObjectClass="msPrint-ConnectionPolicy">
<DSAttributeMultiString bkp:DSAttrName="showInAdvancedViewOnly">
<DSValue><![CDATA[TRUE]]></DSValue>
</DSAttributeMultiString>
<DSAttributeMultiString bkp:DSAttrName="uNCName">
<DSValue><![CDATA[\\Servername\PrinterShareName]]></DSValue>
</DSAttributeMultiString>
<DSAttributeMultiString bkp:DSAttrName="serverName">
<DSValue><![CDATA[\\Servername]]></DSValue></DSAttributeMultiString>
<DSAttributeMultiString bkp:DSAttrName="printAttributes">
<DSValue><![CDATA[0]]></DSValue>
</DSAttributeMultiString>
<DSAttributeMultiString bkp:DSAttrName="printerName">
<DSValue><![CDATA[PrinterShareName]]></DSValue>
</DSAttributeMultiString>
</DSObject>
Solution 2:
Powershell management of Group Policy sucks w/o third-party (commercial) products, in my opinion.
I think you're stuck slinging through the XML (or in HTML if you prefer) in Group Policy Objects to do what you're looking for.
Fortunately the XML doesn't look that terrifying. The per-printer UID value (which I believe is what @KatherineVillyard is referring to in her comment) is just a random GUID generated for each printer referenced in the XML.
Here's some sample Powershell code, shamelessly modeled after Katherine's code:
@"
<?xml version="1.0" encoding="utf-8"?>
<Printers clsid="{1F577D12-3D1B-471e-A1B7-060317597B9C}" disabled="0">
"@
$net = New-Object -COMObject WScript.Network
$printserver = 'print-server'
$printerlist = Get-WMIObject -class Win32_Printer -computer $printserver | Where-Object {$_.ShareName -ne $null} | Select-Object ShareName
foreach ($printer in $printerlist)
{
$date = Get-Date
echo ' <SharedPrinter clsid="{9A5E9697-9095-436d-A0EE-4D128FDFBCE5}"'
' name="' + $printer.ShareName + '"' | echo
' status="' + $printer.ShareName + '"' | echo
echo ' image="2"'
' changed="' + $date + '"' | echo
$ng = [GUID]::NewGuid().ToString('B')
' uid="' + $ng + '">' | echo
echo ' <Properties'
echo ' action="R"'
echo ' comment=""'
' path="\\' + $printserver + '\' + $printer.ShareName + '"' | echo
echo ' location=""'
echo ' default="1"'
echo ' skipLocal="1"'
echo ' deleteAll="0"'
echo ' persistent="0"'
echo ' deleteMaps="0"'
echo ' port=""/>'
echo ' </SharedPrinter>'
}
@"
</Printers>
"@
(I write really, really ugly Powershell code.)
I have not actually tried to have the GPP CSE parse this XML. The XML does validate, at least.
I'm starting to think about writing some monstrosity using Get-GPO
and parsing out the GUID to get to the filesystem path for the GPO in the SYSVOL but, given that I need to do some real work this evening, I think I'm going to leave that as an exercise for the reader. >smile< It should be highly feasible, though.
Solution 3:
I recently undertook a similar project and after looking into good ol' GPO push method vs. newer GPP vs. scripting, I chose for scripting the whole thing. I have no idea what would work best for you but here are a couple of pointer for you:
-
Ideally use a client with a new OS (Windows 8/2012+) to connect to print servers and get Printer information from Print Servers:
Get-Printer -computer PrintServerName
Use AD Security groups to map print queues to computers. So let's say you have a print queue (you gathered from above command) named \PrintServer1\MyColorPrinter123, create a security group like printer.group.PrintServer1.MyColorPrinter123, and add the computers to that group
-
In the startup script, have a function to check group membership of the computer when it is coming up, and see if it is part of any printer groups. If it is , use the built-in printui.exe (or printui.dll) commands to map the printer like this
Invoke-Expression 'rundll32 printui.dll,PrintUIEntry /ga /n"\PrintServer1\MyColorPrinter123" /q'
Once the computer is up, that print spooler service will push the 'print connection', which is what GPOs used to do, to any user that's going to login.
You can go a lot more detailed** but on a high level that's what it takes.
** I created a GUI that allows users (techs really) to choose any Print Server, and it gives them list of printers on that server. If they choose one, they can see all the properties of it. That info comes from Get-Printer I mentioned above. If you export that data as a csv, you could then re-use it to display information..
** Techs use that GUI to send a request to add a computer to printers that it is supposed to connect. This is a 'request', b/c they do not have permissions in AD.
** A simple backend script watches the folder and add the computer to the printer group I mentioned above. So, if you already know who should get which printer, great, you can easily do it. Adding computers to groups is a simple job with AD Cmdlets.
** You can also schedule a job to check the print servers to see if there are any new print queues, and compare them to you AD groups.
So, creating a 'managed' solution is a bit involved, but it's easy to start with the basics and keep on adding to have a very flexible, easy to use system that does not involve GPOs... just a bit PowerShell