Dude, Where Are My Printers?

I have been meaning to get this blogged for a while, so I am finally taking a break from some other things to get it out here.

I was recently asked to help identify where printers were located in an Active Directory domain.  The person asking me has various lists of printers and queues that had been set up over a period of time, but didn’t have a good way to gather the information to verify the lists.  Being the proponent of Powershell that I am, I suggested we use that to try to gather some information.

To begin with, I started looking at how I could gather the print queue information from Active Directory.  As it turns out, there is a query filter for an objectCategory of “printQueue.”  So, I set out to design the Directory Services query to gather the information from all of Active Directory.  This snippet shows how I set that up.

$root = New-Object system.DirectoryServices.DirectoryEntry
$search = New-Object system.DirectoryServices.DirectorySearcher($root)
$search.PageSize = 1000
$search.Filter = "(objectCategory=printQueue)"
$search.SearchScope = "Subtree"
$search.SearchRoot = $root
$colProplist = "Name","UNCName","PortName","PrintShareName","ShortServerName"
foreach ($i in $colProplist){$search.PropertiesToLoad.Add($i) | out-null }
$colResults = $search.FindAll()

This is just a standard query against Active Directory.  Instead of pulling all properties of the AD records, I have only requested the Name, UNCName, PortName, PrintShareName, and ShortServerName properties. 

The next thing we will do is create Powershell Custom Objects.  We will do this using a Foreach block that will allow us to create a local array of objects.  I will set a Begin block in the loop that will collect all the different print queue objects and then output the collection at once.

$colResults | foreach-object -Begin {$i=0;$Results=@()} `
 -Process {
 $i += 1
 $properties = @{
  Name=$_.Properties.Item("Name")[0];
  UNCName=$_.Properties.Item("UNCName")[0];
  PortName=$_.Properties.Item("PortName")[0];
  IPAddress= (Get-IP -hostname $_.Properties.Item("PortName")[0]);
  PrintShareName=$_.Properties.Item("PrintShareName")[0];
  ServerName=$_.Properties.Item("ShortServerName")[0]
 }
 $Result = New-Object -TypeName PSObject -Property $properties
 $results += $Result
 Write-Progress -Activity "Gathering Printer Queues from AD: $($_.Properties.Item("Name")[0])" -CurrentOperation "($i of $($colResults.Count))" -Status Progress -PercentComplete ($i/$colResults.Count*100)
} -End {Write-Output $Results}

As I was building this script, I found that it took a really long time to gather the objects from Active Directory.  So long, in fact, that I began to wonder if it was really working.  To help out, I set up the “Write-Progress” cmdlet to report the status of the script.  Since I have the number of objects in total from Active Directory, I can calculate the percent complete and show the current item being processed. 

There is also a new function that you might notice on the “IPAddress” line.  The code for that function is listed here.

Function Get-IP {
 Param (
  [string]$hostname
 )
 if ($IPLookup.Contains($hostname))
 {
  Write-Verbose "Hashtable contains entry. ($hostname)"
  Write-Output $IPLookup[$hostname]
 }
 elseif ($hostname.startswith("IP_"))
 {
  Write-Verbose "Hostname starts with 'IP_' ($hostname)"
  if (!($hostname.Substring(3) -as [Net.IPAddress]))
  {
   Write-Verbose "Substring appears to NOT be an IP Address. ($($hostname.Substring(3)))"
   try{
    $output = [System.Net.Dns]::GetHostAddresses($hostname) | Select-Object  -expand IPAddressToString
    Write-Output $output
    $IPLookup.Add($hostname, $output)
   }
   catch{
    Write-Verbose "Cannot resolve to IP."
    Write-Output "-"
    $IPLookup.Add($hostname, "-")
   }
  }
  else
  {
   Write-Verbose "Substring appears to be an IP Address. ($($hostname.substring(3)))"
   Write-Output $hostname.Substring(3)
   $IPLookup.Add($hostname, $hostname.Substring(3))
  }
 }
 else
 {
  Write-Verbose "Assumed to be a hostname, resolve to IP Address ($hostname)"
  try{
   $output = [System.Net.Dns]::GetHostAddresses($hostname) | Select-Object  -expand IPAddressToString
   Write-Output $output
   $IPLookup.Add($hostname, $output)
  }
  catch{
   Write-Verbose "Cannot resolve to IP."
   Write-Output "-"
   $IPLookup.Add($hostname, "-")
  }
 }
}

This function tries to resolve the IP Address for the hostname, if it can be found.  Another issue I found with resolving so many different print queues is the time it takes to resolve each one.  Since I was able to see many of the same hostnames when I was testing and validating the resulting data, I chose to use a hashtable to cache the results.  If the hostname has already been found and resolved, the hashtable will have that information already in memory and a call to the network stack will not be necessary.  This had the effect of greatly increasing the speed of processing.

Unfortunately, this script was not able to record every print queue in the enterprise, but it was able to help identify greater than 85% of the print queues.  With a number of approximately 14,000, any help is welcome.  I hope this helps someone else.

Here is the complete code as I used it:


Function Get-IP {
 Param (
  [string]$hostname
 )
 if ($IPLookup.Contains($hostname))
 {
  Write-Verbose "Hashtable contains entry. ($hostname)"
  Write-Output $IPLookup[$hostname]
 }
 elseif ($hostname.startswith("IP_"))
 {
  Write-Verbose "Hostname starts with 'IP_' ($hostname)"
  if (!($hostname.Substring(3) -as [Net.IPAddress]))
  {
   Write-Verbose "Substring appears to NOT be an IP Address. ($($hostname.Substring(3)))"
   try{
    $output = [System.Net.Dns]::GetHostAddresses($hostname) | Select-Object  -expand IPAddressToString
    Write-Output $output
    $IPLookup.Add($hostname, $output)
   }
   catch{
    Write-Verbose "Cannot resolve to IP."
    Write-Output "-"
    $IPLookup.Add($hostname, "-")
   }
  }
  else
  {
   Write-Verbose "Substring appears to be an IP Address. ($($hostname.substring(3)))"
   Write-Output $hostname.Substring(3)
   $IPLookup.Add($hostname, $hostname.Substring(3))
  }
 }
 else
 {
  Write-Verbose "Assumed to be a hostname, resolve to IP Address ($hostname)"
  try{
   $output = [System.Net.Dns]::GetHostAddresses($hostname) | Select-Object  -expand IPAddressToString
   Write-Output $output
   $IPLookup.Add($hostname, $output)
  }
  catch{
   Write-Verbose "Cannot resolve to IP."
   Write-Output "-"
   $IPLookup.Add($hostname, "-")
  }
 }
}

$IPLookup = @{}
$root = New-Object system.DirectoryServices.DirectoryEntry
$search = New-Object system.DirectoryServices.DirectorySearcher($root)
$search.PageSize = 1000
$search.Filter = "(objectCategory=printQueue)"
$search.SearchScope = "Subtree"
$search.SearchRoot = $root

$colProplist = "Name","UNCName","PortName","PrintShareName","ShortServerName"
foreach ($i in $colProplist){$search.PropertiesToLoad.Add($i) | out-null }

$colResults = $search.FindAll()
$colResults | foreach-object -Begin {$i=0;$Results=@()} `
 -Process {
 $i += 1
 Write-Verbose "Items in Hash table: $($IPLookup.Count)"
 $properties = @{
  Name=$_.Properties.Item("Name")[0];
  UNCName=$_.Properties.Item("UNCName")[0];
  PortName=$_.Properties.Item("PortName")[0];
  IPAddress= (Get-IP -hostname $_.Properties.Item("PortName")[0]);
  PrintShareName=$_.Properties.Item("PrintShareName")[0];
  ServerName=$_.Properties.Item("ShortServerName")[0]
 }
 $Result = New-Object -TypeName PSObject -Property $properties
 $results += $Result
 Write-Progress -Activity "Gathering Printer Queues from AD: $($_.Properties.Item("Name")[0])" -CurrentOperation "($i of $($colResults.Count))" -Status Progress -PercentComplete ($i/$colResults.Count*100)
} -End {Write-Output $Results}

Advertisements
This entry was posted in Active Directory, ADSI, PSObjects and tagged , , , , , . Bookmark the permalink.

2 Responses to Dude, Where Are My Printers?

  1. Amol says:

    Not working for me 😦

Comments are closed.