Over a million developers have joined DZone.

Hey WMI, Where Can I Print

·

Windows Management Instrumentation, or WMI for short, is a very powerful and often under used technology. I am simply fascinated by how much you can accomplish using WMI. It is mostly intended for computer management tasks but it can be used for much more. Some examples include getting all kinds of computer information, managing system services and application. This article will cover a possible solution for listing all available printers on a particular computer and show you to get this information through ColdFusion. WMI can be used through VBScript, C++ and .NET. However since part of my current work is done in .NET, the solution relies on .NET and IIS. Further more, here are the requirements that the we will satisfy:

  • List all available printers including shared printers
  • Allow for filtering the list of printers by the printer name
  • Be language independent

Accomplishing the first two tasks is fairly easy with WMI. So lets get started.

List All Available Printers

This is pretty straight forward using C# provided that you reference the System.Management namespace and add the appropriate "using" directive:

using System.Management;


Next, we need to declare some variable to query WMI:

ObjectQuery oq;
ConnectionOptions co;
ManagementScope ms;
ManagementObjectSearcher mos;
ManagementObjectCollection moc;


Set the connection options:

co = new System.Management.ConnectionOptions();
co.Authentication = System.Management.AuthenticationLevel.Default;
co.Impersonation = System.Management.ImpersonationLevel.Impersonate;


Connect to the local machine:

// Set the management scope to the local computer
ms = new ManagementScope("\\\\" + Environment.MachineName, co);


And get the list of printers:

// Create an object query to get the printer list
oq = new ObjectQuery("select * from Win32_Printer");

mos = new ManagementObjectSearcher(ms, oq);
moc = mos.Get();


What is left to do is just loop through the management objects found and display/aggregate the printer properties:

if (moc != null)
{
// Loop through the found printers and collect the information
foreach (ManagementObject mo in moc)
{
// Do something with the printer name
// mo.Properties["Name"].Value.ToString();
}
}

Filtering By Printer Name

There is two ways that this can be accomplished. Let's explore both.

  1. Using WMI query clause.
    WMI on Windows XP or newer supports the "like" operator so filtering the printer list by the printer name or a partial printer name is very trivial:

    // Create an object query to get the printer list
    oq = new ObjectQuery("select * from Win32_Printer where Name like \"%myPrinterName%\"");

    Where "myPrinterName" is the full name or part of the name of the printer. Notice to use of "%" as those work as a wildcard. 
  2. Using simple string compare.
    Since there are plenty of servers still using Windows 2000 the string compare method is more compatible:

    if (moc != null)
    {
    // Loop through the found printers and collect the information
    foreach (ManagementObject mo in moc)
    {
    if (mo.Properties["Name"].Value.ToString().ToLower().Contains("myPrinterName"))
    {
    // Do something with the printer name
    // mo.Properties["Name"].Value.ToString();
    }
    }
    }

Language Independence

A simple way to implement languages independence is through a web service. So we take the code above and wrap it in a web service. The two addition are the separation of the code in two functions (getPrinters() and getPrintersWithNameFilter()) and the addition of a small helper function called saveProperties() . Here is the final result:

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Management;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Reflection;

namespace systemInfo
{
/// <summary>
/// Contains system related functions
/// </summary>
[WebService(Namespace = "http://webapps/systemInfo")]
[Description("Contains a set of printer related functions")]
public class systemInfo : System.Web.Services.WebService
{
/// <summary>
/// Returns a list of available printers
/// </summary>
/// <returns>systemData object serialized to XML</returns>
[WebMethod]
[Description("Returns a list of available printers")]
public systemData getPrinters()
{
return getPrintersWithNameFilter(null);
}

/// <summary>
/// Returns a list of available printers filtered by the name specified
/// </summary>
/// <param name="nameFilter">The name of the printer to filter by</param>
/// <returns>systemData object seralized to XML</returns>
[WebMethod(MessageName = "getPrintersWithNameFilter")]
[Description("Returns a list of available printers filtered by the name specified")]
public systemData getPrintersWithNameFilter(string nameFilter)
{
ObjectQuery oq;
ConnectionOptions co;
ManagementScope ms;
ManagementObjectSearcher mos;
ManagementObjectCollection moc;
int counter = 0;
ArrayList properties = null;
systemDataPrinter printer = null;
systemData systemDataInstance = new systemData();

if (!String.IsNullOrEmpty(nameFilter))
{
nameFilter = nameFilter.ToLower();
}

co = new System.Management.ConnectionOptions();
co.Authentication = System.Management.AuthenticationLevel.Default;
co.Impersonation = System.Management.ImpersonationLevel.Impersonate;

// Set the management scope to the local computer
ms = new ManagementScope("\\\\" + Environment.MachineName, co);

// Create an object query to get the printer list
oq = new ObjectQuery("select * from Win32_Printer");

mos = new ManagementObjectSearcher(ms, oq);
moc = mos.Get();

if (moc != null)
{
// Create an array of printer objects to store the printer data
systemDataInstance.printer = new systemDataPrinter[moc.Count];

// Loop through the found printers and collect the information
foreach (ManagementObject mo in moc)
{
properties = new ArrayList();
properties.Add(mo.Properties["Name"]);
properties.Add(mo.Properties["Location"]);
properties.Add(mo.Properties["ShareName"]);
properties.Add(mo.Properties["Description"]);
properties.Add(mo.Properties["Caption"]);

if (!String.IsNullOrEmpty(nameFilter))
{
// Filter the printers returned by the name filter specified
// Also, exclude printers that start with "__" since those are printer sessions
if (mo.Properties["Name"].Value.ToString().ToLower().Contains(nameFilter)
&& !mo.Properties["Name"].Value.ToString().StartsWith("__"))
{
printer = new systemDataPrinter();

// Save all the properties from the array list
// to the printer object
saveProperties(properties, ref printer);

// Add the current printer object to the array of printers
systemDataInstance.printer[counter] = printer;

counter++;
}
}
// Exclude printers that start with "__" since those are printer sessions
else if (!mo.Properties["Name"].Value.ToString().StartsWith("__"))
{
printer = new systemDataPrinter();

// Save all the properties from the array list
// to the printer object
saveProperties(properties, ref printer);

// Add the current printer object to the array of printers
systemDataInstance.printer[counter] = printer;

counter++;
}
}
}

return systemDataInstance;
}

/// <summary>
/// Saves the properties passed by the ManagementObject into
/// an instance of the printer object
/// </summary>
/// <param name="properties">A list of printer properties</param>
/// <param name="printer">The printer object where the properties will be stored</param>
private void saveProperties(ArrayList properties, ref systemDataPrinter printer)
{
PropertyInfo pi = null;
string value = String.Empty;

foreach (PropertyData prop in properties)
{
value = Convert.ToString(prop.Value);

// Get the printer property corresponding to the current property name
pi = printer.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

if (pi != null)
{
// Set the value of the current property through reflextion
pi.SetValue(printer, value, null);
}
}
}
}
}

Consuming with ColdFusion

Getting the information from the web service and into ColdFusion readable format is as easy as calling any other web service. First you create a web service object and point it to the WSDL URL:

<cfset systemInfoService = createObject("webservice", "http://webapps/systemInfo/systemInfo.asmx?WSDL") />


Next, you call the getPrinters() function to get a list of all the printers installed on the computer:

<cfset printersArray = systemInfoService.getPrinters().getPrinter() />


the getPrinter() on the end returns an array of printer objects:

getPrintersCFDump

If you like to filter the list of printers and get only the printers that are installed on a different server but shared locally, you just need to pass a string containing part of the server name:

<cfset printersArray = systemInfoService.getPrintersWithNameFilter(nameFilter = "\\ntserver_hq5").getPrinter() />


Where "ntserver_hq5" is the name of the server where the printer is actually installed. For network printers installed locally, WMI returns the server name as part of the printer name.

Once you perform the web service call, you simply output the printers array as follows:

<cfoutput>

<ul>
<cfloop from="1" to="#arraylen(printersArray)#" index="i">
<li>
#printersArray[i].getName()#
<ol>
<li>
Location: #printersArray[i].getLocation()#
</li>
<li>
ShareName: #printersArray[i].getShareName()#
</li>
<li>
Description: #printersArray[i].getDescription()#
</li>
<li>
Caption: #printersArray[i].getCaption()#
</li>
</ol>
</li>
</cfloop>
</ul>

</cfoutput>
getPrintersWithNameFilterOutput

Implementation Details

The service returns four properties for each printer. Here is an example XML:

<?xml version="1.0" encoding="utf-8" ?>
<systemData xmlns:xs="http://webapps/systemInfo">
<printer>
<name>t</name>
<location>t</location>
<shareName>t</shareName>
<description>t</description>
<caption>t</caption>
</printer>
<printer>
<name>t</name>
<location>t</location>
<shareName>t</shareName>
<description>t</description>
<caption>t</caption>
</printer>
</systemData>


WMI actually returns quite a few properties for each printer. However, I chose only the Name, Location, ShareName, Description and Caption as relevant.

Once the sample XML is defined, Visual Studio makes it pretty easy to generate a XML Schema from your sample XML by going to the "XML" menu and clicking on "Create Schema"

createXMLSchema

You end up with the following schema:

<?xml version="1.0" encoding="utf-8"?>
<schema xmlns:xs="http://webapps/systemInfo" attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns="http://www.w3.org/2001/XMLSchema">
<element name="systemData">
<complexType>
<sequence>
<element maxOccurs="unbounded" name="printer">
<complexType>
<sequence>
<element name="name" type="string" />
<element name="location" type="string" />
<element name="shareName" type="string" />
<element name="description" type="string" />
<element name="caption" type="string" />
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>


To convert the schema to a C# class so we can populate it at runtime, serialize it and return it we can use the xsd.exe utility with the following arguments:

C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin\xsd.exe "path\to\schema.xsd" /classes /nologo /out:"path/to/output.cs"


This way we end up with a serializable C# class which can be populated at runtime and still be serialized as our example XML. I called mine systemData and used it above as:

// Create new instance of the systeData class
systemData systemDataInstance = new systemData();

// Create a new instance of the systemDataPrinter class
systemDataPrinter printer = null;

// Create an array of printer objects to store the printer data
systemDataInstance.printer = new systemDataPrinter[moc.Count];


where moc.Count is the number of WMI ManagementObject that was returned for the WMI query.

The last point worth mentioning is inside the saveProperties() function. The function takes an array list of properties, loops over them and for each one sets the according property in the systemDataPrinter class. It does so by using reflextion:

// Get the printer property corresponding to the current property name
pi = printer.GetType().GetProperty(prop.Name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

if (pi != null)
{
// Set the value of the current property through reflextion
pi.SetValue(printer, value, null);
}

Gotchas

  1. To get network printers listed you have to have the printer installed on the machine under a local account and configure the web service to use the account when performing the query. You use the "impersonate" tag and specify the user and the password:

    <identity impersonate="true" userName="user@domain.com" password="password" />
  2. Once you use xsd.exe to generated the C# serializable class, you have to edit it and add the namespace for the root node attribute:

    [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://webapps/systemInfo", IsNullable = false)]
  3. The web service has to be setup in IIS by creating an application and setting the directory security to allow anonymous access.
    iisCreateApplication iisSetDirectorySecurity
  4. The web service has to be configured to allow calls through HTTP get inside the Web.config:

    <webServices>
    <protocols>
    <add name="HttpGet"/>
    </protocols>
    </webServices>

Downloads

Web Service Implementation: systemInfo.printer.zip

Topics:

Published at DZone with permission of Boyan Kostadinov, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}