RIS-style naming with MDT 2010: Use a web service

I’ve been toying around with using a web service that could be used to implement RIS-style computer naming.  Unfortunately, I haven’t had time to work on it much in the last year or so, so I’ll post the code as-is and tell you up front that it’s not really complete – you might need to make some changes to it to meet your specific needs.  So here’s the code (written using Visual Studio 2008 and .NET 3.5), which shows how easy it is to do LDAP queries and object creation using .NET:

 using System;
using System.Collections;

using System.Collections.Generic;

using System.ComponentModel;

using System.Diagnostics;

using System.DirectoryServices;

using System.Data;

using System.Linq;

using System.Web;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.Xml.Linq;
namespace DemoWebService
{
    [WebService(Namespace = "https://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [ToolboxItem(false)]
    public class NameService : System.Web.Services.WebService
    {
        [WebMethod]
        public String GenerateName(String dnsDomain, String prefix, String uuid, String machineObjectOU)
        {
            // Build the search
            DirectoryEntry entry =
                new DirectoryEntry("LDAP://" + dnsDomain);
            DirectorySearcher search = new DirectorySearcher(entry,
               "(name=" + prefix + "*)");
            // Execute the search and build a list of the matching names and their UUIDs
            Dictionary<String, Guid> existingNames = new Dictionary<String, Guid>();
            foreach (SearchResult result in search.FindAll())
            {
                String name = result.Properties["name"][0].ToString().ToUpper();
                Guid netbootGuid = new Guid();
                if (result.Properties["netbootGuid"].Count > 0)
                    netbootGuid = new Guid((byte[])result.Properties["netbootGuid"][0]);
                Trace.WriteLine("Found computer " + name + " with GUID " + netbootGuid.ToString());
                existingNames.Add(name, netbootGuid);
            }
            // See if we can find an existing match.  If so, return it.
            Guid existingUuid = new Guid(uuid);
            if (existingNames.ContainsValue(existingUuid))
            {
                foreach (String name in existingNames.Keys)
                    if (existingNames[name] == existingUuid)
                    {
                        // TODO: Maybe we want to move the computer object to the specified OU
                        return name;
                    }
            }
            // Find the first available name in sequence
            String nextName = null;
            for (Int32 i = 1; i <= 999; i++)
            {
                String testName = prefix + i.ToString("000");
                if (!existingNames.ContainsKey(testName))
                {
                    nextName = testName;
                    break;
                }
            }
            if (nextName == null)
                return null;  // All names were taken
            // Add the computer to AD
            try
            {
                DirectoryEntry dirEntry = new DirectoryEntry("LDAP://" + machineObjectOU);
                DirectoryEntry newUser = dirEntry.Children.Add("CN=" + nextName, "computer");
                newUser.Properties["samAccountName"].Value = nextName + "$";
                newUser.Properties["netbootGUID"].Value = existingUuid.ToByteArray();
                newUser.Properties["description"].Value = "Added by MDT";
                newUser.CommitChanges();
                newUser.Close();
            }
            catch (Exception e)
            {
                Trace.WriteLine("Unable to add computer: " + e.ToString());
            }
            // Return the name
            return nextName;
        }
    }
}

To use this, you would add an entry to CustomSettings.ini to call the web service.  That would look something like this:

[Settings]
Priority=Default, GetName
Properties=DnsDomain, Prefix

[Default]
Prefix=MDTTEST
DnsDomain=mydomain.com
MachineObjectOU=OU=Workstations,DC=mydomain,DC=com

[GetName]
WebService=https://myserver/NameService.asmx/GenerateName
Parameters=DnsDomain, Prefix, UUID, MachineObjectOU
OSDComputerName=string

The web service would be passed the DNS domain name (mydomain.com), computer prefix (MDTTEST), the current machine’s SMBIOS UUID, and the OU to which new computers should be added.  It will return a name starting with the specified prefix and ending with the next available three-digit number.  So the first machine would be MDTTEST001, the second MDTTEST002, etc.  These computer names are added to Active Directory with the “netbootGUID” attribute set, so that if the machine is ever rebuilt it will use the same computer name again.  (The code purposely doesn’t try to find any computer object with that SMBIOS UUID.  Instead, it only looks for computers with the right prefix that have a matching UUID.  This might not be the behavior you want, but it was the behavior I wanted.)

The output from ZTIGather.wsf when processing this INI file would look something like this:

Added new custom property DNSDOMAIN
Added new custom property PREFIX
Using from [Settings]: Rule Priority = DEFAULT, GETNAME
------ Processing the [DEFAULT] section ------
Property MACHINEOBJECTOU is now = OU=Workstations,DC=mydomain,DC=com
Using from [DEFAULT]: MACHINEOBJECTOU = OU=Workstations,DC=mydomain,DC=com
Property DNSDOMAIN is now = mydomain.com
Using from [DEFAULT]: DNSDOMAIN = mydomain.com
Property PREFIX is now = MDTTEST
Using from [DEFAULT]: PREFIX = MDTTEST
------ Processing the [GETNAME] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CS.ini
Finished determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CS.ini
CHECKING the [GETNAME] section
About to execute web service call using method POST to https://server/NameService.asmx/GenerateName: DnsDomain=mydomain.com&Prefix=MDTTEST&UUID=814100CD-CE48-CB11-A536-B7561D1E4450&MachineObjectOU=OU=Workstations,DC=mydomain,DC=com
Response from web service: 200 OK
Successfully executed the web service.
Property OSDCOMPUTERNAME is now = MDTTEST001
Obtained OSDCOMPUTERNAME value from web service:  string = MDTTEST001

This isn’t quite as flexible as the RIS naming, where you could use other variables in the computer name, but there’s no reason you couldn’t add more logic to cover those cases too.  There’s also no guarantee that the web service will add the computer account to the same DC that the computer ends up using, which could cause some naming conflicts if the new computer object doesn’t replicate before the computer tries to join the domain.  Use at your own risk :-)