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 :-)
Comments
Anonymous
January 01, 2003
Hey , Correct me if im wrong but this solution will not work with Windows 7 and autoname deploymebt ? I have tested it and 7 just doestn like object already existing in domain. It claims that it cannot reuse the computer accountAnonymous
January 01, 2003
This seems more correct?: 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); }Anonymous
January 01, 2003
Hi Michael - thanks for the code, this web service will be really useful for us. We have the service returning a machine name to MDT, as can be seen in our BDD.log file. The web service is creating the corresponding account successfully in AD. However, the domain join is failing. If I attempt a manual domain join after MDT is finished, I get an error saying the account already exists. Is there a particular attribute that needs to be set to make it a "managed" account that's missing from your code perhaps? Or is this a security issue?Anonymous
June 21, 2010
How do i acctually implement this. I have problems with values not beeing set. All values remain blank except the UUID.Anonymous
October 11, 2010
The comment has been removedAnonymous
May 10, 2011
Hi, there is a bug in this code. The part existingNames.Add(name, netbootGuid); need to be in the loop instead of under the trace.Anonymous
April 23, 2012
Hi, Rick Sorry I am very new to subject, can you please put some step by step instructions. Regards,