System.DirectoryServices and System.Management to add printer from server?

wcq

New member
Joined
Nov 14, 2006
Messages
4
Programming Experience
Beginner
Hello! I'm new to VB.NET Forums. Well, I've lurked for awhile, just never posted anything. Sorry :D. I am a novice, so please bear with me. I'm having some difficulty with a Windows form I'm working on and was hoping someone here could point me in the right direction, as I've been unable to figure this one out via the usual searches and Googles.

I'm attempting to pull all printers on the print server into a combobox. On select changed, I would like the property "location" to show in a label box below the combo (so the user knows where the printer in question is located in the building). On button click, I'd like to set the selected printer as default. All users on the system have access to the print server, so permissions should not be a concern.

I've managed to populate the combobox with the printer names (although they're all prefixed with DC-WALLY-, eg. DC-WALLY-HP_Printer_Name_Whatever). On button click, the exception is always thrown, so I'm doing something wrong. Also I can't seem to get the selectindexchanged to work.

Can anyone experienced with System.DirectorServices and/or System.Management lend a hand? Code is below. Thanks for your time.

Form1 contains 1 combobox (ComboBox1), 1 button (Button1), and 1 label (you guessed it, Label1)

--Form1.vb--
VB.NET:
Imports System.DirectoryServices
Imports System.Management
Imports System.Data
Public Class Form1
    Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        Try            
            Dim de As New DirectoryEntry("LDAP://CN=DC-WALLY,OU=Domain Controllers,DC=biblioteca,DC=local")
            Dim src As New DirectorySearcher("(&(objectCategory=printQueue))")            
            src.SearchRoot = de
            src.SearchScope = SearchScope.Subtree
            Dim res As SearchResult
            
            For Each res In src.FindAll()
                Me.ComboBox1.Items.Add(res.Properties("Name")(0))
            Next res
        Catch ex As Exception
            Me.Label1.Text = "No printers found."
        End Try
    End Sub

Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
    'Not sure how to do this part
    'On select index changed, show properties("location")(0) for selected printer name
End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        SetDefaultPrinter(Me.ComboBox1.SelectedIndex)
End Sub
Sub SetDefaultPrinter(ByVal strPrinter As String)
        strPrinter = Me.ComboBox1.SelectedIndex
        Dim wmi As ManagementClass
        Dim obj As ManagementObject
        Dim gotit As Boolean = False

        wmi = New ManagementClass("\root\cimv2:Win32_Printer")
        For Each obj In wmi.GetInstances
            If obj("Name") = strPrinter Then
                ' The SetDefaultPrinter method is new to WinXP/Win2003
                obj.InvokeMethod("SetDefaultPrinter", Nothing)
                gotit = True
            End If
        Next

        If Not gotit Then
            MsgBox("Error: No printer by that name found.")
        End If
End Sub
(note, I had tried the code below before, but it didn't work either:
VB.NET:
'Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click
'        Dim wsh As Object
'        Dim printer = Me.ComboBox1.SelectedItem
'        
'        Try
'            wsh = CreateObject("WScript.Network")
'            wsh.SetDefaultPrinter(printer)
'            wsh = Nothing
'            Me.Label1.Text = "Default Printer Set To " & printer
'        Catch ex As Exception
'            Me.Label1.Text = "Error!!"
'        End Try
'    End Sub
)
 
It is possible to do this all with WMIs Win32_Printer class you have discovered. You can ask it for properties Name, Location and DeviceID, and use method SetDefaultPrinter (using DeviceID in path). I recently discovered a great tool for generating WMI code that almost write all code you need, see http://www.vbdotnetforums.com/showthread.php?t=13972
The tool can be set to VB.Net code, it can target local or remote machine, there is one tab for class/property queries, one for method invocation. You can try out codes directly from this tool. Try it out.
 
Wow, thanks!

I've managed to pull all the printers to the combobox, and list the name/location in the label, but it's a bit slow... probably because I used two scopes. But I couldn't figure out how to do it with just one. Also, I don't quite understand how to set the default printer once I've got it selected. I know I need to use SetDefaultPrinter, but I can't get it to work. :confused: Here's what I've got so far:

VB.NET:
Imports System.Management
Imports System.Data

Public Class Form1
    Dim foo As Boolean = False

    Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load

        Try
            Dim connection As New ConnectionOptions
            connection.Username = "Administrator"
            connection.Password = "*********"
            connection.Authority = "ntlmdomain:biblioteca.local"

            Dim scope As New ManagementScope("\\10.1.1.8\root\CIMV2", connection)
            scope.Connect()

            Dim query As New ObjectQuery("SELECT * FROM Win32_Printer")

            Dim searcher As New ManagementObjectSearcher(scope, query)



            For Each queryObj As ManagementObject In searcher.Get()
                Me.ComboBox1.Items.Add(queryObj("DeviceID"))                
            Next
        Catch ex As Exception
            Me.Label1.Text = "Error"
        End Try
    End Sub

    Private Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged        

        Dim setPrinter = Me.ComboBox1.SelectedItem        
        Dim connection2 As New ConnectionOptions
        connection2.Username = "Administrator"
        connection2.Password = "*******"
        connection2.Authority = "ntlmdomain:biblioteca.local"

        Dim scope2 As New ManagementScope("\\10.1.1.8\root\CIMV2", connection2)
        scope2.Connect()

        Dim query2 As New ObjectQuery("SELECT * FROM Win32_Printer WHERE DeviceID = " & Chr(34) & setPrinter & Chr(34))

        Dim searcher2 As New ManagementObjectSearcher(scope2, query2)

        For Each queryObj As ManagementObject In searcher2.Get()
            Me.Label1.Text = "Name: " & queryObj("Name") & vbCrLf & "Location: " & queryObj("Location")
        Next

        foo = True
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        If foo = True Then
            'Dim setDefault As ManagementObject
            'setDefault.InvokeMethod("SetDefaultPrinter")
            '
        End If
    End Sub
    
End Class
Any idea what I'm doing wrong? Basically I just want to take whatever Me.Combobox.SelectedItem is and set that as the default. Any suggestions or pointers on cleaning this form up would be greatly appreciated.

-edit-
I think another reason I'm having problem is that I haven't used the AddPrinterConnection method yet? I would need to create a new management object, add a connection to the selected printer, then I could use the SetDefaultPrinter method? Sorry for all the questions. I'm still pretty new to this.
 
Last edited:
Yes, you can set this up also with the Code Creator, use the "Execute a method" tab. You'll get that standard connection stuff and these two important code lines:
VB.NET:
Dim classInstance As New ManagementObject(scope, New ManagementPath("Win32_Printer.DeviceID='printerId'"), Nothing)
Dim outParams As ManagementBaseObject = classInstance.InvokeMethod("SetDefaultPrinter", Nothing, Nothing)
What you do with Selected index changed, querying WMI for again and again for the same info is valid, but there also exist something in programming called variables and arrays and collections you know ;) - where you store stuff you re-use, especially the info that takes some processing to get hold of.. So I would suggest you work some with these fundamental programming techniques being variables of various types.

Another tip in the same vein with WMI you for example do this query:
SELECT * FROM Win32_Printer
This gets all properties for this class, these property values in turn may come from different system places/functions. To economize and speed things up you can limit the query for only what you need, in this case only DeviceID (required for SetDefaultPrinter method) and Location like this:
SELECT DeviceID,Location FROM Win32_Printer
 
Thanks for the tips. I need to get used to the differences between VB6 and VB.NET so that I can use/reuse variables. I could then populate an array with DeviceID and Location, then rather than query WMI again, I can just refer to the array to retrieve the information, correct? I think I'm slowly learning :D. Bear with me, I'll figure this out and report back here.
 
update!

It works (mostly)! On form load the first combobox populates with network printers. The user selects one to add with button click. Then the second combobox populates with printers added to local machine, and from those can choose to set the default. The only problem that remains is displaying both the DeviceID and Location in the Label controls associated with the comboboxes' SelectIndexChanged events.

I'm really not sure if I should use a collection or an array for the printer properties and combobox. I'm currently using a collection, but I can only get it to work with DeviceID. If I attempt to throw Location in there, the exception catches. For this reason, I'm thinking an array may work best.

Something like... but it's not working yet:
VB.NET:
Public NetworkPrinterArray As New ArrayList()
    Public LocalPrinterArray As New ArrayList()
    Public Class NetworkPrinter
        Public NetworkPrinterID As String
        Public NetworkPrinterLoc As String
        Public Sub New(ByVal strNPrinterID As String, ByVal strNPrinterLoc As String)
            Me.NetworkPrinterID = strNPrinterID
            Me.NetworkPrinterLoc = strNPrinterLoc
        End Sub
        Public ReadOnly Property nDeviceID() As String
            Get
                Return NetworkPrinterID
            End Get
        End Property
        Public ReadOnly Property nLocation() As String
            Get
                Return NetworkPrinterLoc
            End Get
        End Property
        Public Overrides Function ToString() As String
            Return Me.nDeviceID + " - " + Me.nLocation
        End Function
    End Class

For Each queryObj As ManagementObject In searcher.Get()
    LocalPrinterArray.Add(New NetworkPrinter(queryObj("DeviceID"),queryObj("Location")))
Next

Me.Combobox1.DataSource = LocalPrinterArray
Me.Combobox1.DisplayMember = nDeviceID & "-" &  nLocation
Me.Combobox1.ValueMember = nDeviceID


Working form code:
VB.NET:
Imports System.Management
Imports System.Data

Public Class Form1
    Public Sub Form_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
        Try
            Dim connection As New ConnectionOptions
            connection.Username = "Administrator"
            connection.Password = "*******************"
            connection.Authority = "ntlmdomain:biblioteca.local"

            Dim scope As New ManagementScope("\\10.1.1.8\root\CIMV2", connection)
            scope.Connect()

            Dim query As New ObjectQuery("SELECT DeviceID,Location FROM Win32_Printer")

            Dim searcher As New ManagementObjectSearcher(scope, query)

            
            
            For Each queryObj As ManagementObject In searcher.Get()
                

                PrinterCollection.Add(queryObj("DeviceID"))
                
            Next

            Me.ComboBox1.DataSource = PrinterCollection
            
            Me.ComboBox1.SelectedIndex = 0
        Catch ex As Exception
            Me.Label1.Text = "Error finding remote printers - check permissions."
        End Try
    End Sub
    Public Sub ComboBox1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox1.SelectedIndexChanged
        Me.Label1.Text = PrinterCollection(Me.ComboBox1.SelectedIndex + 1)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim PrinterToSet As String
        PrinterToSet = Me.Label5.Text

        Dim classInstance As New ManagementObject("root\CIMV2", "Win32_Printer.DeviceID='" & PrinterToSet & "'", Nothing)
        
        Dim outParams As ManagementBaseObject = classInstance.InvokeMethod("SetDefaultPrinter", Nothing, Nothing)

    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim connection As New ConnectionOptions
        connection.Username = "Administrator"
        connection.Password = "***************"
        connection.Authority = "ntlmdomain:biblioteca.local"

        Dim classInstance As New ManagementClass("root\CIMV2", "Win32_Printer", Nothing)

        Dim inParams As ManagementBaseObject = classInstance.GetMethodParameters("AddPrinterConnection")

        Dim printerID As String
        
        printerID = "\\dc-wally\" & Me.Label1.Text

        inParams("Name") = printerID

        Dim outParams As ManagementBaseObject = classInstance.InvokeMethod("AddPrinterConnection", inParams, Nothing)

        Try
            Dim localsearcher As New ManagementObjectSearcher("root\CIMV2", "SELECT DeviceID,Location FROM Win32_Printer")

            For Each queryObj As ManagementObject In localsearcher.Get()
                LocalPrinters.Add(queryObj("DeviceID"))
            Next

            Me.ComboBox2.DataSource = LocalPrinters
            Me.ComboBox2.SelectedIndex = 0
        Catch ex As Exception
            Me.Label5.Text = "Error finding local printers."
        End Try
    End Sub

    Private Sub ComboBox2_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ComboBox2.SelectedIndexChanged
        Me.Label5.Text = LocalPrinters(Me.ComboBox2.SelectedIndex + 1)
    End Sub
End Class
 
Back
Top