Using PowerShell and TFS API to list users in TFS 2010 (and 2012)

Update: Just wanted to let everyone know that this PowerShell script also runs under PowerShell 3.0 and it also works with TFS 2012 Team Explorer.

Update 2: After some training in Powershell, I updated the script below to also be in the form of an advanced function, taking optional parameters and also using TeamProjectPicker in case no parameters are passed. Enjoy!

First and foremost: this is my first post! :-)

Now, let's get down to business. I've been studying PowerShell for a short time and wanted to practice it and I have recently had a lot of requests from clients where they ask me the easiest way to get a "dump" of all the users on all projects in TFS.

When, why not use this as a good example to learn PowerShell and solve this problem?

Let's begin.

You can get a list of all users by using tfssecurity utility. For example: running

tfssecurity /imx "Project Collection Valid Users" /collection: <collectionUrl> will get you something like this:

Which is helpful somewhat, but not quite, since it doesn't group users by group membership.

Brian Harry posted in his blog a good solution to this problem using C#. The output of that code is something similar like this:

 

This is close to what we want, but the listing is not organized in a hierarchical fashion. Yes, you could fix the code to do it, but we're talking about using PowerShell here. :-)

Anyway, the script in this blog post will not require IT admins to have Visual Studio to compile code or rely on a developer to do it for them. It only requires PowerShell 2.0 (also works with 3.0) and Team Explorer 2012 (also works with 2010 SP1)

The script source code is:

#TFS Powershell script to dump users by groups

#for all team projects (or a specific team project) in a given collection

#

#Author: Marcelo Silva (marcelo.silva@microsoft.com)

#

#Copyright 2013

#

function Get-TFSGroupMembership

{

    Param([string] $CollectionUrlParam,

          [string[]] $Projects,

          [switch] $ShowEmptyGroups)

 

    $identation = 0

    $max_call_depth = 30

 

    function write-idented([string]$text)

    {

        Write-Output $text.PadLeft($text.Length + (6 * $identation))

    }

 

 

    function list_identities ($queryOption,

                              $tfsIdentity,

                              $readIdentityOptions

                              )

    {

        $identities = $idService.ReadIdentities($tfsIdentity, $queryOption, $readIdentityOptions)

       

        $identation++

   

        foreach($id in $identities)

        {

            if ($id.IsContainer)

            {

                if ($id.Members.Count -gt 0)

                {

                    if ($identation -lt $max_call_depth) #Safe number for max call depth

                    {

                        write-idented "Group: ", $id.DisplayName

                        list_identities $queryOption $id.Members $readIdentityOptions

                    }

                    else

                    {

                        Write-Output "Maximum call depth reached. Moving on to next group or project..."

                    }

                }

                else

                {

  if ($ShowEmptyGroups)

                    {

                        write-idented "Group: ", $id.DisplayName

                        $identation++;

                        write-idented "-- No users --"

                        $identation--;

                    }

                }

            }

            else

            {

                if ($id.UniqueName) {

                    write-idented "Member user: ", $id.UniqueName

                }

                else {

                    write-idented "Member user: ", $id.DisplayName

                }

            }

        }

 

        $identation--

    }

 

 

    # load the required dlls

 

    Add-Type -AssemblyName "Microsoft.TeamFoundation.Client, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",

                           "Microsoft.TeamFoundation.Common, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",

                           "Microsoft.TeamFoundation, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"

 

    #[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")

    #[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Common")

    #[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation")

 

    $tfs

    $projectList = @()

 

    if ($CollectionUrlParam)

    {

        #if collection is passed then use it and select all projects

        $tfs = [Microsoft.TeamFoundation.Client.TfsTeamProjectCollectionFactory]::GetTeamProjectCollection($CollectionUrlParam)

 

        $cssService = $tfs.GetService("Microsoft.TeamFoundation.Server.ICommonStructureService3")

   

        if ($Projects)

        {

   #validate project names

            foreach ($p in $Projects)

            {

                try

                {

                    $projectList += $cssService.GetProjectFromName($p)

                }

                catch

                {

    Write-Error "Invalid project name: $p"

                    exit

                }

            }

        }

        else

        {

            $projectList = $cssService.ListAllProjects()

        }

    }

    else

    {

        #if no collection specified, open project picker to select it via gui

        $picker = New-Object Microsoft.TeamFoundation.Client.TeamProjectPicker([Microsoft.TeamFoundation.Client.TeamProjectPickerMode]::MultiProject, $false)

        $dialogResult = $picker.ShowDialog()

        if ($dialogResult -ne "OK")

        {

            exit

        }

 

        $tfs = $picker.SelectedTeamProjectCollection

        $projectList = $picker.SelectedProjects

    }

 

 

    try

    {

        $tfs.EnsureAuthenticated()

    }

    catch

    {

        Write-Error "Error occurred trying to connect to project collection: $_ "

        exit 1

    }

 

    $idService = $tfs.GetService("Microsoft.TeamFoundation.Framework.Client.IIdentityManagementService")

 

    Write-Output ""

    Write-Output "Team project collection: " $CollectionUrlParam

    Write-Output ""

    Write-Output "Membership information: "

 

    $identation++

 

    foreach($teamProject in $projectList)

    {

        Write-Output ""

        write-idented "Team project: ",$teamProject.Name

   

        foreach($group in $idService.ListApplicationGroups($teamProject.Name,

                                                           [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid))

        {

            list_identities ([Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Direct) $group.Descriptor ([Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)

        }

    }

 

    $identation = 1

 

    Write-Output ""

    Write-Output "Users that have access to this collection but do not belong to any group:"

    Write-Output ""

 

    $validUsersGroup = $idService.ReadIdentities([Microsoft.TeamFoundation.Framework.Common.IdentitySearchFactor]::AccountName,

                 "Project Collection Valid Users",

                                                  [Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Expanded,

                                                  [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)

 

    foreach($member in $validUsersGroup[0][0].Members)

    {

        $user = $idService.ReadIdentity($member,

                                        [Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Expanded,

                                        [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)

 

        if ($user.MemberOf.Count -eq 1 -and -not $user.IsContainer)

        {

            if ($user.UniqueName) {

                write-idented "User: ", $user.UniqueName

            }

            else {

                write-idented "User: ", $user.DisplayName

            }

        }

                                                             

    }

}

 

 

 

 There are a couple of points to note about this script:

  • The script uses recursion to navigate a tree of group memberships. In my tests, I found that I had to specify a max # of recursions in order to avoid a call depth error in PowerShell.
  • The ShowEmptyGroups option can be used to list all groups, even ones that are empty
  • You can pass an array of team project names if you want, otherwise it will list all team projects in the given collection
  • If you don't pass a team collection URL, it will pop up a dialog so you can select the team project collection and the team projects you want the report for.
  • There is a "bonus" piece of code at the end that lists anyone that have permissions set at individual level without belonging to any groups.

You should get an output similar to the one below:

 

PS C:\Users\marcelos\Documents\TFS PowerShell> . .\TFS-Dump-Group-Membership.ps1

 

PS C:\Users\marcelos\Documents\TFS PowerShell> Get-TFSGroupMembership https://192.168.1.72:8080/tfs/defaultcollection -ShowEmptyGroups

Team project collection:

https://192.168.1.72:8080/tfs/defaultcollection

Membership information:

      Team project: JobsSite

            Group: [JobsSite]\Project Administrators

                  Member user: TFS10RTM\TFSSETUP

            Group: [JobsSite]\Contributors

                  Member user: TFS10RTM\Darren

                  Member user: TFS10RTM\Nicole

            Group: [JobsSite]\Readers

                  Member user: TFS10RTM\Larry

            Group: [JobsSite]\Builders

                  -- No users --

      Team project: TailspinToys

            Group: [TailspinToys]\Builders

                  -- No users --

            Group: [TailspinToys]\Readers

                  -- No users --

            Group: [TailspinToys]\Project Administrators

                  Member user: TFS10RTM\Andrew

                  Member user: TFS10RTM\TFSSETUP

            Group: [TailspinToys]\Contributors

                  Member user: TFS10RTM\Darren

                  Member user: TFS10RTM\Larry

      Team project: MyApplication

            Group: [MyApplication]\Project Administrators

                  Member user: TFS10RTM\TFSSETUP

            Group: [MyApplication]\Contributors

                  Group: [MyApplication]\DatabaseDevelopers

                        Member user: TFS10RTM\Andrew

                        Member user: TFS10RTM\Darren

            Group: [MyApplication]\Readers

                  -- No users --

            Group: [MyApplication]\Builders

                  -- No users --

            Group: [MyApplication]\DatabaseDevelopers

                  Member user: TFS10RTM\Andrew

                  Member user: TFS10RTM\Darren

Users that have access to this collection but do not belong to any group:

      User: TFS10RTM\Renee

PS C:\Users\marcelos\Documents\TFS PowerShell> 

 

There you have it!

Comments

  • Anonymous
    April 01, 2013
    The comment has been removed

  • Anonymous
    April 12, 2013
    Hi Val!What version of Team Explorer you have on your PC? This script will work on Team Explorer 2010 and 2012.It also should work on PowerShell v2 and v3.Also, check out the updated version with better handling on parameters, and it also makes it a function.Can you post the script output? (make sure to mask identities already displayed, your server name and collection/team project names also)

  • Anonymous
    July 07, 2013
    We need to query work items for particular member from the project.We are not able to connect to TFS 2012 through Powershell.We have installed visual studio 2012 and TFS Power tools and Powershell v2.Kindly let us know if what all tools we need to install at our machine to connect to the TFS.

  • Anonymous
    July 10, 2013
    Mangesh, all you need is what you have installed: Visual Studio 2012 and PowerShell v2. You don't need the PowerTools but they don't hurt.The connection failure may be due to a problem in your script or maybe permissions issue. Can you post the error msg you get when trying to run the PowerShell script?

  • Anonymous
    November 15, 2013
    The comment has been removed

  • Anonymous
    November 21, 2013
    Like George, the script does not do anything for me either.  I am using the TFS Power Tools Powershell command prompt.  Any ideas?

  • Anonymous
    December 12, 2013
    If you're running the script and nothing happens, try calling the function directly like:PS C:>Get-TFSGroupMembership 192.168.1.72/.../defaultcollectionI'll update the example since it assumes the script does not have a function.

  • Anonymous
    February 14, 2014
    The comment has been removed

  • Anonymous
    March 11, 2014
    Getting this error:Exception calling "ReadIdentities" with "4" argument(s): "An item with the same key has already been added."At C:ScriptsPowershellTFS-Dump-Group-Membership.ps1:155 char:5    $validUsersGroup =  $idService.ReadIdentities([Microsoft.TeamFoundation.Fram ...~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException   + FullyQualifiedErrorId : TeamFoundationServiceException Cannot index into a null array.At C:ScriptsPowershellTFS-Dump-Group-Membership.ps1:160 char:24    foreach($member in $validUsersGroup[0][0].Members)                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   + CategoryInfo          : InvalidOperation: (:) [], RuntimeException   + FullyQualifiedErrorId : NullArray

  • Anonymous
    May 09, 2014
    Script worked great. Did get a few errors please see below. Just got to remember to use the . .Scriptname, I almost missed the first dot. :)Exception calling "ReadIdentities" with "3" argument(s): "Server was unable to process request. ---> There was an error generating the XML document. ---> TF20507: The stringargument contains a character that is not valid:'u8236'. Correct the argument, and then try the operation again.Parameter name: value"At C:Network_DrivePowerShellTFSUserList.ps1:28 char:9        $identities = $idService.ReadIdentities($tfsIdentity, $queryOption, $rea ...~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~   + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException   + FullyQualifiedErrorId : TeamFoundationServiceException

  • Anonymous
    January 27, 2015
    In the next version of this script, it would be nice to store the output into a PowerShell object instead of plain text.  An object will allow information to be extracted by other PowerShell commands very easily.Thanks.

    • Anonymous
      July 20, 2016
      When I try to run get-TFSGROUPMEMBERShip it can't find the cmdlet.. I have version 3.0 Powershell where do I get the cmdlet
      • Anonymous
        February 10, 2017
        That cmdlet is actually a function described on the script. You have to run the script first so it loads Get-TFSGroupMembership function into memory.
  • Anonymous
    September 19, 2016
    It's great!!! still useful for TFS2015.Though there is something unimportant error like this:https://social.msdn.microsoft.com/Forums/windowsdesktop/zh-CN/64465c9b-83d4-45e4-a455-231027f83790/how-to-list-all-the-groups-and-their-users-under-one-project?forum=tfsadmin

    • Anonymous
      February 10, 2017
      I saw that but not sure why it could not find TFSSecurity. It may have been due to a path issue. I recently had a colleague that used the script to run it on TFS 2015.3, so it should work fine on the latest TFS 2015 as long as you make sure the assemblies loaded are Version 14.0.0
  • Anonymous
    February 23, 2017
    Is there a way to retrieve each user's assigned permissions or a group's permissions? I need to create a "system generated list" that shows users, groups, and their permissions to meet our financial/CM auditing requirements.

    • Anonymous
      February 24, 2017
      The comment has been removed
  • Anonymous
    November 27, 2017
    Hi Marcelo,First of all Thank you for this wonderful post! This has been very helpful to me for pulling the list of users and their membership from my TFS installation. I also need to get the Team Administrator name for a TFS Team, could you please help me what updates do I need to make to your script to get that?Thanks,Pawan

    • Anonymous
      February 08, 2018
      Hi Pawan. I haven't had time, unfortunately, to update the script to look at team memberships.Maybe in the next month or so, when I publish my "yearly" blog entry :-) I may consider doing it.RgdsMarcelo
  • Anonymous
    April 11, 2018
    Marcelo, thank you so much for this post. I have found it very valuable on multiple occasions.