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")
      $g2 = [adsi]"LDAP://CN=$groupname,$strOU"
      $g2.sAMAccountName = $groupname
      Write-Host "Created $groupname in $strOU"

For add-usertogroup we pass it a user DN.  DNs are the full LDAP distinguished name like:
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 $($ to $($"
      $members = $group.member
      $group.member = $members+$user.distinguishedName

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)

Wednesday, May 05, 2010

Use powershell to quickly backup all TFS Work Item Types

Hey all,
Just a quickie to easily backup all your current work items from all projects in Team Foundation Server 2008 SP1.  I wanted to do this ahead of a big migration and I am adverse to manual labor.

It starts w/ a function i found a couple years ago.  I don’t recall where or I would give credit here.  It is very useful for any tfs powershell manipulation.

Edit below to enter your $TFSHost and update your path if necessary.  I recommend running from a blank directory.  It will create a directory for each project and export each WIT for that project to the respective directories.

Update: I added another function from my library test-win32 and had that manage the default paths for x64 and x86 archs.

Good luck
$tfshost = "thshost"

function Test-Win32() {
    return [IntPtr]::size -eq 4

if (test-win32) {
      $tfstoolspath = "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE"
} else {
      $tfstoolspath = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE"

function get-tfs {
      #10-08 - cornasdf - i stole this from the web but this is a great way to connect to TFS
      # in your code you use: $tfs =  get-tfs $TFSHost
      #some quick examples from resolvedticketsmail.ps1:
      #get select from Stored query
      #$query = $tfs.WIT.Projects[$ProjectHoldingQuery].StoredQueries | where{$_.Name -eq $QueryViewName}

      #get results of specified query
      #$oldTickets = $tfs.WIT.Query($query.QueryText)

            [string] $serverName = $(throw 'serverName is required')

            # load the required dll

            $propertiesToAdd = (
                  ('VCS', 'Microsoft.TeamFoundation.VersionControl.Client', 'Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer'),
                  ('WIT', 'Microsoft.TeamFoundation.WorkItemTracking.Client', 'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore'),
                  ('CSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.ICommonStructureService'),
                  ('GSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.IGroupSecurityService')

            # fetch the TFS instance, but add some useful properties to make life easier
            # Make sure to "promote" it to a psobject now to make later modification easier
            [psobject] $tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)
            foreach ($entry in $propertiesToAdd) {
                  $scriptBlock = '
                        [System.Reflection.Assembly]::LoadWithPartialName("{0}") > $null
                  ' -f $entry[1],$entry[2]
                  $tfs | add-member scriptproperty $entry[0] $ExecutionContext.InvokeCommand.NewScriptBlock($scriptBlock)
            return $tfs

$tfs = get-tfs $tfshost

$projects = $tfs.wit.Projects

foreach ($proj in $projects) {
      if ( -not (Test-Path -path .\$($proj.Name))) {
            New-Item .\$($proj.Name) -type directory
      foreach ($wit in $proj.WorkItemTypes) {
            "Exporting $($proj.Name) : $($wit.Name) -> .\$($proj.Name)\$($wit.Name).xml"
            & "$tfstoolspath\witexport.exe"  /p "$($proj.Name)" /n "$($wit.Name)" /f ".\$($proj.Name)\$($wit.Name).xml" /t $tfshost