Thursday, May 06, 2010

Using Powershell to create groups, populate groups and retrieve LDAP distinguished names

Update 20121203 - I just noticed this is still getting hits.  this is largely deprecated, from Powershell 2.0 and beyond, do your self a favor and use the ActiveDirectory module.  if you don't have access to that module for some reason (they do exist) the below should still work.

Update: 20100507 - updated the sam search string to do fuzzy matching.

A couple more functions I needed during a recent project.  Using powershell of course, we needed to create a group in ad in a specific OU.  We also wanted to populate that group.  To do the second, I needed a helper function to get the distinguished name of a group or user.  I leverage two main sites for most of these with some light modifications.   

For create group we pass it the name of our new group and the OU we want to create our group in, such as:
Create-group “Newgroup” “OU=ServerGroups,DC=example,DC=com”

function create-group ($groupname, $strOU) {
      $OU = [adsi]"LDAP://$strOU"
      $group = $ou.Create("Group", "CN=$groupname")
      $group.setinfo()
      $g2 = [adsi]"LDAP://CN=$groupname,$strOU"
      $g2.sAMAccountName = $groupname
      $g2.setinfo()
      Write-Host "Created $groupname in $strOU"
}

For add-usertogroup we pass it a user DN.  DNs are the full LDAP distinguished name like:
LDAP://CN=myuser,OU=Admins,DC=example,DC=com 
So we would pass something like
Add-usertogroup “LDAP://CN=myuser,OU=Admins,DC=example,DC=com” “LDAP://CN=Newgroup,OU=ServerGroups,DC=example,DC=com”

That is kind of annoying so you could also use the get-dn function below. 
The safe way:
$userDN = get-dn "myuser"
$groupDN = get-dn "newgroup"
add-usertogroup $userDN $groupDN

or the one liner
add-usertogroup (get-dn "myuser") (get-dn "newgroup")

function add-usertogroup ($userDN, $groupDN) {
      $user = [adsi]$userDN
      $group = [adsi]$groupDN
      Write-Host "Adding $($user.cn) to $($group.cn)"
      $members = $group.member
      $group.member = $members+$user.distinguishedName
      $group.setinfo()
}

I would further note this excellent site describing how to write an LDAP filter.  It cleared a few things up for me at long last.  I can’t believe I never realized that & was a logical AND...  Pretty straightforward afterwards but the writeup above helped me bridge the gap.  Note that it is for some software or other so ignore the part about escaping the special characters at the top. B)

function get-dn ($SAMName)
{
      $root = [ADSI]''
      $searcher = new-object     System.DirectoryServices.DirectorySearcher($root)
      #note: if you don't want fuzzy searches, remove the *s from the line below.  
      #this will force a match of the search string only - thanks jc for the tip
      $searcher.filter = "(&(|(objectClass=user)(objectClass=group))(sAMAccountName=*$SAMName*))"
      $user = $searcher.findall()
      if ($user.count -gt 1)
      {   
            $count = 0
            foreach($i in $user)
            {
            write-host $count ": " $i.path
            $count = $count + 1
      }
    $selection = Read-Host "Please select item: "
      return $user[$selection].path
      } else {
      return $user[0].path
      }
}

Note that it matches the SAM Account Name (aka the ‘Pre-Windows 2000’ name in the AD snapin)
Enjoy

5 comments:

  1. This reminds me if my little group membership script....

    param([string]$name)

    if (!$name)
    {
    Write-Error "Something went wrong. Syntax`: .\Get_Membership.ps1 `"Name of Group`" in quotes"
    Exit
    }

    $objDomain = [adsi]""
    $objSearcher = New-Object System.DirectoryServices.DirectorySearcher($objDomain)

    $objSearcher.Filter = "(&(objectCategory=group)(CN=*$name*))"
    $colResults = $objSearcher.FindAll()

    if($colResults.count -eq 0)
    {
    "Group not found"
    }
    elseif($colResults.count -gt 1)
    {
    "More than 1 group found, please add more words to search"
    }
    else
    {
    $colResults[0].getdirectoryentry().member | %{[adsi]("LDAP://"+$_)} | %{$_.cn.tostring()}# + " - " + $_.scriptPath}
    }

    You are using the parameter itself in the search filter. Nice and quick but if you have a group called 'Group1' and a group called 'group12' and you search for 'group1', you will get back more than one group. So as always, you have to be careful with wildcards.

    The half commented line when outputting the membership is if you would like to output another attribute, in this case I was checking what their login scripts were.

    ReplyDelete
  2. Nice and quick but if you have a group called 'Group1' and a group called 'group12' and you search for 'group1', you will get back more than one group.

    Good point but if you look, there is logic for that. if there is more than one result returned, the script asks you which one you want. Note that I haven't been able to make it give me more than one result though. If I have group1, group122, and group123 and I search for group1, i get group1. Search for group12 and I don't get anything.

    ReplyDelete
  3. This reminds me if my little group membership script....

    Which is still being used. I obviously have not referred to it lately b/c I just spent some time reinventing the wheel. funnily enough, I was just writing a similar script to compare groups in two domains. Had i seen this post maybe 3 hours earlier, I could have saved myself some time. B)

    ReplyDelete
  4. Note that I haven't been able to make it give me more than one result though. If I have group1, group122, and group123 and I search for group1, i get group1. Search for group12 and I don't get anything.

    Throw in some wildcards, your script already handles multiple returns...

    $searcher.filter = "(&(|(objectClass=user)(objectClass=group))(sAMAccountName=*$SAMName*))"

    Give that a try!

    ReplyDelete
  5. that was it. I updated the post above. Thanks.

    ReplyDelete

analytics