narrow default width wide
colour style colour style colour style colour style

Using MDT Web Services for UDI

I have recently done some work on a UDI deployment that I wanted to share. It involved writing a custom MDT web service that is used in multiple ways.

First off, my thanks to those that have posted info on this kind of thing before: Michael Niehaus, Maik Koster, Todd Hemsell, Chris Nackers, Brandon Linton (and many others). If you are just getting started with MDT/OSD/ZTI/UDI, I highly recommend looking over their blogs.

For this project, we had a few needs:

  1. Keep using RIS-Style Naming when building systems.
  2. Based on location, use a specific computer naming prefix and default OU.
  3. Allow the techs to override the computer name and/or target OU during the build process.
  4. Use an existing computer name by default if the system is being rebuilt manually.


All of this is accomplished by using a combination of UDI and the custom web service. I thought about using Maik's web service but I knew I was going to have to add some custom functions in order to get the functionality that was required. So, I just created a new web service and included everything that was required in it. We may end up installing Maik's web service and using it for the "standard" functions and just including the custom code in this web service.


We just have to gather all of our default information via the web service by specifying everything in CustomSettings.ini. The web service also takes advantage of a XML cross-reference file to set some default settings based on the AD site of the computer being built.


The web service has a few functions that can be used:

  • GetDefaultsForADSite - Returns OSDDOMAINOUNAME and NAMEPREFIX Given an ADSITE by parsing a XML file 
  • GetNewName -  Returns a new Computer Name to use. Inputs DnsDomain, NamePrefix, NumberOfNumbers, UUID, MACAddress, SMSServer, SMSSite 
  • GetSite -  Returns AD site for an IP address (taken 99% from http://www.myitforum.com/articles/43/view.asp?id=12513)

If you look at the CustomSettings.ini example, you can see a few variables that we are setting in the Default section.

  • DnsDomain - Used by the web service to connect to AD
  • NumberofNumbers - The number of digits to use after your naming prefix
  • SMSServer - Used for MAC lookups to find existing computer objects
  • SMSSite - Same
  • MacAddress - Set to 'MacAddress001'. This variable is created by MDT. We are just using it.
  • IPAddress - Set to 'IPAddress001'.This variable is created by MDT. We are just using it.

The Priority variable under [Settings] is important so that everything is processed in the correct order. We have to know the variables specified in the [Default] section in order to get the ADSite. We then use the ADSite to get the defaults for that ADSite (NamePrefix and OSDDomainOUName). Once we have the NamePrefix, we can then determine the computer name to use by default.


One thing I want to highlight is the fact that the Web Service methods for 'WSGETADSITE' and 'WSGETNAME' only return one value. I left these functions working like most of the other Web Services you can find documented. They just return the value as a string and the CustomSettings.ini reflects that. For 'WSGETDEFAULTSFORADSITE', we are returning multiple values. We do this by returning XML instead of a String. Since we define the XML elements that are returned to match TS Variables that are defined, they will be automatically picked up by ZTIGather (see http://blogs.technet.com/b/mniehaus/archive/2007/09/19/d4b3-new-feature-calling-web-services.aspx for details).


This is pretty slick so I wanted to demonstrate this in use. I am pretty sure Maik's web service uses this for some of its functions as well.


After writing this post, I found this: http://mdtcustomizations.codeplex.com/SourceControl/changeset/view/358#34905. It looks like it is definitely returning XML data for some functions. Wish I would have found it before coding everything! :)


Another thing worth mentioning is that UDI seems to use OSDDomainOUName differently than a normal Task Sequence would. Since you define your OUs in the UDI Wizard Designer with both a full LDAP path and a 'friendly name', they chose to have UDI only match things up properly if you pre-define OSDDomainOUName with that 'friendly name'. So, when defining the custom XML that is used by this web service, you need to make sure that the "OU" nodes in ADSiteToOU.xml match up to those friendly names in UDIWizard_Config.xml.


Here is an example of how the Gather step will process everything:

ZTIGather.log


Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Added new custom property IPADDRESS
Added new custom property ADSITE
Added new custom property DNSDOMAIN
Added new custom property NAMEPREFIX
Added new custom property NUMBEROFNUMBERS
Added new custom property MACADDRESS
Added new custom property SMSSERVER
Added new custom property SMSSITE
Using from [Settings]: Rule Priority = DEFAULT,WSGETADSITE,WSGETDEFAULTSFORADSITE,WSGETNAME
------ Processing the [DEFAULT] section ------
Property SLSHARE is now = \\mdt\SLSHARE
Using from [DEFAULT]: SLSHARE = \\mdt\SLSHARE
Property OSINSTALL is now = Y
Using from [DEFAULT]: OSINSTALL = Y
Property SKIPCAPTURE is now = YES
Using from [DEFAULT]: SKIPCAPTURE = YES
Property IPADDRESS is now = IPAddress001
Using from [DEFAULT]: IPADDRESS = IPAddress001
Property DNSDOMAIN is now = ssmlab.com
Using from [DEFAULT]: DNSDOMAIN = ssmlab.com
Property NUMBEROFNUMBERS is now = 4
Using from [DEFAULT]: NUMBEROFNUMBERS = 4
Property MACADDRESS is now = MacAddress001
Using from [DEFAULT]: MACADDRESS = MacAddress001
Property SMSSERVER is now = sccm1
Using from [DEFAULT]: SMSSERVER = sccm1
Property SMSSITE is now = 009
Using from [DEFAULT]: SMSSITE = 009
------ Processing the [WSGETADSITE] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [WSGETADSITE] section
Only the first IPADDRESS value will be used in the web service call.
Property UserDomain is now = ssmlab
Property UserID is now = administrator
About to execute web service call using method POST to http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetSite: IPAddress=10.10.1.50
Response from web service: 200 OK
Successfully executed the web service.
Property ADSITE is now = Subnet1
Obtained ADSITE value from web service:  string = Subnet1
------ Processing the [WSGETDEFAULTSFORADSITE] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [WSGETDEFAULTSFORADSITE] section
Property UserDomain is now = ssmlab
Property UserID is now = administrator
About to execute web service call using method POST to http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetDefaultsForADSite: ADSite=Subnet1
Response from web service: 200 OK
Successfully executed the web service.
Property OSDDOMAINOUNAME is now = IC RIS Holding
Obtained OSDDOMAINOUNAME value from web service:  OSDDOMAINOUNAME = IC RIS Holding
Property NAMEPREFIX is now = w009-
Obtained NAMEPREFIX value from web service:  NAMEPREFIX = w009-
------ Processing the [WSGETNAME] section ------
Determining the INI file to use.
Using COMMAND LINE ARG: Ini file = CustomSettings.ini
Finished determining the INI file to use.
Using specified INI file = CustomSettings.ini
CHECKING the [WSGETNAME] section
Only the first MACADDRESS value will be used in the web service call.
Property UserDomain is now = ssmlab
Property UserID is now = administrator
About to execute web service call using method POST to http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetNewName: DnsDomain=ssmlab.com&NamePrefix=w009-&NumberOfNumbers=4&UUID=C797B78E-4211-4261-8089-DB75A8D7FB5B&MacAddress=00:15:5D:8F:83:1E&SMSServer=sccm1&SMSSite=009
Response from web service: 200 OK
Successfully executed the web service.
Property OSDCOMPUTERNAME is now = W009-0003
Obtained OSDCOMPUTERNAME value from web service:  string = W009-0003
------ Done processing CustomSettings.ini ------




CustomSettings.ini

[Settings]
Priority=Default,WSGETADSITE,WSGETDEFAULTSFORADSITE,WSGETNAME
;Properties=MyCustomProperty
Properties=IPAddress,ADSite,DnsDomain,NamePrefix,NumberOfNumbers,MacAddress,SMSServer,SMSSite

[Default]
OSInstall=Y
;SkipAppsOnUpgrade=YES
SkipCapture=YES
;SkipAdminPassword=NO
;SkipProductKey=YES
SLShare=\\mdt\SLSHARE
;OSDComputerName=w009-custom1
DnsDomain=ssmlab.com
;NamePrefix=w009-
NumberOfNumbers=4
SMSServer=sccm1
SMSSite=009
MacAddress=MacAddress001
IPAddress=IPAddress001

[WSGETADSITE]
Webservice=http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetSite
Parameters=IPAddress
ADSite=string

[WSGETDEFAULTSFORADSITE]
Webservice=http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetDefaultsForADSite
Parameters=ADSite
;OSDDomainOUName=string

[WSGETNAME]
;Webservice=http://MDT.ssmlab.com/WebApplication2/WebService1.asmx?op=GetNewName
Webservice=http://MDT.ssmlab.com/WebApplication2/WebService1.asmx/GetNewName
Parameters=DnsDomain,NamePrefix,NumberOfNumbers,UUID,MacAddress,SMSServer,SMSSite
OSDComputerName=string



ADSiteToOU.XML (needs to be in the same folder as the Web Service ASMX file)

<adsitetooumappings>
  <adsite>
    <sitename>Subnet1</sitename>
    <ou>IC RIS Holding</ou>
    <nameprefix>w009-</nameprefix>
  </adsite>
  <adsite>
    <sitename>Subnet2</sitename>
    <ou>SMGSI RIS Holding</ou>
    <nameprefix>w801-</nameprefix>
  </adsite>
  <adsite>
    <sitename>Subnet3</sitename>
    <ou>SMGSI RIS Holding</ou>
    <nameprefix>w046-</nameprefix>
  </adsite>
</adsitetooumappings>
 

UDIWizard_Config.xml

      <datacollection name="Domain">
<dataitem displayname="ssmlab.com">
<setter property="Domain" value="ssmlab.com">
<setter property="OrganizationalUnits">
<datacollection name="OU">
<dataitem displayname="SMGSI RIS Holding">
<setter property="OU" value="OU=RIS Holding,OU=Workstations,OU=SMGSI,OU=SSMHC,DC=ssmlab,DC=com">
</setter></dataitem>
<dataitem displayname="IC RIS Holding">
<setter property="OU" value="OU=RIS Holding,OU=Workstations,OU=IC,OU=SSMSTL,OU=SSMHC,DC=ssmlab,DC=com">
</setter></dataitem>
</datacollection>
</setter>
</setter></dataitem>
</datacollection>
 

Web Service Code (I think CodeMirror may be stripping some of the code comments out so I apologize for that):

Option Explicit On
Option Strict On
 
'This web service will find a computer name to use
'1. Check AD for a GUID match
'2. Check SCCM for a MAC address match
'3. Find next available name in AD
'   Pass the name prefix and the number of digits after the prefix
'   Examples ("3" = 001-999 ---- "4 "= 0001-9999)

 
 
'Must Add System.DirectoryServices as reference
'Must add System.Data.SqlClient as reference
'''''''''''''''''''''''''''''''''''''''''''
'TODOs/ToThinkAbouts
'We can check v_r_system.SMBIOS_GUID0 for GUIDs as well
'   Skip this for now-MAC should be good enough

'Do we want to populate the netbootguid in AD?
'Will most likely have to be after UDI since the Compname could change from what this web service chooses

'Get OU for calling user?

'Get OU friendly name by AD site (or by user OU)
'   Read cross-reference from an external XML file for easy editing

'http://technet.microsoft.com/en-us/library/bb490304.aspx#E0JC0AA
'   Good info on what info we can pass via customsettings.ini (mac, etc)

 
'Getting IIS working
'http://webcache.googleusercontent.com/search?q=cache:iCl6wFJU0sQJ:www.experts-exchange.com/Software/Server_Software/Web_Servers/Microsoft_IIS/Q_26489006.html+folderlevelbuildproviders+64&cd=18&hl=en&ct=clnk&gl=us&source=www.google.com
'
'The fix was to run aspnet_regiis.exe /iru from the command prompt window making sure to use the relevant Framework
'and version (in our case running .NET 4.0 on a 64bit machine)...
'%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe /iru 
'did the trick; the .NET Compilation tab subsequently opened without error.

'From the Microsoft .Net 4 Framework readme
'2.3.2.6 Re-registering ASP.NET 4 might be required on Windows Vista, Windows Server 2008, Windows 7, and Windows Server 2008 R2
'ASP.NET 4 must be re-registered if IIS 7/7.5 or the IIS7/7.5 .NET Extensibility feature is enabled *after* the .NET Framework 4 has already been installed on the computer. ASP.NET 4 must also be re-registered if the .NET Extensibility feature is removed when the .NET Framework 4 is installed on the computer.
'For both cases, re-registration is required because the operating system installation and uninstallation process for IIS7 and IIS 7.5 and for the .NET Extensibility feature were not designed for the scenario where a later version of the .NET Framework already exists on the computer.
'To resolve this issue:
'To re-register ASP.NET 4, run the following command:
'aspnet_regiis -iru -enable 
'Make sure that you use the version of aspnet_regiis.exe that is installed in the .NET Framework 4 installation directory.

'Have to add this to web.config (at minimum) in order for the POST method to function (look at Maiks web.config to see other possible good options)
'
'   <webservices>
'      <protocols>
'        <add name="HttpGet">
'        <add name="HttpPost">
'      </add></add></protocols>
'    </webservices>
' 

'We can return multiple values as XML
'http://blogs.technet.com/b/mniehaus/archive/2007/09/19/d4b3-new-feature-calling-web-services.aspx
'   The XML returned as a result of the web service call is searched for any property defined via CustomSettings.ini or ZTIGather.xml 
'   (just like with a database query or other rule).  
'   However, the XML search is case-sensitive.
'   Fortunately the web service above returns all upper case property names, which is what ZTIGather expects.  
'   It is possible to remap lower or mixed-case entries to get around this.

'This is the source for what his example returns

'<newdataset>
'  
'    <city>Maryland Heights</city>
'    <state>MO</state>
'    <zip>63043</zip>
'    314
'    C
'  <table class="mceItemTable"></table>
'</newdataset>

'A Normal "string return" (http://blogs.technet.com/b/mniehaus/archive/2009/12/06/ris-style-naming-with-mdt-2010-use-a-web-service.aspx) will look like this:
'   
'  <string xmlns="http://tempuri.org/">IC RIS Holding</string> 

'''''''''''''''''''''''''''''''''''''''''''

Imports System.Web.Services
Imports System.Web.Services.Protocols
Imports System.ComponentModel
 
Imports System.DirectoryServices
Imports System.Data.SqlClient
 
Imports System
Imports System.Collections
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.Data
Imports System.Linq
Imports System.Web
Imports System.Xml.Linq
Imports System.Xml
 
Imports System.DirectoryServices.ActiveDirectory
Imports System.IO
 
' To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line.
'  _
 _
 _
 _
Public Class WebService1
    Inherits System.Web.Services.WebService
 
     _
    Public Function HelloWorld() As String
       Return "Hello World"
    End Function
 
     _
    Public Function GetNewName(ByVal DnsDomain As String, ByVal NamePrefix As String, ByVal NumberOfNumbers As Integer, ByVal UUID As String, ByVal MACAddress As String, ByVal SMSServer As String, ByVal SMSSite As String) As String
 
        Dim ComputerName As String = "COMPNAMEERROR1"    'Set a default so we can see if this web service failed

        ''For web service testing via a browser (so you don't have to fill in the values each time)
        'DnsDomain = "ds.ad.ssmhc.com"
        'NamePrefix = "w009-"

        'UUID = "4C4C4544-0050-5210-804C-B5C04F524431"   'Match
        'UUID = "4C4C4544-0050-5210-804C-B5C04F524432"   'No Match

        'SMSServer = "SCCM1"
        'SMSSite = "009"

        'MACAddress = "00:1A:A0:62:B4:FC"   'Match
        'MACAddress = "00:1A:A0:62:B4:FF"   'No Match

        Dim blnFoundMatch As Boolean = False
        Debug.Print("UUID:" & UUID)
        Try
 
            Dim GUID As New Guid(UUID)
            Dim GUIDConverted As String = BitConverter.ToString(GUID.ToByteArray())
            Dim GUIDConvertedLDAP As String = "\" & Replace(GUIDConverted, "-", "\", 1, -1, CompareMethod.Text)
            'Debug.Print("GUIDConverted:" & GUIDConverted)
            Debug.Print("GUIDConvertedLDAP:" & GUIDConvertedLDAP)
 
            ''''''''''''''
            'Debug.print sample output during testing
            'UUID:4C4C4544-0050-5210-804C-B5C04F524431
            'GUIDConverted:44-45-4C-4C-50-00-10-52-80-4C-B5-C0-4F-52-44-31
            'GUIDConvertedLDAP:\44\45\4C\4C\50\00\10\52\80\4C\B5\C0\4F\52\44\31
            '(&(objectClass=computer)(netbootGuid=\44\45\4C\4C\50\00\10\52\80\4C\B5\C0\4F\52\44\31))

            'build the search, based on the passed domain, then the name prefix
            Dim adRoot As New DirectoryEntry("LDAP://" & DnsDomain)
            Dim ADFilter As String = "(&(objectClass=computer)(netbootGuid=" & GUIDConvertedLDAP & "))"
            'Debug.Print(ADFilter)
            Dim dirSearch1 As New DirectorySearcher(adRoot, ADFilter)
            ' Dim existingNames1 As New Dictionary(Of String, Guid)()
            'existingNames.Com()
            For Each result As SearchResult In dirSearch1.FindAll()
                Dim strCurrName As String = result.Properties("name")(0).ToString.ToUpper
                Debug.Print("AD GUID MATCH: " & strCurrName)
                ComputerName = strCurrName
                blnFoundMatch = True
            Next
        Catch ex As Exception
            ComputerName = "COMPNAMEERROR2"
        End Try
 
        '''''''''''''''''''''''''''''''''''''''''''''''''
        If blnFoundMatch = False Then
            'SQL Query for MAC Match

            Try
 
                Dim SQLQueryString As String = "select distinct top 1 SYS.Netbios_Name0 from v_GS_NETWORK_ADAPTER NWA " & _
                    "JOIN v_R_System SYS on NWA.ResourceID = SYS.ResourceID where MACAddress0 = '" & MACAddress & "'"
                'Debug.Print(SQLQueryString)
                Dim SQLResults As New List(Of String)
 
                Dim SQLConn As New SqlConnection()
                Dim SQLCmd As New SqlCommand()
                Dim sConnString As String = "Data Source=" & SMSServer & ";Initial Catalog=SMS_" & SMSSite & ";Integrated Security=True"
                SQLConn.ConnectionString = sConnString
                SQLConn.Open()
                SQLCmd.CommandTimeout = 60
                SQLCmd.Connection = SQLConn
                SQLCmd.CommandText = SQLQueryString
                Dim SQLReader As SqlClient.SqlDataReader = SQLCmd.ExecuteReader
 
                While SQLReader.Read
                    Dim SCCMCompName As String = SQLReader("Netbios_Name0").ToString
                    Debug.Print("SCCMCompNameViaMAC: " & SCCMCompName)
                    ComputerName = SCCMCompName
                    blnFoundMatch = True
                End While
            Catch ex As Exception
                ComputerName = "COMPNAMEERROR3"
            End Try
        End If
 
        '''''''''''''''''''''''''''''''''''''''''''''''''

        If blnFoundMatch = False Then
            'Find next available computer name

            Try
 
                'build the search, based on the passed domain, then the name prefix
                Dim adRoot As New DirectoryEntry("LDAP://" & DnsDomain)
                Dim dirSearch As New DirectorySearcher(adRoot, "(name=" & NamePrefix & "*)")
                Dim existingNames As New Dictionary(Of String, Guid)()
                'Loop through the results
                For Each result As SearchResult In dirSearch.FindAll()
 
                    Dim intCount As String = result.Properties.Count.ToString()
                    'Debug.Print(intCount)

                    'get the name, and create a guid holder
                    Dim strCurrName As String = result.Properties("name")(0).ToString.ToUpper
                    'Debug.Print(strCurrName)
                    Dim netbootGuid As New Guid
 
                    'if we have a guid use that with this name
                    If result.Properties("netbootGuid").Count > 0 Then
                        netbootGuid = New Guid(DirectCast(result.Properties("netbootGuid")(0), Byte()))
                    End If
 
                    'add details to the dictonary, if they are not already there
                    If Not existingNames.ContainsKey(strCurrName) Then
                        existingNames.Add(strCurrName, netbootGuid)
                    End If
 
                Next
 
                'now try and get the next name in sequence
                Dim strNextName As String = Nothing
 
                'Set a default if we got a bad variable passed
                If IsNumeric(NumberOfNumbers) = False Then NumberOfNumbers = 4
 
                Dim sMaxRecordToCheck As String = StrDup(Convert.ToInt32(NumberOfNumbers), "9")
                Dim intMaxRecordToCheck As Integer = Convert.ToInt32(sMaxRecordToCheck)
                Debug.Print("intMaxRecordToCheck: " & intMaxRecordToCheck)
                'loop through all the available machine numbers up to max of xxxx (9, 99, 999, 9999, etc.)
                For i As Int32 = 1 To intMaxRecordToCheck
 
                    'Dim strNameTest As String = NamePrefix.ToUpper & i.ToString("0000")
                    Dim strNameTest As String = NamePrefix.ToUpper & i.ToString("d" & NumberOfNumbers)
                    Debug.Print(strNameTest)   'Shows each computer name that is checked

                    'is this name in the list, if not we have our name
                    If Not existingNames.ContainsKey(strNameTest) Then
                        strNextName = strNameTest
                        Exit For
                    End If
                Next
 
                'return our new name
                ComputerName = strNextName
            Catch ex As Exception
                ComputerName = "COMPNAMEERROR4"
            End Try
        End If
 
        Return ComputerName
    End Function
 
     _
    Public Function GetDefaultsForADSite(ByVal ADSite As String) As XmlDocument
 
        Dim OUForADSite As String
        Dim NamePrefix As String
 
        Dim ADSiteToOUXML As String = "ADSiteToOU.xml"
        Dim XMLDoc As New Xml.XmlDocument
        Dim Root As XmlNode
 
        ''''''''Get Default OU''''''''
        Try
            XMLDoc.Load(Server.MapPath(ADSiteToOUXML))
            Dim XPathQueryOU As String = "/ADSiteToOUMappings/ADSite[SiteName='" & ADSite & "']/OU"
            'Debug.Print(XMLDoc.InnerText)
            'Debug.Print(XMLDoc.InnerXml)

            Root = XMLDoc.DocumentElement
            'Dim NodeOU As XmlNode = XMLDoc.SelectSingleNode("//*")
            Dim NodeOU As XmlNode = Root.SelectSingleNode(XPathQueryOU)
            If NodeOU Is Nothing Then
                Debug.Print("NULL")
                OUForADSite = Nothing
            Else
                Debug.Print(NodeOU.InnerText)
                OUForADSite = NodeOU.InnerText
            End If
        Catch ex As Exception
            OUForADSite = Nothing
        End Try
 
        ''''''''Get Name Prefix''''''''
        Try
            Dim XPathQueryNamePrefix = "/ADSiteToOUMappings/ADSite[SiteName='" & ADSite & "']/NamePrefix"
            Root = XMLDoc.DocumentElement
            Dim NodeNamePrefix As XmlNode = Root.SelectSingleNode(XPathQueryNamePrefix)
            If NodeNamePrefix Is Nothing Then
                Debug.Print("NULL")
                NamePrefix = Nothing
            Else
                Debug.Print(NodeNamePrefix.InnerText)
                NamePrefix = NodeNamePrefix.InnerText
            End If
        Catch ex As Exception
            NamePrefix = Nothing
        End Try
 
        ''''''''Create output XML''''''''
        Try
 
            Dim XMLSettings As New XmlWriterSettings()
            XMLSettings.OmitXmlDeclaration = True
            XMLSettings.Indent = True
            XMLSettings.Encoding = Encoding.UTF8
            Dim SW As New StringWriter()
 
            Dim Writer As XmlWriter = XmlWriter.Create(SW, XMLSettings)
            'Writer.Create(SW, XMLSettings)

            Writer.WriteStartElement("Table")
            If OUForADSite IsNot Nothing Then Writer.WriteElementString("OSDDOMAINOUNAME", OUForADSite)
            If NamePrefix IsNot Nothing Then Writer.WriteElementString("NAMEPREFIX", NamePrefix)
            Writer.WriteEndElement()
            Writer.Flush()
 
            'Debug.Print(SW.ToString)

            'Making the output in proper XML format
            'http://geekswithblogs.net/pakistan/archive/2005/08/09/49701.aspx

            Dim XMLOutput As New XmlDocument
            XMLOutput.LoadXml(SW.ToString)
            'Debug.Print(XMLOutput.ToString)
            Return XMLOutput
 
            'Return SW.ToString
        Catch ex As Exception
            Return New XmlDocument
        End Try
 
    End Function
 
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    ''Code from Maik Koster with a few small tweaks
    ''http://www.myitforum.com/articles/43/view.asp?id=12513

     _
    Public Function GetSite(ByVal IPAddress As String) As String
        Dim Location As String = ""
        'Debug.Print(IPAddress)
        Try
 
            For Each site As ActiveDirectorySite In Forest.GetCurrentForest.Sites
                For Each subnet As ActiveDirectorySubnet In site.Subnets
                    'Debug.Print(site.Name)
                    If IPInSubnet(IPAddress, subnet.Name) Then
                        Location = site.Name
                        Exit For
                    End If
                Next
            Next
        Catch ex As Exception
        End Try
 
        Return Location
    End Function
 
    '' <summary>
    '' The GetOverlappingADSubnets method returns a list of Active Directory Subnets 
    '' which overlaps with subnets from other Active Directory sites
    '' </summary>
    '' <returns>List of overlapping Active Directory subnets</returns>
    '' <remarks></remarks>
    Public Shared Function GetOverlappingADSubnets() As List(Of String)
        Dim SubnetOverlaps As New List(Of String)
        Dim Sites1 As New List(Of ActiveDirectorySite)
        Dim Sites2 As New List(Of ActiveDirectorySite)
 
        For Each Site As ActiveDirectorySite In Forest.GetCurrentForest.Sites
            Sites1.Add(Site)
            Sites2.Add(Site)
        Next
 
        For Each site1 As ActiveDirectorySite In Sites1
            For Each site2 As ActiveDirectorySite In Sites2
                For Each Subnet1 As ActiveDirectorySubnet In site1.Subnets
                    For Each Subnet2 As ActiveDirectorySubnet In site2.Subnets
                        If site1.ToString  site2.ToString AndAlso SubnetOverlapsSubnet(Subnet1.Name, Subnet2.Name) Then
                            If Not SubnetOverlaps.Contains(site2.ToString & ":" & Subnet2.Name & " - " & site1.ToString & ":" & Subnet1.Name) Then
                                SubnetOverlaps.Add(site1.ToString & ":" & Subnet1.Name & " - " & site2.ToString & ":" & Subnet2.Name)
                            End If
                        End If
                    Next
                Next
            Next
        Next
 
        Return SubnetOverlaps
    End Function
 
    '' <summary>
    '' The IPInSubnet method checks if an IP Address is within a specified IP Subnet. 
    '' </summary>
    '' <span name="IPAddress" class="mceItemParam"></span>IP Address
    '' <span name="subnet" class="mceItemParam"></span>IP Subnet
    '' <returns>Returns True if the IP Address is within the subnet. False if not.</returns>
    '' <remarks></remarks>
    Public Shared Function IPInSubnet(ByVal IPAddress As String, ByVal subnet As String) As Boolean
        Dim Result As Boolean = False
        Try
 
            Dim SplittedSubnetName As String() = subnet.Split(CChar("/"))
            If SplittedSubnetName.Length = 2 Then
                Dim IPAddressInDecimal As Long = IPToDecimal(IPAddress)
                Dim SubnetmaskBits As Integer = Integer.Parse(SplittedSubnetName(1))
                Dim NoOfAddresses As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits)) - 1)
                Dim LowIPAddress As Long = IPToDecimal(SplittedSubnetName(0))
                Dim HighIPAddress As Long = LowIPAddress + NoOfAddresses
                Dim TotalIPAddressCount As Long = (Convert.ToInt64(Math.Pow(2, 31))) - 1
 
                If LowIPAddress <= IPAddressInDecimal AndAlso IPAddressInDecimal <= HighIPAddress AndAlso NoOfAddresses <= TotalIPAddressCount Then
                    Result = True
                End If
            End If
        Catch ex As Exception
        End Try
 
        Return Result
    End Function
 
    '' <summary>
    '' The SubnetOverlapsSubnet method will check if two given subnets overlap or collide with each other.
    '' It will return a list of Site/Subnet combinations which overlap
    '' </summary>
    '' <span name="Subnet1" class="mceItemParam"></span>First Subnet - Format 192.168.20.0/24
    '' <span name="Subnet2" class="mceItemParam"></span>Second Subnet to check against - Format 192.168.20.0/24
    '' <returns>List of Site/Subnet combinations of colliding subnets</returns>
    '' <remarks></remarks>
    Public Shared Function SubnetOverlapsSubnet(ByVal Subnet1 As String, ByVal Subnet2 As String) As Boolean
        Dim result As Boolean
 
        Try
 
            Dim splittedsubnetname1 As String() = Subnet1.Split(CChar("/"))
            Dim splittedsubnetname2 As String() = Subnet2.Split(CChar("/"))
 
            Dim SubnetmaskBits1 As Integer = Integer.Parse(splittedsubnetname1(1))
            Dim SubnetmaskBits2 As Integer = Integer.Parse(splittedsubnetname2(1))
 
            Dim NoOfAddresses1 As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits1)) - 1)
            Dim NoOfAddresses2 As Integer = Convert.ToInt32(Math.Pow(2, (32 - SubnetmaskBits2)) - 1)
 
            Dim LowIPAddress1 As Long = IPToDecimal(splittedsubnetname1(0))
            Dim LowIPAddress2 As Long = IPToDecimal(splittedsubnetname2(0))
 
            Dim HighIPAddress1 As Long = LowIPAddress1 + NoOfAddresses1
            Dim HighIPAddress2 As Long = LowIPAddress2 + NoOfAddresses2
 
            If (LowIPAddress1  HighIPAddress2) Then
                result = False
            Else
                result = True
            End If
        Catch ex As Exception
        End Try
        Return result
    End Function
 
    '' <summary>
    '' the IPToDecimal method converts an IP Address into its decimal representation
    '' </summary>
    '' <span name="IPAddress" class="mceItemParam"></span>IP Address
    '' <returns>Decimal representation of the IP Address</returns>
    '' <remarks></remarks>
    Public Shared Function IPToDecimal(ByVal IPAddress As String) As Long
        'Dim IPAddresses As String() = IPAddress.Split(".")
        Dim IPAddresses As String()
        Try
            IPAddresses = IPAddress.Split(CChar("."))
            Return Convert.ToInt64(((Int32.Parse(IPAddresses(0)) * Math.Pow(2, 24) + _
                (Int32.Parse(IPAddresses(1)) * Math.Pow(2, 16) + _
                (Int32.Parse(IPAddresses(2)) * Math.Pow(2, 8) + _
                (Int32.Parse(IPAddresses(3))))))))
        Catch ex As Exception
            Return 0
        End Try
    End Function
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

 
End Class