lunes, 13 de febrero de 2012

Moving computers in Active Directory using a webservice

Moving computers in Active Directory using a webservice

March 15, 2009
If you are using GPOs in your Active Directory Environment you can come into a situation during your deployments, where the settings of a GPO might have impacts on the deployment process. To avoid this and be able to have full control on what happens during the deployment, one way to solve this problem is to have a specific OU which blocks all GPOs. I refer to this as staging OU. But this raises two other problems.
When we add a new computer it's quite easy to have it added to a specific OU. But what happens with existing computer accounts if you are doing a reimage? Sure you could delete the computer object first and have it recreated. But often you have specific settings on some computers, they are member of some security groups etc. and you probably want to preserve this information. In this case you need to be able to re-use the former computer account. So we need to move the computer from the existing OU to the staging OU.
Second after the deployment process has finished, what happens to the new (or old) computers in the staging OU? Want your helpdesk have them moved to the destination OU? I would expect to have it moved automatically.
This problem has already been touched by Ben Hunter (How to move a computer object in Windows PE, How to ensure the computer is in the correct OU). The idea behind it is basically to have a StagingOU and three scripts. The first script will be executed right before all settings will be written to the sysprep.inf and the computer reboots. It will try to move an existing computer account to the staging OU and swap the MachineObjectOU and StagingOU Values. This way new computers will end up in the staging OU during the sysprep.
The second script will swap the values back to it's original values, so MachineObjectOU is now pointing again to the final OU for this computer. This third script will actually move the computer account to the MachineObjectOU and should be executed quite late in the Deployment process.
Actually this is working great. Good work Ben!
There are some requirements like ADSI and credentials with proper permission to move computer objects must be available for the script, but nothing really difficult. So why would I want to write something different? Actually moving computers is a quite common task. e.g. if you have a couple of locations with assigned OUs and users are moving you might want to have a way to automate (or semi-automate) the moving of computer objects. Anyway, I wanted to have a more generic way to move computer objects. And I wanted to be able to request this move from different sources. So I ended up creating another section for my webservice.
Moving an object in AD using the .net Framework is actually quite easy. You only need to get the DirectoryEntry for the OU you would like to move the object to:
    ' Get OU
    Dim OU As DirectoryEntry

    ' Remove "LDAP://" from OU Path if necessary
    OU = New DirectoryEntry(Serverpath & "/" & StripLDAP(OUPath), _Username, _Password)

Then we search for the Computer object
    ' Search for the computer
    Dim Computer As DirectoryEntry
    Computer = FindComputer(ComputerName).GetDirectoryEntry

and if found, move the Computer to the OU:
    ' Move computer to OU
    If Not Computer Is Nothing AndAlso Not OU Is Nothing Then
        Try
            Computer.MoveTo(OU)
            Result = True
        Catch exc As Exception
            Trace.WriteLine("MoveComputerToOU: Unhandled exception - " & exc.ToString)
        End Try
    End If

The FindComputer Function which you see above is simply searching for all objects with "cn=ComputerName" and return the first result:
    'Create Active Directory Searcher to get AD Object
    Dim ADSearcher As New DirectorySearcher(_Root, String.Concat("(", SearchProperty, "=", SearchValue, ")"))

    ' Get only the first result. Search should find unique objects
    Dim SResult As SearchResult = ADSearcher.FindOne

So far so good. That was easy to implement. Now we just take the scripts from Ben Hunter and change them in a way that they not move the computer themselves, instead calling the webservice.
That's it?
Here comes the caveat. The webservice is running on a webserver and will probably contact the closest Domain Controller. So the Computer will be moved but due to replication intervals and topology this change can take a while, before the Domain controller of the site the computer is sitting in knows about it. Depending on the duration of your Deployment process this will have some funny results. During the Testphase of this change we even "lost" some computers somewhere.
The solution to this is to do this change on the remote Domain Controller of the site of the client requesting this change. If you look at the code above, you will find the string "Serverpath". So if you create this DirectoryEntry not with LDAP://yourdomain.com... but with LDAP://YourLocalDomainController/... everything will work as supposed. But how do we get the local Domain Controller?
First we need to find the AD Site. I posted already how to do this (Get Active Directory Site for IP Address). So have a look on this link for further references. Then we need to find a Domain Controller for this site (we just pick one as we assume replication within an ADSite is fast enough. We could change the function to take the DC as an argument but so far we haven't had any problems):
    ' Connect to local Domain Controller to avoid problems when moving computer accounts
    If HostIPAddress <> "" AndAlso HostIPAddress.Substring(0, 3) <> "127" Then
        Dim Site As String
        ' Get SiteCode for Host IP Address
        Site = Me.GetSite(HostIPAddress)

        ' Get First DirectoryServer of Site
        Dim DC As DirectoryEntry
        DC = GetDCForSiteCode(Site)

        If Not DC Is Nothing Then
            ' Connect to local Domain Controller
            Serverpath = DC.Path.Substring(0, DC.Path.IndexOf("/", 7))
            Dim DE As New DirectoryEntry(Serverpath, _Username, _Password)
            If Not DE Is Nothing Then
                ' Change context to new DC
                _Root = DE
            End If
        End If
    End If

 
    Private Function GetDCForSiteCode(ByVal SiteCode As String) As DirectoryEntry
        Dim Result As DirectoryEntry

        If SiteCode <> "" Then
            For Each Site As ActiveDirectorySite In Forest.GetCurrentForest.Sites
                If Site.Name = SiteCode Then
                    'Get first DirectoryServer from Site
                    If Site.Servers.Count > 0 Then
                        Result = Site.Servers(0).GetDirectoryEntry
                    End If
                End If
            Next
        End If

        Return Result
    End Function

As we are now able to move a computer, we need to implement this into our Deployment Process. As already mentioned, we will use three scripts. The first is called Z-MoveComputer_StagingOU.wsf. If a value for the custom Property "StagingOU" has been supplied it will swap it with the value of MachineObjectOU and try to move the computer to the staging OU based on the current computername. The script needs to run before(!) the Configure task.
The Second script is called Z-Movecomputer_SwapOUValues.wsf and will simply revert the change we have done. So it needs to run after(!) the Configure task. (But before the third script :-) )
The third script is called Z-MoveComputer_HostOS.wsf and will try to move the current computer to the OU specified in MachineObjectOU (which has been set back to it's original value with the second script). So it should be executed quite late in the process somewhere in the State Restore Phase.
To be able to use these scripts, you need to add some information to your CustomSettings.ini. You need to define a custom property called StagingOU
    Properties=..., StagingOU, ....
and supply a value for it.
    [Default]
    StagingOU=OU=MDT,DC=mydomain,DC=com

And we need to have a section called "MoveComputerToOU" with the definition of the webservice call (you can choose a different name but then you need to adjust the scripts):
    [MoveComputerToOU]
    WebService=http://MyWebServer/Deployment/ad.asmx/MoveComputerToOU
    Parameters=ComputerName,MachineObjectOU
    MachineObjectOU=OUPath

That's all. The Webservice itself expects two Parameters, Computername and OUPath. As we use the Property "MachineObjectOU" to store our value, we need to tell the MDT Script to call the webservice with the proper Parameter. That's why we need to include this "MachineObjectOU=OUPath" mapping in this section. MDT will then call the webservice using the value of MachineObjectOU for the Parameter OUPath.
There are actually other usages for this Werbservice. One I'm thinking about to implement is to move a computer to some kind of "Disabled computers" OU during the capture of computers which are about to be replaced. Or use it as part of a standardized way to move (and probably rename) computers between OUs to comply with your Business rules. It's up to you.
The most current version of the Webservice used in this example can be downloaded from MDTCustomizations on CodePlex. Also the example scripts from this post can be downloaded from this CodePlex project (Download Example script)
Btw. I would be happy to get some feedback on the problems or additional ideas you had what could help others. The webservice seems to grow to a generic one covering a lot of functions. So what could come next?
UPDATE: With MDT 2010 there has been a couple of changes in the way webservices are beeing called and how the response needs to be handled. See Making custom Database and Webservice scripts work again in MDT 2010 for more information.
Enhanced by Zemanta

No hay comentarios:

Publicar un comentario