Tim Long

Forever in Electric Dreams
The life and times of a Small Business Server MVP and all-round technology enthusiast. Tim is founder of TiGra Networks, a company based in South Wales UK specialising in small business IT. This blog is aimed at Microsoft Small Business Specialists, IT professionals, Astronomers and anyone interested in science and technology.

Recent Posts

Tags

News

  • We are experiencing technical difficulties with images in our blog posts due to an incompatibility between Windows Live Writer and Community Server 2008. If the images seem to be incorrect, this will be why. There is a workaround available but it will take a little time for us to schedule this upgrade into our server. Please bear with us until then. Locations of visitors to this page
    View Tim Long's profile on LinkedIn

Community

Email Notifications

TiGra Networks

My Family

Photo Galleries

SBS Groupies

Archives

January 2006 - Posts

Another watershed - the 1 Gb download
First 1Gb download

Today I embarked on my first 1Gb download.

1 Gb.

This is the beta release of the WinFX software development kit - a DVD image weighing in at 1081 Mb. Previously, my largest download was the US Naval Observatory A2.0 stellar astrometric catalogue - a database containing the coordinates and other details of 55 million stars. The USNO A2.0 data is about 8 Gb but usually fills 13 CD-ROMs and comprises 12 individual .ISO images plus some odds and ends. So while USNO A2.0 remains the largest download, this new WinFX SDK images is the largest individual file I've ever downloaded.

It says a lot about the computing industry, the Internet and the adoption of broadband networking technology such as ADSL that we can now download 1 Gb files. I remember my first job out of university my computer had a 1200 baud Hayes Smartmodem and I used to baulk at doing a 100 Kb download. Now, 1Gb doesn't really seem all that bad. My download manager, ReGet Deluxe, is currently downloading 20 streams in parallel over my 2Mbps ADSL connection. It's screaming along and estimates just another hour and a quarter until completion. The USNO catalogue, by comparison, took about three days using FTP (the bandwidth was throttled at the remote end).

Man, 1 Gb in less than an hour and a half. The world just got smaller.

Exploring Active Directory

ADExplorer experimental appThe first step is to find out how to dive into ActiveDirectory. In the MSDN Library is a walkthru that shows how to access ActiveDirectory objects and display them in a TreeView control. The left-hand pane in the screen shot is the output from the MSDN code. I have extended that to perform LDAP searches and allow the whole AD tree to be browsed using LDAP queries. The right hand pane in the screen shot shows the results of that (click the thumbnail for full size view).

This was easier than I expected. Most of the work was in populating the nodes in the TreeView. The program starts with a DirectoryEntry object which has its Path property set (at design time) to the root node to be searched in ActiveDirectory. I chose to start with LDAP://TiGraNetworks.local as my root path. Then there are two main methods that populate the TreeView.

The method AddLDAPChildren takes a TreeNode object (which contains a DirectoryEntry object in its Tag property). It then preforms an LDAP query on ActiveDirectory, using a DirectorySearcher object. The DirectorySearcher object is called searchLDAP and is configured at design time to search only a single level of objects. The method iterates through the results, creating a new TreeNode for each and adding them to the starting branch. Each new TreeNode has its Tag property set to the DirectoryEntry item of that result.

That all sounds complicated but really what it does is, given any starting point in ActiveDirectory, find all the direct children of that node. The rest is all window dressing to make the TreeView control work.

        /// <summary>
        /// Given a branch (with a DirectoryEntry stored in its Tag property)
        /// search the directory for possible child entries and add them as new nodes.
        /// Search only one level in the directory.
        /// </summary>
        /// <param name="branch"></param>
        protected void AddLDAPChildren(TreeNode branch)
        {
            branch.Nodes.Clear();
            DirectoryEntry dirEntry = (DirectoryEntry) branch.Tag;
            searchLDAP.SearchRoot = dirEntry;
            SearchResultCollection dirResults = searchLDAP.FindAll();
            foreach (SearchResult dirResult in dirResults)
            {
                DirectoryEntry dirItem = dirResult.GetDirectoryEntry();
                TreeNode resultNode = new TreeNode(dirResult.Path);
                resultNode.Tag = dirItem;
                branch.Nodes.Add(resultNode);
            }
        }

Now, the next important method is the event handler that handles the BeforeExpand event. Any time a node in the tree is expanded, this event handler gets called. My original plan was that whenever a node was expanded, I would perform an AD query starting at that node and add the node's children "just in time". However, there is a problem with that. The problem is that a node is not expandable unless it already has children, so this event never executes. I decided on a slightly different technique. I start by creatin the root node in the tree and immediately adding all its children (by calling AddLDAPChildren). I then programmatically expand that root node, which has two effects:

  1. It displays the children
  2. It fires the BeforeExpand event.

Which brings me to the BeforeExpand evant handler. What this handler does is whenever a node expands, we iterate through the child nodes and add the children to each one. So the expanded node is having its grandchildren added. Whenever a node is expanded, we add all of its grandchildren.

        /// <summary>
        /// When a node in the tree is about to expand, we visit each of its child nodes.
        /// For each child node, we populate all of its children from the directory.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void viewLDAP_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            try
            {
                TreeNode branch = e.Node;
                foreach (TreeNode child in branch.Nodes)
                {
                    AddLDAPChildren(child);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }
    }
}

The rest of the code is reproduced at the end of the article, including the Form1_Load event handler which does most of the initialisation.

Next Steps

In the screen shot, you can see that the highlighted line is an OU (Organisational Unit) entry called WSS User Manager. I created this OU manually in my ActiveDirectory. The plan is that the SharePoint web part will eventually create user accounts in a designated OU, for easier management (for example a Group Policy can be applied to the OU). The parent OU is the <domain>/MyBusiness/Users OU, which is where Small Business Server stores all of its user accounts.

In the finished WSS Web Part, we will need to have a way to configure which OU it uses as a place to store the user accounts it will create. One way to do this might be to use the Web.config file for the site.

As a Domain Power User, I was able to browse the directory with no problems. It remains to be seen whether I will be able to create user accounts so easily.

The Application Source Code

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.DirectoryServices;
using System.Diagnostics;

namespace ActiveDirectoryWalkThru
{
    /// <summary>
    /// Summary description for Form1.
    /// </summary>
    public class Form1 : System.Windows.Forms.Form
    {
        private System.Windows.Forms.TreeView viewPC;
        private System.DirectoryServices.DirectoryEntry entryPC;
        private System.DirectoryServices.DirectorySearcher searchLDAP;
        private System.Windows.Forms.TreeView viewLDAP;
        private System.DirectoryServices.DirectoryEntry entryLDAP;
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.Container components = null;

        public Form1()
        {
            //
            // Required for Windows Form Designer support
            //
            InitializeComponent();

            //
            // TODO: Add any constructor code after InitializeComponent call
            //
        }

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows Form Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.viewPC = new System.Windows.Forms.TreeView();
            this.entryPC = new System.DirectoryServices.DirectoryEntry();
            this.searchLDAP = new System.DirectoryServices.DirectorySearcher();
            this.viewLDAP = new System.Windows.Forms.TreeView();
            this.entryLDAP = new System.DirectoryServices.DirectoryEntry();
            this.SuspendLayout();
            // 
            // viewPC
            // 
            this.viewPC.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
                | System.Windows.Forms.AnchorStyles.Left)));
            this.viewPC.ImageIndex = -1;
            this.viewPC.Location = new System.Drawing.Point(16, 16);
            this.viewPC.Name = "viewPC";
            this.viewPC.SelectedImageIndex = -1;
            this.viewPC.Size = new System.Drawing.Size(232, 448);
            this.viewPC.TabIndex = 0;
            // 
            // entryPC
            // 
            this.entryPC.Path = "WinNT://TIGRANETWORKS/Europa";
            // 
            // searchLDAP
            // 
            this.searchLDAP.SearchScope = System.DirectoryServices.SearchScope.OneLevel;
            // 
            // viewLDAP
            // 
            this.viewLDAP.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
                | System.Windows.Forms.AnchorStyles.Left) 
                | System.Windows.Forms.AnchorStyles.Right)));
            this.viewLDAP.ImageIndex = -1;
            this.viewLDAP.Location = new System.Drawing.Point(256, 16);
            this.viewLDAP.Name = "viewLDAP";
            this.viewLDAP.PathSeparator = "/";
            this.viewLDAP.SelectedImageIndex = -1;
            this.viewLDAP.Size = new System.Drawing.Size(432, 448);
            this.viewLDAP.TabIndex = 1;
            // 
            // entryLDAP
            // 
            this.entryLDAP.AuthenticationType = System.DirectoryServices.AuthenticationTypes.ReadonlyServer;
            this.entryLDAP.Path = "LDAP://TiGraNetworks.local";
            // 
            // Form1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
            this.ClientSize = new System.Drawing.Size(696, 478);
            this.Controls.Add(this.viewLDAP);
            this.Controls.Add(this.viewPC);
            this.Name = "Form1";
            this.Text = "Form1";
            this.Load += new System.EventHandler(this.Form1_Load);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() 
        {
            Application.Run(new Form1());
        }

        private void AddPathAndProperties(TreeNode node, 
            System.DirectoryServices.DirectoryEntry entry)
        {
            node.Nodes.Add(new TreeNode("Path: " + entry.Path));
            TreeNode propertyNode = new TreeNode("Properties");
            node.Nodes.Add(propertyNode);
            foreach (string propertyName in entry.Properties.PropertyNames) 
            {
                string oneNode = propertyName + ": " + 
                    entry.Properties[propertyName][0].ToString();
                propertyNode.Nodes.Add(new TreeNode(oneNode));
            }
        }

        private void Form1_Load(object sender, System.EventArgs e)
        {
            TreeNode users = new TreeNode("Users");
            TreeNode groups = new TreeNode("Groups");
            TreeNode services = new TreeNode("Services");
            viewPC.Nodes.AddRange(new TreeNode[] { users, groups, services });

            foreach(System.DirectoryServices.DirectoryEntry child 
                        in entryPC.Children) 
            {
                TreeNode newNode = new TreeNode(child.Name);
                switch (child.SchemaClassName) 
                {
                    case "User" :
                        users.Nodes.Add(newNode);   
                        break;
                    case "Group" :
                        groups.Nodes.Add(newNode);   
                        break;
                    case "Service" :
                        services.Nodes.Add(newNode);   
                        break;
                }
                AddPathAndProperties(newNode, child);
            }

            // Now for the LDAP tree.
            TreeNode ADRoot = new TreeNode(entryLDAP.Path);
            ADRoot.Tag = entryLDAP;
            viewLDAP.BeforeExpand += new TreeViewCancelEventHandler(viewLDAP_BeforeExpand);
            viewLDAP.Nodes.Add(ADRoot);
            AddLDAPChildren(ADRoot);
            ADRoot.Expand();
        }

        /// <summary>
        /// Given a branch (with a DirectoryEntry stored in its Tag property)
        /// search the directory for possible child entries and add them as new nodes.
        /// Search only one level in the directory.
        /// </summary>
        /// <param name="branch"></param>
        protected void AddLDAPChildren(TreeNode branch)
        {
            branch.Nodes.Clear();
            DirectoryEntry dirEntry = (DirectoryEntry) branch.Tag;
            searchLDAP.SearchRoot = dirEntry;
            SearchResultCollection dirResults = searchLDAP.FindAll();
            foreach (SearchResult dirResult in dirResults)
            {
                DirectoryEntry dirItem = dirResult.GetDirectoryEntry();
                TreeNode resultNode = new TreeNode(dirResult.Path);
                resultNode.Tag = dirItem;
                branch.Nodes.Add(resultNode);
            }
        }

        /// <summary>
        /// When a node in the tree is about to expand, we visit each of its child nodes.
        /// For each child node, we populate all of its children from the directory.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void viewLDAP_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            try
            {
                TreeNode branch = e.Node;
                foreach (TreeNode child in branch.Nodes)
                {
                    AddLDAPChildren(child);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
        }
    }
}
WSS User Manager Project

Visual Studio logo graphicI am embarking on a software development project to create a SharePoint web part that can be used to administer site users. The idea is that the owner of a sharepoint site can delegate user management to someone outside of their organisation. The delegated user, lets call them a moderator, should be able to create new site users, which means creating a new user account in Active Directory and then giving that user the appropriate permissions to access the SharePoint site.

There are a number of challenges in this project that will require a bit of research. Accessing ActiveDirectory objects, creating a SharePoint web part, not to mention the security implications.

I've decided to run a series of posts in my personal blog journalling my progress. Step one will be to try a few little experiments to see how easy or hard it is to access ActiveDirectory objects.

Woa Horsey!

Too late!

OK, from the beginning. Went hiking last weekend, thought I'd take some fresh air and start on my new fitness regime. Great plan, so pick a new footpath never before trodden. Lot of horses along the way. Then suddenly a particularly awkward style to cross. Awkward because it exits directly into a 2 foot deep muddy ditch. That is a tricky step down, steep and slimy, got to turn around and step off backwards while holding in the the fence for support.

OK, managed the style, now in the bottom of the ditch. Turn around and try to climb out, without coming to grief on the muddy bank. Made it! Stand up straight, look for the path ahead.

HOLY MOTHER OF GOD! A crazy horse is galloping accross the field straight at me, now two! Nostrils flaring, hind legs kicking, teeth chomping. Steps back in amazement. Oh oh, where did the ground go? Plop! Slurp! Crunch! There goes my knee.

I have a weak knee from an accident I hade while caving about a decade ago. The slightest thing sets it off. Now I've twisted it or done something nasty and it goes "crunch" when I bend my leg to 45°. Ouch. I know from experience that the only thing to do is to lock the leg dead straight and hobble like Long John Silver all the way back to the car.

It would have been a really pleasant walk and a positive start to getting fit again, if not for that darned horse. Don't public footpath owners have a duty of care not to put crazed livestock on their land?