Integrating Visio 2007 and Active Directory

Summary: Learn about several ways to integrate Microsoft Office Visio 2007 with Active Directory, to create data-driven diagrams that can update data directly in Active Directory. (29 printed pages)

Visimation, Inc.

January 2007

Applies to: Microsoft Office Visio 2007, Microsoft Visual Studio 2005, Microsoft Windows Server 2003 Active Directory

Contents

  • Overview

  • Building the Add-in Project

  • Customizing the Stencil and Template

  • Writing the Code

  • Running the Visio Add-In

  • Deploying the Visio Add-In

  • Conclusion

  • About the Author

  • Additional Resources

Overview

This article is based on a previous article (Generating Active Directory Diagrams with Visio 2003 and Visual Studio .NET 2003), and is intended to illustrate how you can integrate the Windows Server 2003 Active Directory directory service and Microsoft Office Visio 2007. We update the code from the original article to use Office Visio 2007 and Microsoft Visual Studio 2005, and we take it further by adding more integration with Active Directory to the diagram we created in the original article.

This article is modeled around an IT support worker, who needs a tool to perform basic administrative tasks on Active Directory user objects, such as:

  • Enabling or disabling an account

  • Unlocking an account

  • Resetting an account's password

  • Sending an e-mail message to an account

The user can generate a diagram of all users in a particular organizational unit, and from there can instantly see which accounts are disabled or locked out. Additionally, the user can right-click the User shape and perform the desired actions listed previously.

Building the Add-in Project

Before you can begin building the add-in, ensure that you have the following prerequisites installed:

Creating the Add-in Project

Follow these directions to create the add-in project.

To create the Visio add-in project

  1. Open Visual Studio 2005.

  2. On the File menu, point to New, and then click Project.

  3. In the New Project dialog box, in the Project Types tree, select Visual C#.

    If you have successfully installed the Visio 2007 SDK, you see an icon for Visio add-in or add-on.

  4. Select this icon, enter the project name ActiveDirectoryAddin, and then click OK.

    Figure 1. The New Project dialog box

    The New Project dialog box

The wizard generates a new solution that contains two projects:

  • A Visio add-in project that contains references to the Visio primary interop assemblies and a file named Connect.cs that implements the IDTExtensibility2 interface to connect to Visio as a COM add-in. You work with this project to build logic to generate the diagrams.

  • A setup project that installs the Visio add-in to your computer. When you finish coding the solution, you must compile the setup project and run it to install the new Visio add-in.

The following figure shows both projects displayed in Solution Explorer.

Figure 2. Projects displayed in Solution Explorer

Projects displayed in Solution Explorer

Customizing the Stencil and Template

The recommended way to integrate the data from Active Directory into our diagram is to add Shape Data, Data Graphics, and custom shortcut menus to our shapes. To do this, we alter the Active Directory template and the associated stencil file to create our own template and stencil, and then we customize our shapes with Shape Data and Data Graphics.

Create the Stencil and Template

Follow these directions to set up the template and stencil.

To create the template

  1. Open Visio, and create an Active Directory diagram. To do this, on the File menu, point to New, point to Network, and then click Active Directory.

  2. On the left side, you should see three stencils open. Close the Active Directory Sites and Services stencil and the Exchange Objects stencil. To do this, right-click each stencil title bar, and then click Close.

  3. From the Active Directory Users and Computers stencil, drag a Domain shape, a User shape, and an Organizational Unit shape onto the page.

  4. Close the Active Directory Users and Computers stencil.

  5. Open the Document Stencil. To do this, on the File menu, point to Shapes, and then click Show Document Stencil. You should see three shapes: Domain, User, and Organizational Unit. Because these shapes are now on the Document Stencil, we do not need any other stencils with our template.

  6. Press CTRL+A to select all shapes on the page, and then press the Delete key.

  7. Save your document as a Visio Template (.vst) file named Active Directory Manager.vst. To do this, on the File menu, click Save As. In the file name box, type Active Directory Manager.vst, and then click Save.

Adding Shape Data Fields

Shape Data (formerly called Custom Properties in earlier versions of Visio) is a placeholder to store fields of data inside each Visio shape. When we connect to Active Directory, we want to store the Active Directory entry's properties inside each shape, and Shape Data is the perfect place to do this.

Adding Shape Data is easy, especially with Shape Data sets, which enable you to apply a particular set of Shape Data fields to a group of shapes all at once. We will create two Shape Data sets, one for common Active Directory properties that apply to all of our Active Directory shapes, and another set that applies to user objects. Then, we apply those Shape Data sets to the shapes in our stencil.

To add the Shape Data fields

  1. Ensure that you are running Visio in Developer mode, which presents you with additional options in Shape Data dialog boxes. To do this, on the Tools menu, point to Options, and then click the Advanced Tab. Select Run in Developer Mode, and then click OK.

  2. If it is not already open, open the Shape Data window. To do this, on the View menu, click Shape Data Window.

  3. Right-click inside the Shape Data window, and then click Shape Data Sets.

  4. In the Shape Data Sets window, click Add.

  5. In the Add Shape Data Set dialog box, select Create New Set. For the name of the set, type Common Active Directory Properties. Click OK.

  6. Repeat Step 4 and Step 5 to add another set. For the name of this set, type User AD Properties.

  7. In the Shape Data Sets window, select the Common Active Directory Properties set, and then click Define to open the Define Shape Data dialog box, where you can define the shape data that is stored with Visio shapes.

  8. In the Define Shape Data dialog box, add several shape data items, by using the following instructions:

    1. In the Label box, type LDAP Path. (Spaces are allowed in this box.)

    2. In the Name box, type LDAPPath. (Spaces are not allowed in this box.) The Name field is what we will reference programmatically when we set the Active Directory data in the shape. This text box is not visible in the dialog box if you are not running Visio in Developer mode.

    3. In the Type drop-down list, select String.

    4. In the Prompt box, type The LDAP Path of this object.

  9. Add two more string shape data items. To do this, click New, and then repeat Steps 8a–8d, using the information from Table 1.

    Table 1. First Shape Data set

    Label

    Name

    Type

    Prompt

    DN

    DN

    String

    The Distinguished Name of this object

    Display Name

    DisplayName

    String

    The Display Name of this object

  10. Click OK to return to the Shape Data Sets window.

  11. In the Document Stencil, select all three shapes in the stencil. To do this, left-click each shape while pressing the SHIFT key.

  12. When all shapes in the stencil are selected, in the Shape Data Sets window, select the Common Active Directory Properties set, select Shape selected in stencil, clear the Remove Existing Sets check box, and then click Apply.

  13. In the Shape Data Sets window, select the User AD Properties set, and then click Define. Repeat Steps 8a–8d, using the information from Table 2.

    Table 2. Second Shape Data set

    Label

    Name

    Type

    Prompt

    Email

    Email

    String

    The e-mail address of this user

    Department

    Department

    String

    The department of this user

    Title

    Title

    String

    The title of this user

    Is Disabled

    IsDisabled

    Boolean

    Is the account disabled?

    Is Locked Out

    IsLockedOut

    Boolean

    Is the account locked out?

  14. In the Document Stencil, select only the User shape.

  15. With the User shape selected, select only the User AD Properties set, select the Selected shapes in stencil check box, clear the Remove Existing Sets check box, and then click Apply.

Your shapes are now configured to store data from Active Directory, and can support Data Graphics.

Adding Data Graphics

Data Graphics, new in Visio 2007, are visual overlays that present additional data about a shape. Data Graphics can be in the form of additional graphic icons, text labels, or even bar charts or progress bars, and they can be located in many different positions around or on top of a shape.

When we bring users from Active Directory into our diagram, we want to see an immediate visual graphic to indicate if the user's account is disabled, or if their account is locked out.

To accomplish this, we add some Data Graphics to our User shape in the Document Stencil, by using the following directions.

To add Data Graphics

  1. Right-click the User shape in the stencil, point to Edit Master, and then click Edit Master Shape.

  2. In the master shape's editing window, right-click the User shape, point to Data, and then click Edit Data Graphic.

  3. In the Edit Data Graphic dialog box, add an icon set. To do this, click New Item, and then click Icon Set.

  4. In the New Icon Set dialog box, select Is Disabled from the Data field drop-down list, select the third callout series from the Callout drop-down list, and then, under Callout position, select Right Edge for Horizontal and Top for Vertical.

  5. To the right of the icon with the red circle and white X inside, ensure the first drop-down list is set to equals, and then type TRUE in the second combo box. Your screen should look like Figure 3.

    Figure 3. Edit Icon Set dialog box

    Edit Icon Set dialog box

  6. Click OK, and then add another new icon set, for the Is Locked Out data field. To do this, in the New Icon Set dialog box, select Is Locked Out from the Data field drop-down list.

  7. Select the third callout again, set the Horizontal callout position to Left Edge, and (next to the yellow triangle icon) set the drop-down lists to equals and TRUE. The screen for this icon set should look like Figure 4.

    Figure 4. Edit Icon Set dialog box with Data field set to Is Locked Out

    Edit Icon Set dialog box with Data field

  8. Click OK to exit the Edit Data Graphic dialog box.

Adding Shortcut Menus to the User Shape

In addition to storing Active Directory data with our shapes, we also want them to be rich enough to enable us to perform actions against the user. To do this, we need to create shortcut menus by using the Visio ShapeSheet spreadsheet.

To create shortcut menus

  1. While still in the Master shape's editing window, right-click the User shape, and then click Show ShapeSheet.

  2. On the Insert menu, click Section, select the Actions checkbox, and then click OK.

  3. Scroll to the Actions section, which should already contain one row. Add three more rows by right-clicking and selecting Insert Row three times.

  4. Fill in the cells for the four rows by using the information in Table 3. Be sure to press Enter after entering each cell's value into the formula bar.

    Table 3. Information for Action section

    Action

    Menu

    Invisible

    =RUNADDONWARGS("QUEUEMARKEREVENT","/soln=ADAddIn /cmd=EnableDisable")

    =IF(Prop.IsDisabled,"Enable","Disable")

    =FALSE

    =RUNADDONWARGS("QUEUEMARKEREVENT","/soln=ADAddIn /cmd=UnlockAccount")

    =IF(Prop.IsLockedOut,"Unlock","")

    =NOT(Prop.IsLockedOut)

    =RUNADDONWARGS("QUEUEMARKEREVENT","/soln=ADAddIn /cmd=Email")

    ="Send Email"

    =STRSAME(Prop.Email,"",TRUE)

    =RUNADDONWARGS("QUEUEMARKEREVENT","/soln=ADAddIn /cmd=ResetPassword")

    ="Reset Password"

    =FALSE

    Figure 5. Actions rows

    Actions rows

These steps create four actions on a shortcut menu for the User shape, to enable or disable the account, to unlock the account (if it is locked), and to send an e-mail message to the account. In the Action cell, we used the QUEUEMARKEREVENT function, which triggers an event handler in our C# code to let our add-in know that it needs to respond. In the Menu cell, we determine what text to display in the shortcut menu. In the Invisible cell, we determine whether we need to show or hide the shortcut menu item.

Add a Marker to the Document

The last step that we need to do in our template is to stamp our document with a marker so that our add-in code can verify that the document that is loaded is our custom template. This helps to prevent the add-in toolbar from appearing when other types of documents or templates are loaded.

An easy way to do this is to add a user-defined cell to our document, in the document's ShapeSheet. To access the document ShapeSheet and add this cell, follow these steps.

To add data to the Document ShapeSheet

  1. Open the Drawing Explorer window. To do this, on the View menu, click Drawing Explorer Window.

  2. Right-click the Document node (the top-level node in the tree), and then click Show ShapeSheet.

  3. On the Insert menu, click Section, select the User Defined Cells check box, and then click OK.

  4. In the User Defined Cells section, there should be one row. Select the cell that contains the row name, and then, in the formula bar, enter =IsActiveDirectoryManagerDiagram. Press Enter.

  5. Click in the Value cell, and then, in the formula bar, type FALSE. Press Enter.

  6. Save the template file.

Writing the Code

Now for the fun! We write the code in several steps:

  1. Add event handlers for Visio events.

  2. Add methods to respond to MarkerEvent events.

  3. Create an ADSI (Active Directory Service Interfaces) helper class.

  4. Create a form for choosing the organizational unit to search in.

  5. Create a form for resetting a user's password.

  6. Create a class to generate the diagram.

Adding Event Handlers

To begin, we modify the Connect.cs file to add some event handlers for Visio events, so that we can create our toolbar in response to document events, and so that we can respond to MarkerEvent events that are triggered by shortcut menu right-click actions on our shapes. First, ensure that you have the following using directives at the top of the Connect.cs file.

using System;
using System.Runtime.InteropServices;
using System.DirectoryServices;
using System.Reflection;
using Extensibility;
using Microsoft.Office.Core;
using Visio = Microsoft.Office.Interop.Visio;
using System.Windows.Forms;
using System.Collections.Generic;

Next, add the following fields after the class declaration.

private CommandBars _cBars;
private CommandBar _cBar;
private CommandBarButton _button;

Add the following code to the OnStartupComplete method to begin wiring up events in Visio.

public void OnStartupComplete(ref System.Array custom)
{

    // Add an event handler for the 
    //document's created, opened, and changed events.
    vsoApplication.DocumentOpened += new Microsoft.Office.Interop.Visio.EApplication_DocumentOpenedEventHandler(vsoApplication_DocumentOpened);
    vsoApplication.DocumentCreated += new Microsoft.Office.Interop.Visio.EApplication_DocumentCreatedEventHandler(vsoApplication_DocumentCreated);
    vsoApplication.DocumentChanged += new Microsoft.Office.Interop.Visio.EApplication_DocumentChangedEventHandler(vsoApplication_DocumentChanged);

    // Add an event handler for the QueueMarkerEvent.
    vsoApplication.MarkerEvent += new Microsoft.Office.Interop.Visio.EApplication_MarkerEventEventHandler(vsoApplication_MarkerEvent);

}

Add the following event handlers and methods to set up the add-in toolbar.

void vsoApplication_DocumentCreated(Microsoft.Office.Interop.Visio.Document doc)
{
    OnDocumentCreatedOpenedChanged(doc);
}

void vsoApplication_DocumentOpened(Microsoft.Office.Interop.Visio.Document doc)
{
    OnDocumentCreatedOpenedChanged(doc);
}

void vsoApplication_DocumentChanged(Microsoft.Office.Interop.Visio.Document doc)
{
    OnDocumentCreatedOpenedChanged(doc);
}

private void OnDocumentCreatedOpenedChanged(Visio.Document doc)
{
    _cBars = (CommandBars)vsoApplication.CommandBars;
    _cBar = null;

    // Check to see whether the document that 
    // is in focus is based on our template.
    if (Convert.ToBoolean((short)doc.DocumentSheet.get_CellExistsU("User.IsActiveDirectoryManagerDiagram", 0)))
    {
        // This is our document, so load our toolbar fresh.
        try
        {
            _cBar = _cBars.Add(
                    "Active Directory Manager", 
                    Missing.Value, 
                    Missing.Value, 
                    true);

            _button = (CommandBarButton)_cBar.Controls.Add(
                    MsoControlType.msoControlButton,
                    Missing.Value,
                    Missing.Value,
                    Missing.Value,
                    true);

            _button.Caption = "Create Users Diagram";
            _button.Style = MsoButtonStyle.msoButtonCaption;
            _button.Tag = "Create Users Diagram";

            // Transform the next three lines of code to a single line of code.
            _button.Click += new _CommandBarButtonEvents_ClickEventHandler(button_Click);

            _cBar.Visible = true;

        }
        catch (Exception)
        {
            _cBar = _cBars["Active Directory Manager"];
        }
    }
    else
    {
        // Delete our toolbar, if it exists.
        try
        {
            _cBar = _cBars["Active Directory Manager"];
            _cBar.Delete();
        }
        catch (Exception)
        {
            // Do nothing, our toolbar does not exist.
        }
    }
}

void button_Click(CommandBarButton Ctrl, ref bool CancelDefault)
{
    using (frmConnectionProperties propertiesDialog = new frmConnectionProperties())
    {
        if (propertiesDialog.ShowDialog() == DialogResult.OK)
        {
            DiagramGenerator dg = new DiagramGenerator(vsoApplication);
            dg.CreateDiagram(propertiesDialog.Users);
        }
    }
}

Next, we add the handler for the MarkerEvent event, which processes the responses from our shape shortcut menu right-click actions. The MarkerEvent handler code below has access to a ContextString, which helps our code determine what shape was clicked, and what shortcut menu item was clicked on that shape.

void vsoApplication_MarkerEvent(Microsoft.Office.Interop.Visio.Application app, int SequenceNum, string ContextString)
{
    // The ContextString should look like this:
    // "/doc=[id] /page=[id] /shape=[id] /shapeu=[id] /soln=ADAddIn /cmd=EnableDisable"
    // We can parse this, and determine what shape was clicked, and then get the LDAPPath.

    string docId = null;
    string pageId = null;
    string shapeId = null;
    string soln = null;
    string cmd = null;

    Visio.Shape shape = null;

    List<string> contextList = new List<string>();
    contextList.AddRange(ContextString.Split(' '));
    contextList.ForEach(
        delegate(string item)
        {
            if (item.Contains("/doc="))
            {
                docId = ParseCommandValue(item);
            }
            if (item.Contains("/page="))
            {
                pageId = ParseCommandValue(item);
            }
            if (item.Contains("/shape="))
            {
                shapeId = ParseCommandValue(item);
            }
            if (item.Contains("/soln="))
            {
                soln = ParseCommandValue(item);
            }
            if (item.Contains("/cmd"))
            {
                cmd = ParseCommandValue(item);
            }
        }
    );

    // If the solution is not ours, exit.
    if (string.IsNullOrEmpty(soln) || !string.Equals(soln, "ADAddIn"))
    {
        return;
    }

    // The solution is ours, get the shape that was clicked.
    shape = (Visio.Shape)app.Documents[Convert.ToInt32(docId)].Pages[Convert.ToInt32(pageId)].Shapes.get_ItemU(shapeId);

    // We have the shape, get the LDAP path property.
    string ldapPath = null;
    ldapPath = shape.get_Cells("Prop.LDAPPath").Formula;

    // Determine what action to perform.
    switch (cmd)
    {
        case "EnableDisable":
            // Get the current IsDisabled value.
            bool isDisabled = Convert.ToBoolean(shape.get_Cells("Prop.IsDisabled").Formula);
            EnableDisableUser(ldapPath, shape, isDisabled);
            break;
        case "ResetPassword":
            ResetPassword(ldapPath, shape);
            break;
        case "UnlockAccount":
            UnlockAccount(ldapPath, shape);
            break;
        case "Email":
            // Get the e-mail address.
            string emailAddress = shape.get_Cells("Prop.Email").Formula;
            EmailUser(emailAddress);
            break;
        default:
            break;
    }
}

When you invoke the RUNADDONWARGS ShapeSheet function, Visio automatically appends a series of command-like arguments to your context string so that you can find the current document, page, and shape that the statement was executed on. To work with this, you need to parse this context string into its individual pieces, so that you can use those and index in to the Visio collections to get your Document, Page, and Shape objects. Add the following method to help parse the context string.

private string ParseCommandValue(string commandString)
{
    return commandString.Substring(commandString.IndexOf("=") + 1);
}

Add Methods to Respond to MarkerEvent Events

In the MarkerEvent event handler we discussed previously, we referenced several methods to call, depending on which cmd parameter was provided in the context string.

Now, we need to add those methods into our class.

private void ResetPassword(string ldapPath, Visio.Shape shapeToUpdate)
{
    using (frmResetPassword resetPasswordForm = new frmResetPassword())
    {
        if (resetPasswordForm.ShowDialog() == DialogResult.OK)
        {
            using (DirectoryEntry de = new DirectoryEntry())
            {
                de.Path = ldapPath;
                de.AuthenticationType = AuthenticationTypes.Secure;

                if (de != null)
                {
                    try
                    {
                        de.Invoke("setPassword", resetPasswordForm.txtNewPassword);
                        de.CommitChanges();
                    }
                    catch (Exception)
                    {
                        MessageBox.Show("Password could not be reset");
                    }
                }
                else
                {
                    MessageBox.Show("Password could not be reset");
                }
            }
        }
    }
}

private void UnlockAccount(string ldapPath, Visio.Shape shapeToUpdate)
{
    using (DirectoryEntry de = new DirectoryEntry())
    {
        de.Path = ldapPath;
        de.AuthenticationType = AuthenticationTypes.Secure;
        
        if (de != null)
        {
            try
            {
                de.InvokeSet("IsAccountLocked", false);
                de.CommitChanges();
            }
            catch (Exception)
            {
                MessageBox.Show("Account could not be unlocked");
            }
        }
        else
        {
            MessageBox.Show("Account could not be unlocked");
        }
    }
}

private void EmailUser(string emailAddress)
{
    // Send an e-mail message to the user, by opening the default e-mail client.
    System.Diagnostics.Process emailProcess = new System.Diagnostics.Process();
    emailProcess.StartInfo.FileName = "mailto:" + emailAddress;
    emailProcess.Start();
    emailProcess.Dispose();
}

private void EnableDisableUser(string ldapPath, Visio.Shape shapeToUpdate, bool isAccountDisabled)
{
    using (DirectoryEntry de = new DirectoryEntry())
    {
        de.Path = ldapPath;
        de.AuthenticationType = AuthenticationTypes.Secure;

        if (de != null)
        {
            try
            {
                de.InvokeSet("AccountDisabled", !isAccountDisabled);
                de.CommitChanges();
            }
            catch (Exception)
            {
                MessageBox.Show("Account could not be changed");
            }
        }
        else
        {
            MessageBox.Show("Account could not be changed");
        }
}
}

Each one of these methods uses Active Directory to find the user and to alter data for that user. The EmailUser method uses some built-in capabilities of Microsoft Windows to open the default e-mail client to send an e-mail message.

Create an ADSI Helper Class

Next, we need to create a class to help us manage connecting to Active Directory to retrieve users for our diagram. For brevity, we assume that Windows Authentication will be used, and that the user running this application has rights to edit other user accounts.

To create the procedure title

  1. Right-click the project, point to Add, and then click New Class.

  2. Type ADSIHelper for the name of the class.

  3. Ensure the following using statements are at the top of the class code:

    using System;
    using System.Collections.Generic;
    using System.DirectoryServices;
    using System.Text;
    
  4. Ensure that the class looks like the following code:

    public sealed class ADSIHelper : IDisposable
        {
    
            private DirectoryEntry _connection;
            private string _path;
    
            // Next connect the constructor that uses Windows Authentication to 
            // Active Directory.
            public ADSIHelper(string path)
            {
                _path = path;
                _connection = CreateDirectoryEntry(_path);
            }
    
            private DirectoryEntry CreateDirectoryEntry(string path)
            {
                DirectoryEntry de = new DirectoryEntry();
                de.AuthenticationType = AuthenticationTypes.Secure;
                de.Path = path;
    
                return de;
            }
    
            public List<DirectoryEntry> GetUsers() 
            {
                List<DirectoryEntry> users = new List<DirectoryEntry>();
    
                DirectorySearcher ds = new DirectorySearcher(_connection);
    
                // Transform the next three lines of code to a single line of code.
                ds.Filter = "(&(objectCategory=Person)(objectClass=User))";
    
                ds.SearchScope = SearchScope.Subtree;
                ds.ServerTimeLimit = TimeSpan.FromSeconds(60);
                SearchResultCollection results = ds.FindAll();
    
                foreach (SearchResult result in results) {
                  DirectoryEntry userEntry = CreateDirectoryEntry(result.Path);
                  users.Add(userEntry);
                  userEntry.Close();
                }
    
                return users;
            }
    
            #region IDisposable Members
    
            ~ADSIHelper()
            {
                Dispose(false);
            }
    
            private void Dispose(bool disposing)
            {
                if (_connection != null)
                {
                    _connection.Close();
                    _connection = null;
                }
            }
    
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
    
            #endregion
        }
    

Add a Form to Reset a User's Password

Next, we add a form to reset a user's password.

To add a form to reset a user's password

  1. Right-click the project, point to Add, and then click Windows Form.

  2. Type frmResetPassword for the name of the form.

  3. Add two text boxes, named txtNewPassword and txtConfirmPassword, and then set their Modifier property to Internal.

  4. Add an OK button and a Cancel button, named btnOK and btnCancel.

  5. Set the form's AcceptButton property to btnOK. Set the form's CancelButton property to btnCancel. Your form should look like Figure 6.

    Figure 6. The reset password form

    The reset password form

  6. Add the following click event for btnOK:

    private void btnOK_Click(object sender, EventArgs e)
    {
        if (!string.Equals(txtNewPassword.Text, txtConfirmPassword.Text))
        {
            MessageBox.Show("Your passwords must match");
        }
        Else
        {
            this.DialogResult = DialogResult.OK;
            this.Close;
        }
    }
    

Add a Form to Connect to Active Directory and Generate a Diagram

Now we need to add a form so that we can specify a Lightweight Directory Access Protocol (LDAP) path to a specific organizational unit. This form will be opened from our toolbar's button_Click event and, when filled out, will start the diagram-generation process, returning all the users that exist in that organizational unit.

To add the connection form

  1. Right-click the project, point to Add, and then click Windows Form.

  2. Type frmConnectionProperties for the name of the form.

  3. Drag two Label controls, a TextBox control (named txtLDAPPath), an OK button (named btnOK), and a Cancel button (named btnCancel) onto the page. Your form should look like Figure 7.

    Figure 7. The connection form

    The connection form

  4. Add the following code to the form.

NoteNote

If you just cut and paste this code into your project, the btnOK_Click and btnCancel_Click event handlers will not be added to the buttons' click events. Instead, double-click the buttons on the form and then add the code separately to each event handler.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.DirectoryServices;
using System.Text;
using System.Windows.Forms;

namespace ActiveDirectoryAddIn
{
    public partial class frmConnectionProperties : Form
    {

        private List<DirectoryEntry> _users;

        public List<DirectoryEntry> Users
        {
            get
            {
                return _users;
            }
        } 
        
        public frmConnectionProperties()
        {
            InitializeComponent();
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            this.Cursor = Cursors.WaitCursor; ADSIHelper adhelper;
            
            adhelper = new ADSIHelper(txtLDAPPath.Text);
            
            try
            {
                _users = adhelper.GetUsers();
            }
            catch (Exception ex)
            {
                this.Cursor = Cursors.Arrow;
                MessageBox.Show("Error while reading the users:" + ex.Message);
                return;
            }

            this.Cursor = Cursors.Arrow;
            this.DialogResult = DialogResult.OK;
            this.Close();

        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }

    }
}

Create the Class to Generate the Diagram

Finally, we need to generate one more class, which we use to do the heavy lifting and generate the diagram. This class performs the following tasks:

  1. Connects to Active Directory.

  2. Gets a list of users in an organizational unit.

  3. Drops the organizational unit shape on the page.

  4. Drops a User shape for each Active Directory user returned, and connects it to the Organizational Unit shape.

  5. Sets the Shape Data on each User shape.

  6. Automatically lays out the diagram.

To add the DiagramGenerator class

  1. Right-click the project, point to Add, and then click Class.

  2. Type DiagramGenerator for the name of the class.

  3. Add the following code to the class:

    using System;
    using System.Collections.Generic;
    using System.DirectoryServices;
    using Visio = Microsoft.Office.Interop.Visio;
    using System.Text;
    
    namespace ActiveDirectoryAddIn
    {
        internal class DiagramGenerator
        {
    
            private Visio.Application _vsoApplication;
            public DiagramGenerator(Visio.Application vsoApplication)
            {
                _vsoApplication = vsoApplication;
            }
    
            public void CreateDiagram(List<DirectoryEntry> users)
            {
                // Add this line to provide Undo functionality in Visio.
                int undoScope = _vsoApplication.BeginUndoScope("Users Diagram");
    
                // Get a reference to the Visio Window and Page objects.
                Visio.Window vsoWindow = _vsoApplication.ActiveWindow;
                Visio.Page vsoPage = vsoWindow.PageAsObj;
    
                // Get a reference to the Visio Master to drop.
                Visio.Master ouMaster = _vsoApplication.ActiveDocument.Masters.get_ItemU("Organizational Unit");
    
                // Drop a department shape.
                Visio.Shape vsoOUShape = vsoPage.Drop(ouMaster, 0, 0);
                vsoOUShape.Text = "OU";
    
                // Define the user shape.
                Visio.Shapes shapes = vsoPage.Shapes;
    
                // Get a reference to the Visio Master to drop.
                Visio.Master personMaster = _vsoApplication.ActiveDocument.Masters.get_ItemU("User");
    
                // Drop a Person shape for every user in the Users collection by using
                // the Drop method.
                // You can improve performance if you use the DropMany method instead of the Drop method.
                foreach (DirectoryEntry user in users)
                {
                    Visio.Shape vsoPersonShape = vsoPage.Drop(personMaster, 0, 0);
                    vsoPersonShape.Text = user.Properties["name"].Value.ToString();
    
                    // Connect all the objects.
                    Visio.Shape connector = vsoPage.Drop(
                    _vsoApplication.ConnectorToolDataObject, 0, 0);
    
                    Visio.Cell vsoCell1 = connector.get_CellsU("BeginX");
                    Visio.Cell vsoCell2 = vsoOUShape.get_CellsSRC(7, 3, 0);
                    vsoCell1.GlueTo(vsoCell2);
    
                    vsoCell1 = connector.get_CellsU("EndX");
                    vsoCell2 = vsoPersonShape.get_CellsSRC(7, 0, 0);
                    vsoCell1.GlueTo(vsoCell2);
    
                    // Add custom properties to 
                    //the object IsAccountLockedOut.
                    bool isAccountLocked = (bool)user.InvokeGet("IsAccountLocked");
                    if (isAccountLocked)
                    {
                        vsoPersonShape.get_Cells("Prop.IsLockedOut.Value").FormulaU = "TRUE";
                    }
                    else
                    {
                        vsoPersonShape.get_Cells("Prop.IsLockedOut.Value").FormulaU = "FALSE";
                    }
                    // Assign the value of the LDAPPath property for each user.
                    vsoPersonShape.get_Cells("Prop.LDAPPath.Value").FormulaU = "\"" + user.Path + "\"";
                    // Assign the value for the Email property for each user who has an e-mail address.
                    if (user.Properties.Contains("mail"))
                    {
                        vsoPersonShape.get_Cells("Prop.Email.Value").FormulaU = "\"" + user.Properties["mail"].Value.ToString() + "\"";
                    }
                    // Assign the value of the DN (distinguished name) property for each user who has a distinguished name.
                    if (user.Properties.Contains("distinguishedName"))
                    {
                        vsoPersonShape.get_Cells("Prop.DN.Value").FormulaU = "\"" + user.Properties["distinguishedName"].Value.ToString() + "\"";
                    }
                    // Assign the value of the Department property for each user who belongs to a department.
                    if (user.Properties.Contains("department"))
                    {
                        vsoPersonShape.get_Cells("Prop.Department.Value").FormulaU = "\"" + user.Properties["department"].Value.ToString() + "\"";
                    }
                    // Assign the value of the Display Name property for each user who has a display name.
                    if (user.Properties.Contains("displayName"))
                    {
                        vsoPersonShape.get_Cells("Prop.DisplayName.Value").FormulaU = "\"" + user.Properties["displayName"].Value.ToString() + "\"";
                    }
                    // Assign the value of the IsDisabled property for each user.
                    bool isAccountDisabled = (bool)user.InvokeGet("AccountDisabled");
                    if (isAccountDisabled)
                    {
                        vsoPersonShape.get_Cells("Prop.IsDisabled.Value").FormulaU = "TRUE";
                    }
                    else
                    {
                        vsoPersonShape.get_Cells("Prop.IsDisabled.Value").FormulaU = "FALSE";
                    }
                    // Assign the value of the Title property for each user who has a title.
                    if (user.Properties.Contains("title"))
                    {
                        vsoPersonShape.get_Cells("Prop.Title.Value").FormulaU = "\"" + user.Properties["title"].Value.ToString() + "\"";
                    }
    
                }
    
                //   Lay out shapes in the diagram as Flowchart/Tree.
                Visio.Cell layoutCell;
    
                layoutCell = vsoPage.PageSheet.get_CellsSRC(
                  (short)Visio.VisSectionIndices.visSectionObject,
                  (short)Visio.VisRowIndices.visRowPageLayout,
                  (short)Visio.VisCellIndices.visPLOResizePage);
    
                layoutCell.FormulaU = "TRUE";
    
                layoutCell = vsoPage.PageSheet.get_CellsSRC(
                  (short)Visio.VisSectionIndices.visSectionObject,
                  (short)Visio.VisRowIndices.visRowPageLayout,
                  (short)Visio.VisCellIndices.visPLOPlaceStyle);
    
                layoutCell.set_Result(Visio.VisUnitCodes.visPageUnits,
                   (double)Visio.VisCellVals.visPLOPlaceTopToBottom);
    
                layoutCell = vsoPage.PageSheet.get_CellsSRC(
                  (short)Visio.VisSectionIndices.visSectionObject,
                  (short)Visio.VisRowIndices.visRowPageLayout,
                  (short)Visio.VisCellIndices.visPLORouteStyle);
    
                layoutCell.set_Result(Visio.VisUnitCodes.visPageUnits,
                   (double)Visio.VisCellVals.visLOBRouteFlowNS);
    
                vsoPage.Layout();
    
                _vsoApplication.EndUndoScope(undoScope, true);
            }
        }
    }
    

Running the Visio Add-In

To run the add-in, right-click the Setup project and then click Build. Then, right-click the Setup project again and click Install. Complete the installation wizard to install the add-in on your computer.

The way the toolbar code is written, no toolbar appears unless you open a document based off the Active Directory Manager template you created and saved earlier. To start the add-in and load our toolbar, double-click the Active Directory Manager template. This opens a new document based on that template within Visio and displays the toolbar.

To create the diagram, click Create Users Diagram on the toolbar.

Figure 8. Create Users Diagram button

Create Users Diagram button

Type the desired LDAP Path to the organizational unit that you want to diagram, and then click OK.

Figure 9. The Generate Active Directory Diagram dialog box

The Generate Active Directory Diagram dialog box

After clicking OK, you should see a diagram similar to Figure 10.

Figure 10. The Users diagram

The Users diagram

To modify a user, right-click the User shape, and then click one of the options on the shortcut menu.

Figure 11. Modifying the Users diagram

Modifying the Users diagram

Deploying the Visio Add-In

To deploy your add-in, right-click the Setup project, and then click Build. Distribute the generated .exe file and .msi file to your users, and instruct them to run the .exe file.

Additionally, you need to download the hotfix for Shared Add-Ins in Visual Studio 2005 and set this as a prerequisite for your installer. Instructions and the download can be found in the Microsoft Knowledge Base article FIX: Add-ins, Smart Documents, or Smart Tags That You Create by Using Microsoft Visual Studio 2005 Do Not Run in Office.

Conclusion

In this article, you learned about several ways to integrate Microsoft Office Visio 2007 with Active Directory. We created data-driven diagrams that can update data directly in Active Directory by using Visio 2007 and Visual Studio 2005. We also added more integration with Active Directory to the diagram we created in the article Generating Active Directory Diagrams with Visio 2003 and Visual Studio .NET 2003.

About the Author

Since 1997, Visimation has helped companies improve their productivity by providing visual software tools to ease business and technical tasks, and by offering a broad range of consulting services focusing on Microsoft Visio as a platform for rapid development of efficient Automation programs.

Additional Resources

For more information, see the following resources:

For more information about Visio 2007 integration, see the following articles: