none
Use Powershell to Add AD Computer Object to an AD Group

    Question

  • Hello, I am trying to write a script that adds a Computer Object in Active Directory to a group.  The script works fine if I run it against a computer object that has been in AD a long time.  However, if I run the script on an object that has been recently added to the domain, it always fails.  The purpose of this process is to run it on newly domain joined machines, which isn't working.

    I get the following error:

    The following exception occurred while retrieving member "Add": "Unknown error (0x80005000)"

    Anyone have any ideas why this is happening and any ideas on how to fix it?  My script is below (note: I can't use AD cmdlets to accomplish this as I have to be able to run it from servers that do not have those components installed):

    # Get parameters, if not specified ask for them or set default value.
    param ([string] $ADGroup, [string] $ComputerName)
    If (!$ADGroup)
    {
     $ADGroup = read-host "Enter the Active Directory group to add object to: "
    }

    If (!$ComputerName)
    {
        $ComputerName = $env:computername
    }

    # Set LDAP Search Parameters to find computer account.
    $ComputerFilter = "(&(objectCategory=Computer)(CN=$ComputerName))"
    $GroupFilter = "(&(objectCategory=Group)(CN=$ADGroup))"

    $Domain = New-Object System.DirectoryServices.DirectoryEntry

    $Searcher = New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.SearchRoot = $Domain
    $Searcher.PageSize = 1000
    $Searcher.SearchScope = "Subtree"

    # Set LDAP property list, comma seperated for powershell array (ie. "adspath", "cn")
    $PropertyList = "adspath"

    foreach ($Property in $PropertyList)
    {
        $Searcher.PropertiesToLoad.Add($Property) | Out-Null
    }

    #Find Computer Path
    $Searcher.Filter = $ComputerFilter
    $FindCP = $Searcher.FindOne()
    $ComputerPath = $FindCP.Properties.adspath

    #Find Group Path
    $Searcher.Filter = $GroupFilter
    $FindGP = $Searcher.FindOne()
    $GroupPath = $FindGP.Properties.adspath

    # Get Group Object
    $Group = [ADSI]"$GroupPath"

    # Add Computer to Group
    $Group.Add("$ComputerPath")
    $Group.SetInfo()

    Thursday, February 10, 2011 2:23 PM

Answers

  • I finally got this working.  Apparently using SCCM 2007 R2's Run as feature breaks the ability to access some objects.  I had to use the regular Run Command Line (without Runas) and pass credentials from within the script and then it works.
    • Marked as answer by bcehr Thursday, May 19, 2011 6:36 PM
    Thursday, May 19, 2011 6:36 PM

All replies

  • If the script works for existing computer objects, but fails with newly created computers, that tells me the script is fine. I suspect the new object has not yet replicated to all DC's. Perhaps force replication. Also, you can have the script output $ComputerPath to verify that it is missing when the error is raised, so you know the object was not found.

    Richard Mueller


    MVP ADSI
    Thursday, February 10, 2011 3:22 PM
  • Richard, thanks for the reply!  That is very possible, however, based on how the script is suppose to find a DC, my hope is that it would pull the same DC that the machine used to join the domain a few minutes prior.  Also, I ran a test; I joined the computer to the domain, waited an hour, tried to run and it still gave the same error. Shouldn't AD replicate by then (we have simple a topology, just two sites with a high-speed link, replication set at 15 minutes)?

    Also, I outputted $ComputerPath and confirmed that it was null, so that is definitely where the problem is (it is missing).  I also, disjoined a computer from the domain, let it replicate, then joined it back, then ran script and it immediately worked.  Thoughts?

    Thursday, February 10, 2011 6:44 PM
  • This may not be related, but when I use DirectorySearcher I have better luck when I use:

    $ComputerPath = $FindCP.Properties.Item("adspath")

     

    I suspect the behavior varies with either OS or PowerShell version.

    Richard Mueller


    MVP ADSI
    Saturday, February 12, 2011 1:32 AM
  • Richard, thanks for the suggestion.  I just tried changing that code, but unfortuantly, it didn't make a difference.  It still errors out.  I gave it just over an hour in AD to replicate and I confirmed it existed on all DCs, but it still won't find the $ComputerPath until it sits in there for a more prolonged period.  Any thoughts?  Thanks!

    Monday, February 14, 2011 5:23 PM
  • I agree with Richard that the code must be functioning as there is no issue running it on machines that have been joined to the domain for some time.    I know that the script still needs to run locally on the newly joined servers bu ave you tested running the script directly on a DC, or better yet the DC that the machine used to join the domain?  

    Monday, February 14, 2011 7:57 PM
  • This code worked in a test environment for me.  Do you have problems with a specific group perhaps?  Have you checked to see if the common name (cn) is the same as the sAMAccountName?  If not, then you might be able to change your query and see if it works.  If not, then I would recommend inserting statements to detect the values of each line.  If you have the capability of loading something like the PowerGUI Script Editor, then you can step through the logic one line at a time and is extremely helpful.  Finally, make sure that the account you are running the script as has the appropriate access to update group membership on the target group.  If you are using a password file, you should be warned that those cannot be moved from one system to another as they are tied to the user and machine on which they were created.

    HTH - fr3dd


    fr3dd
    • Proposed as answer by Alan ZhuModerator Tuesday, February 15, 2011 2:20 AM
    • Unproposed as answer by bcehr Tuesday, February 15, 2011 3:06 PM
    • Marked as answer by bcehr Tuesday, February 15, 2011 3:07 PM
    • Unmarked as answer by bcehr Tuesday, February 15, 2011 3:07 PM
    Monday, February 14, 2011 8:46 PM
  • Thanks for the replies!

    Joe, I just tested the script on DC and it works fine.  Tried it on both DC that was used to join domain and another DC.

    fr3dd, as far as I can tell group is fine, as it works in many scenarios.  I have confirmed permissions are fine.  I also waited overnight on a newly joined computer and it worked fine this morning.  I will try running it now and output all the variables and see what I get and will post back.

    Thx!

    Tuesday, February 15, 2011 3:10 PM
  • Since it works fine running it locally on any DC would you consider executing the script remotely on the DC from the server using Invoke-Command:

    (help invoke-command -example ---> example #11)

    Invoke-Command  -Computer DC1  -filepath c:\join_computer_to_group.ps1  -ArgumentList  ADGroupName, Computername

    This would however mean the DC would need to be configured for PS Remoting...

    Tuesday, February 15, 2011 4:21 PM
  • If $ComputerPath has no value at first, but does later, the only explanation I can think of is replication. You can target a specific DC, but that might not solve the problem, unless you know for sure it is the DC used when the object was created. You could run a script to target all DC's and check for the existence of the object on each, but if there is a DNS problem causing the replication delay this might fail.

    Richard Mueller


    MVP ADSI
    Tuesday, February 15, 2011 5:23 PM
  • This PowerShell script worked for me to verify the existence of a specified computer object on each DC in the domain. I hard coded the Common Name of the computer, and the DN of the domain in the SearchRoot value:

    Trap {"Error: $_"; Break;}
    Set-StrictMode -Version latest
    
    $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
    $Searcher = New-Object System.DirectoryServices.DirectorySearcher
    # $Searcher.SearchRoot = "LDAP://$Domain"
    $Searcher.PageSize = 200
    $Searcher.SearchScope = "subtree"
    
    # Find specific Computer.
    $Searcher.Filter = "(&(objectCategory=computer)(cn=MyComputer))"
    $Searcher.PropertiesToLoad.Add("distinguishedName") > $Null
    $Searcher.PropertiesToLoad.Add("sAMAccountName") > $Null
    
    # Enumerate all Domain Controllers.
    ForEach ($DC In $Domain.DomainControllers)
    {
      Write-Host "On DC: $DC"
      $Searcher.SearchRoot = "LDAP://$DC/dc=MyDomain,dc=com"
      $Results = $Searcher.FindAll()
      ForEach ($Result In $Results)
      {
        $DN = $Result.Properties.Item("distinguishedName")
        $Name = $Result.Properties.Item("sAMAccountName")
        Write-Host " Found: $DC, $Name"
      }
    }

     

    This script enumerates all DC's in the domain, then searches each for the computer object with the specified Common Name. If found, it outputs the DN and sAMAccountName of the object. If not found, it only outputs the name of the DC.

    Richard Mueller


    MVP ADSI
    Tuesday, February 15, 2011 5:37 PM
  • Richard, I tried to run your script right after running my script on a DC and it returns found on all DCs.  However, it appears that my newly joined computer for some reason can't bind to AD.  I re-ran my script outputting all the variables and my newly joined machine can't find anything on the domain.  I found that $GroupPath is also returning null.  It appears that it is not a problem with AD replication, but simply that my newly joined computer can't connect to AD and pull data.  $Group is one of the only objects that is getting properly populated, pretty much everything else is $null.  Thoughts?  Thanks!

    joe, that is a good thought on the invoke command, I will try that now and see if it works and let you know.

    Tuesday, February 15, 2011 8:33 PM
  • Out of curiosity, is your script being run on the machines that are being added to the domain or from ones that are already in the domain?  If you are trying to tie this into a build process, it might not work until after the first restart after joining the domain.  Can you provide more details on how a machine is being added and where the script is supposed to be running from?
    fr3dd
    Tuesday, February 15, 2011 9:24 PM
  • fr3dd, the machines are already in the domain, but have not been in the domain very long.  Ultimately yes, I do want to tie this into the build process, but it will always take place after the machines has been rebooted multiple times after joining the domain.  Eventually, I want to make it a step in my Configuration Manager 2007 Task Sequence in OSD (where I would put this script is after joining domain and after multiple reboots in the process), but in my testing I have just been running it after it is all completed.  I see the same results running in OSD and running in Windows afterwards.  I have also found that "occassionally" when running script in OSD, for some reason it will work, but 90+% of the time it doesn't (also if I am re-imaging an existing machine, it always works).  Any thoughts on issues I might find running in this context? Thanks!
    Tuesday, February 15, 2011 9:44 PM
  • That is helpful insight into the issue you might be having.  I had a similar issue with a script that ran fine while logged on interactively, but not as a scheduled task.  I did a few things to address this issue:

    1. I tested the script from a command line using powershell -command ". C:\scriptname.ps1"
    2. I hard-coded all paths that I was determining with environment variables or Get-Location
    3. I installed the Windows SDK on a test system
    4. I used makecert to generate a certificate for signing
    5. I signed the script and made sure that it was from a trusted publisher
    6. After it was signed, I had to trust the certificate

    When a script runs in the background, it does not always behave the same as it does interactively.  I would try steps 1 and 2 to see if they help.  If they do not, you might want to look at the signing option.

    HTH - fr3dd


    fr3dd
    Wednesday, February 16, 2011 3:11 AM
  • From your description, it's as if DirectorySearcher is not yet working, or some .NET classes need time before they can be used. Since PowerShell itself seems OK, I wonder if you could use ADO instead, which uses COM. Just a wild guess, but maybe worth a try. I tried the following and it worked for me (I didn't add the computer to the group):

    Trap {"Error: $_"; Break;}
    Set-StrictMode -Version latest
    
    $ADGroup = "TestGroup"
    $ComputerName = "MyComputer"
    
    # Set LDAP Search Parameters to find computer account.
    $ComputerFilter = "(&(objectCategory=Computer)(CN=$ComputerName))"
    $GroupFilter = "(&(objectCategory=Group)(CN=$ADGroup))"
    
    $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
    $Root = $Domain.GetDirectoryEntry()
    
    $adoConnection = New-Object -comObject "ADODB.Connection"
    $adoCommand = New-Object -comObject "ADODB.Command"
    $adoConnection.Open("Provider=ADsDSOObject;")
    $adoCommand.ActiveConnection = $adoConnection
    $adoCommand.Properties.Item("Page Size") = 200
    $adoCommand.Properties.Item("Timeout") = 30
    $adoCommand.Properties.Item("Cache Results") = $False
    
    $strBase = $Root.distinguishedName
    $strAttributes = "ADsPath"
    $strScope = "subtree"
    
    $strQuery = "<LDAP://$strBase>;$ComputerFilter;$strAttributes;$strScope"
    $adoCommand.CommandText = $strQuery
    $adoRecordset = $adoCommand.Execute()
    
    Do
    {
      $ComputerPath = $adoRecordset.Fields.Item("ADsPath").Value
      $adoRecordset.MoveNext()
    } Until ($adoRecordset.EOF)
    
    $adoRecordset.Close()
    
    $strQuery = "<LDAP://$strBase>;$GroupFilter;$strAttributes;$strScope"
    $adoCommand.CommandText = $strQuery
    $adoRecordset = $adoCommand.Execute()
    
    Do
    {
      $GroupPath = $adoRecordset.Fields.Item("ADsPath").Value
      $adoRecordset.MoveNext()
    } Until ($adoRecordset.EOF)
    
    $adoRecordset.Close()
    $adoConnection.Close()
    
    # Get Group Object
    $Group = [ADSI]"$GroupPath"

     

    I assume your code is in a function, so the param statement is valid. I skipped it in my test and hard coded values.

    Richard Mueller


    MVP ADSI
    Wednesday, February 16, 2011 3:57 AM
  • fr3dd, thanks for the input!  I have already tried 1 and 2 and have confirmed that all variables that I am getting from env or get-location are getting properly populated.  I also already have a process in place for signing all of our scripts and this script is signed and does not appear to have any issues in that area.  However, I do agree that I am likely not seeing the same results in the background as I am interactively, I am just not sure why...
    Wednesday, February 16, 2011 1:23 PM
  • Richard, I tried your script and it appears to work when I run it interactively.  After if I run it in the background it just hangs forever, which is kind of odd.  I am not sure why it is hanging, but I will keep looking at it.  I am going on vacation today, so it will be mid-next week before I can troubleshoot some more.  Thanks for all the help!

    Wednesday, February 16, 2011 6:10 PM
  • Still just guessing, but if the script hangs, maybe it cannot retrieve the domain. Perhaps the DirectoryServices class is bad. Another try would be to hard code the DN of the domain as the base of the search. For example:

    $ADGroup = "TestGroup"
    $ComputerName = "MyComputer"
    
    # Set LDAP Search Parameters to find computer account.
    $ComputerFilter = "(&(objectCategory=Computer)(CN=$ComputerName))"
    $GroupFilter = "(&(objectCategory=Group)(CN=$ADGroup))"
    
    # $Domain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
    # $Root = $Domain.GetDirectoryEntry()
    
    $adoConnection = New-Object -comObject "ADODB.Connection"
    $adoCommand = New-Object -comObject "ADODB.Command"
    $adoConnection.Open("Provider=ADsDSOObject;")
    $adoCommand.ActiveConnection = $adoConnection
    $adoCommand.Properties.Item("Page Size") = 200
    $adoCommand.Properties.Item("Timeout") = 30
    $adoCommand.Properties.Item("Cache Results") = $False
    
    # $strBase = $Root.distinguishedName
    $strBase = "dc=MyDomain,dc=com"
    $strAttributes = "ADsPath"
    $strScope = "subtree"
    
    $strQuery = "<LDAP://$strBase>;$ComputerFilter;$strAttributes;$strScope"
    $adoCommand.CommandText = $strQuery
    $adoRecordset = $adoCommand.Execute()
    
    Do
    {
      $ComputerPath = $adoRecordset.Fields.Item("ADsPath").Value
      $adoRecordset.MoveNext()
    } Until ($adoRecordset.EOF)
    Write-Host "Computer: $ComputerPath"
    
    $adoRecordset.Close()
    
    $strQuery = "<LDAP://$strBase>;$GroupFilter;$strAttributes;$strScope"
    $adoCommand.CommandText = $strQuery
    $adoRecordset = $adoCommand.Execute()
    
    Do
    {
      $GroupPath = $adoRecordset.Fields.Item("ADsPath").Value
      $adoRecordset.MoveNext()
    } Until ($adoRecordset.EOF)
    Write-Host "Group: $GroupPath"
    
    $adoRecordset.Close()
    $adoConnection.Close()
    
    # Get Group Object
    $Group = [ADSI]"$GroupPath"
    Write-Host $Group.Name

     

    This removes the AD.DS .NET class and relies only on PowerShell and ADO. If this works, then your problem is related to the AD.DS .NET class.

    Richard Mueller


    MVP ADSI
    Wednesday, February 16, 2011 6:38 PM
  • Richard, sorry for the slow reply, but I am back in town and now have a lot more info.  I have found that both yours and mine scripts work fine interactively, but neither work in the background.  When I use your script, it simply hangs forever, I think when pulling the RecordSet.  On mine, it fails at: $Domain = New-Object System.DirectoryServices.DirectoryEntry.  If I hard-code this later in the script, then it dies at $Searcher.FindOne().  It appears when running in the background, for some reason I can't connect to AD at all?  It does appear, however, that my $Searcher object is somewhat working as I can succesfully output $Searcher.PropertiesToLoad (but for some reason is missing adspath, which I thought is always there?)?  Any thoughts?  Thanks!
    Wednesday, February 23, 2011 8:22 PM
  • It almost sounds like the process is running as local system or network service which might have trouble with the DirectoryServices objects.  How are you defining the credentials and/or the account which is being used to perform the directory modifications?
    fr3dd
    Wednesday, February 23, 2011 8:44 PM
  • fr3dd, thx for the reply.  I have been attempting to use RunAs to launch the script, and I just outputted $env:username in my script and confirmed that username outputted in my script is the right username with full rights to AD.  Is this an adequate way to confirm this? 
    Wednesday, February 23, 2011 9:02 PM
  • That might depend on where you are getting the account information.  How are you currently launching the RunAs process and are you storing the account credentials in a local file as a secure string?  Also, how are you testing the code in the above example?  Are you creating a PSCredential object?  Can you also log the password that you are passing to the script?
    fr3dd
    Wednesday, February 23, 2011 9:35 PM
  • I am actually letting Windows handle passing the credentials.  My long term goal, is to use Run As feature as part of the SCCM task sequence, but in short-term testing, I have been trying to have Windows Task Scheduler handle the RunAs; with the same results as the SCCM Task sequence. Because Windows is handling the pass, I can't output hte password, best I can think to do it output the instances username, which appears to be valid.  Thoughts?  thx!
    Thursday, February 24, 2011 1:32 PM
  • OK.  So, you are specifying credentials in the scheduled task and specifying to run powershell.exe with the script as an argument, correct?  I presume that you know the script is running because it is logging information.  Depending on the OS, I believe that there is a checkbox to use elevated privileges when the UAC is enabled.  Have you tried checking this option?
    fr3dd
    Thursday, February 24, 2011 5:44 PM
  • Correct, that is how I am running it.  I have confirmed its running by its output.  I have confirmed that the checkbox for elavated privileges is checked.
    Thursday, February 24, 2011 6:57 PM
  • I finally got this working.  Apparently using SCCM 2007 R2's Run as feature breaks the ability to access some objects.  I had to use the regular Run Command Line (without Runas) and pass credentials from within the script and then it works.
    • Marked as answer by bcehr Thursday, May 19, 2011 6:36 PM
    Thursday, May 19, 2011 6:36 PM
  • If you are going to "Run As" you need to log in as the "Run As" account, run the script and answer [A]llways for trusting a signed script. Then it should work.
    Wednesday, February 01, 2012 12:00 AM
  • http://fbinotto.blogspot.co.nz/2013/02/powershell-move-computer-to-ou-and-add.html
    Thursday, February 21, 2013 12:42 AM