Showing posts with label TFS. Show all posts
Showing posts with label TFS. Show all posts

Tuesday, April 8, 2014

Enumerating TFS Permissions for items within the TPC for audit and logging

Below is a powershell cmdlet that I threw together to enumerate data out of our TFS instance.  This is used to keep track of permissions in the event we need to re-create them and to allow us to audit it to make sure things don't change.

This can also be used to keep a pre-production environment's permissions in sync with Production.

In 2014 I was not the best at Powershell but it works :)

param ($serverName = $(throw 'please specify a TFS server name'))

function GetGroupMembership {
 [CmdletBinding()]
 [OutputType([System.Data.Datatable])]
 PARAM (
  [Parameter(Mandatory=$true, Position = 0)]
  [Microsoft.TeamFoundation.Framework.Client.TeamFoundationIdentity]
  $TFIdentity
 )
 
 $tabName = $TFIdentity.DisplayName + "Membership"
 $table = New-Object system.Data.DataTable “$tabName”
 $col1 = New-Object system.Data.DataColumn GroupName,([string])
 $col2 = New-Object system.Data.DataColumn DisplayName,([string])
 $col3 = New-Object system.Data.DataColumn UniqueName,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
  
 #Membership
 $GroupIdentity = $ims.ReadIdentity($TFIdentity.Descriptor,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Direct,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
 $members = $ims.ReadIdentities($GroupIdentity.Members,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Direct,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
 foreach($member in $members)
 {
  $row = $table.NewRow()

  #Enter data in the row
  $row.GroupName = $TFIdentity.DisplayName
  $row.DisplayName = $member.DisplayName
  $row.UniqueName = $member.UniqueName
  
  #Add the row to the table
  $table.Rows.Add($row)
 }
 
 return @(,$table )
}

function GetPermissions {
 [CmdletBinding()]
 [OutputType([System.Data.Datatable])]
 PARAM (
  [Parameter(Mandatory=$true, Position = 0)]
  $QueryName,
  [Parameter(Mandatory=$true, Position = 1)]
  [Microsoft.TeamFoundation.Framework.Client.AccessControlList]
  $acl,
  [Parameter(Mandatory=$true, Position = 2)]
  $namespace,
  [Parameter(Mandatory=$true, Position = 3)]
  $objectType,
  [Parameter(Mandatory=$true, Position = 4)]
  $objectPath
 )
 
 $PermissionstabName = $QueryName
 
 $table = New-Object system.Data.DataTable “$PermissionstabName”
 $col1 = New-Object system.Data.DataColumn ObjectPath,([string])
 $col2 = New-Object system.Data.DataColumn ObjectType,([string])
 $col3 = New-Object system.Data.DataColumn Name,([string])
 $col4 = New-Object system.Data.DataColumn Permission,([string])
 $col5 = New-Object system.Data.DataColumn Value,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
 $table.columns.add($col4)
 $table.columns.add($col5)

 foreach ($ace in $acl.AccessControlEntries)
 {
  if ($ace.IsEmpty)
  {
   continue
  }
  
  $haspermission = $false
  $DenyPermissions = @{}
  $CalculatedPermissions = @{}
  $AllowPermissions = @{}
  foreach ($action in $namespace.description.Actions)
  {
   
   $allowed = ($action.bit -band $ace.Allow) 
   $denied =  ($action.bit -band $ace.Deny)
   if (-not $allowed -and -not $denied)
   {
    continue
   }
   $haspermission  =$true
   if ($allowed)
   { 
    $CalculatedPermissions.Add($action.Name,"Allow")
    $AllowPermissions.Add($action.Name,$action.Name)
   }
   else
   {
    $CalculatedPermissions.Add($action.Name,"Deny")
    $DenyPermissions.Add($action.Name,$action.Name)
   }
   
  }
  
  if ($haspermission)
  {
   
   $identity = $ims.ReadIdentity($ace.Descriptor,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::None,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::IncludeReadFromSource)
   
   $name = $identity.DisplayName
   
   Foreach($permission in $CalculatedPermissions.GetEnumerator())
   {
    $row = $table.NewRow()

    #Enter data in the row
    $row.ObjectPath = $objectPath 
    $row.ObjectType = $objectType
    $row.Name = $name
    $row.Permission = $permission.Name
    $row.Value = $permission.value

    #Add the row to the table
    $table.Rows.Add($row)
   }
  }
 }
 
 return @(,$table )
}

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.VersionControl.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.WorkItemTracking.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Common')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation')

$tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)

$css = $tfs.GetService([Microsoft.TeamFoundation.Server.ICommonStructureService])
$auth = $tfs.GetService([Microsoft.TeamFoundation.Server.IAuthorizationService])
$gss = $tfs.GetService([Microsoft.TeamFoundation.Server.IGroupSecurityService])
$ss = $tfs.GetService([Microsoft.TeamFoundation.Framework.Client.ISecurityService])
$vcs = $tfs.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])
$ims = $tfs.GetService([Microsoft.TeamFoundation.Framework.Client.IIdentityManagementService])
$wis = $tfs.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]) 
$cssNamespace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::CommonStructureNodeSecurityGuid)

$membershipds = New-Object System.Data.DataSet
$permissiondt = New-Object System.Data.DataSet
$Objectpermissiondt = New-Object System.Data.DataSet

#get all TPC Groups
$tpcGroups = $IMS.ListApplicationGroups($null, [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::None)

foreach ($tpcgroup in $tpcGroups)
{
 
 $members = GetGroupMembership $tpcgroup
 if ($members.rows.count -gt 0)
 {
  $membershipds.Tables.Add($members)
 }
}
$Namespaces = $ss.GetSecurityNamespaces()
#Get all the permissions for top level NameSpaces
foreach($namespace in $Namespaces)
{
 
 $NameSpaceToken = ""
 
    switch ($namespace.Description.DisplayName)
 {
  "Server" { $NameSpaceToken =  [Microsoft.TeamFoundation.Framework.Common.FrameworkSecurity]::FrameworkNamespaceToken}
  "Build" { $NameSpaceToken =  [Microsoft.TeamFoundation.Build.Common.BuildSecurity]::PrivilegesToken}
  "BuildAdministration" { $NameSpaceToken =  [Microsoft.TeamFoundation.Build.Common.BuildSecurity]::PrivilegesToken}
  "Workspaces" {  $NameSpaceToken =  [Microsoft.TeamFoundation.VersionControl.Common.SecurityConstants]::GlobalSecurityResource}
  "Collection" {}#Namespace:
  "WorkItemTrackingAdministration" {} #
  "CSS" {  }
  "Iteration" {}
  "VersionControlPrivileges" {$NameSpaceToken =  [Microsoft.TeamFoundation.VersionControl.Common.SecurityConstants]::GlobalSecurityResource} 
  "WorkItemQueryFolders" {}
  "Project" {  $NameSpaceToken = [Microsoft.TeamFoundation.PermissionNamespaces]::Project}
  default
  {
  continue
  }
 }
 try
 {
  $groupAcl = $namespace.QueryAccessControlList($NameSpaceToken,$null,$false)
  $table = GetPermissions $namespace.Discription.Name $groupAcl $namespace "Group" $namespace.Discription.Name
  $permissiondt.Tables.Add($table)
 }
 Catch
 {
 
 }
}

foreach ($project in $css.ListProjects())
{
 $projectGroups = $IMS.ListApplicationGroups($project.Uri,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
  
 foreach($group in $projectGroups)
 {
 
  #only get the groups we care about
  if ($group.DisplayName -eq "[$($project.Name)]\Build Administrators" -or $group.DisplayName -eq "[$($project.Name)]\Project Administrators" -or $group.DisplayName -eq "[$($project.Name)]\Contributors" -or $group.DisplayName -eq "[$($project.Name)]\Readers")
  {
   
  }
  else
  {
   continue;
  }
  
  $members = GetGroupMembership $group
  if ($members.rows.count -gt 0)
  {
   $membershipds.Tables.Add($members)
  }
  
  $projectsecNameSpace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::ProjectSecurityGuid)
  
  #Get Project Permissions
  $ProjectSecurityToken = [Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::ProjectSecurityPrefix + $project.Uri
  $groupacl = $projectsecNameSpace.QueryAccessControlList($ProjectSecurityToken,  [Microsoft.TeamFoundation.Framework.Client.IdentityDescriptor[]]@($group.descriptor), $false)
   
  $table =  GetPermissions $group.DisplayName  $groupAcl $projectsecNameSpace "Project" $project.Name
  $permissiondt.Tables.Add($table)
  
  #Get Top Level Build Permissions for the Project
  $BuildsecNameSpace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Build.Common.BuildSecurity]::BuildNamespaceId)
  
  $teamProjectGuid = [Microsoft.TeamFoundation.LinkingUtilities]::DecodeUri($project.Uri).ToolSpecificId
  $groupacl = $BuildsecNameSpace.QueryAccessControlList($teamProjectGuid,  $null, $false)
  $tablename = $group.DisplayName + "Build Permissions"
  $table = GetPermissions $tablename $groupAcl $BuildsecNameSpace "Build" $project.Name
  
  $permissiondt.Tables.Add($table)
 }
 
 #Get Root Area Path Permissions
 $rootAreaNodeACL = $cssNamespace.QueryAccessControlList($wis.Projects[$project.Name].AreaRootNodeUri,$null,$false)
 $table = GetPermissions "$($project.Name) Root AreaPath Permissions" $rootAreaNodeACL  $cssNamespace "Area Path" $wis.Projects[$project.Name].Name
 $Objectpermissiondt.Tables.Add($table)

  
 #Get Child Area Path Permissions 
 foreach ($area in $wis.Projects[$project.Name].AreaRootNodes)
 {
  $areapath = $area.Path
  
  $areaseclist = $cssNamespace.QueryAccessControlList($area.uri, $null, $true)
  
  $table = GetPermissions "$areapath AreaPath Permissions" $areaseclist  $cssNamespace "Area Path" $areapath
  $Objectpermissiondt.Tables.Add($table)
 }
 
 try
 {
  $vcproject = $vcs.TryGetTeamProject($project.Name)
 }
 catch
 {
  continue
 }
 
 #Get Version Control permissions on all VC objects
 $vcAcls = $vcs.GetPermissions(@($vcproject.ServerItem), [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full)
  
 $table = New-Object system.Data.DataTable "$($project.Name)VCPermissions"
 $col1 = New-Object system.Data.DataColumn ObjectPath,([string])
 $col2 = New-Object system.Data.DataColumn ObjectType,([string])
 $col3 = New-Object system.Data.DataColumn Name,([string])
 $col4 = New-Object system.Data.DataColumn Permission,([string])
 $col5 = New-Object system.Data.DataColumn Value,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
 $table.columns.add($col4)
 $table.columns.add($col5)
 
 foreach($perm in $vcAcls)
 {
  foreach($entry in $perm.Entries)
  {
   foreach($allow in $entry.Allow)
   {
    
    $row = $table.NewRow()
    $row.ObjectPath = $perm.ServerItem
    $row.ObjectType = "VC"
    $row.Name = $entry.IdentityName
    $row.Permission = $allow
    $row.Value = "allow"
    
    #Add the row to the table
    $table.Rows.Add($row)
   }
   foreach($deny in $entry.Deny)
   {
    
    $row = $table.NewRow()
    $row.ObjectPath = $perm.ServerItem
    $row.ObjectType = "VC"
    $row.Name = $entry.IdentityName
    $row.Permission = $deny
    $row.Value = "deny"
    
    #Add the row to the table
    $table.Rows.Add($row)
   }
  }
 }
 
 $Objectpermissiondt.Tables.Add($table)
}

$membershipds.Tables | Format-Table -AutoSize 
$permissiondt.Tables | Format-Table -AutoSize 
$Objectpermissiondt.Tables | Format-Table -AutoSize

Friday, October 18, 2013

TFS 2012 and 2013: Changing bug WIT states breaks the ALM backlog and board with TF400917: The current configuration is not valid of this feature

Some teams have found the default bug states don't work well with their teams.  Moving to the Active, Resolved, Closed workflow for the bug WIT has been the most popular with those I have worked with.  I think part of this is because many teams (or their managers) are stuck in old-school waterfall mode while trying to claim they are embracing SCRUM or AGILE.

Either way if you change the BUG states from the default for the SCRUM or AGILE process template you will find the Backlog and board pages on web view for TFS 2012 or 2013 will break.  If you revert your WIT change the ALM features will work again. 

Broken ALM features will show: TF400917: The current configuration is not valid of this feature.  This feature cannot be used until you correct the configuration.

The link to the "Learn more about configuring features" page will take you to MSDN (http://msdn.microsoft.com/en-us/library/vstudio/jj159364.aspx) which does not have any info on this. 

http://blog.nwcadence.com/configure-features-in-team-foundation-server-2013/ has good info on how to setup the features if they never were setup before.  But in our situation it use to work and is now BROKE!

So what's up?
A few posts will tell you to use
witadmin exportcommonprocessconfig then
witadmin importcommonprocessconfig without making any change to the .xml from the export. 

This will result in an error tf400536.  It will say the "requirementworkitems" entries are wrong. 

So how the hell do you fix it?

You need to run
  1. witadmin exportcategories /p:<PROJECT> /collection:<URL TO TPC> /f:catagories.xml
  2. Edit the file and remove <WORKITEMTYPE name="Bug" /> from <CATEGORY refname="Microsoft.RequirementCategory" name="Requirement Category">
  3. witadmin importcategories /p:<PROJECT> /collection:<URL TO TPC> /f:catagories.xml

 

Wednesday, August 7, 2013

Migrating TFS (2010 or 2012) Personal Queries from one project to another via Powershell

The powershell script below can be saved run on a users system to migrate their personal TFS Queries from one project to another.  This is useful when migrating from TFS 2010 to TFS 2012.

You will need to update the script Source and Target Server URLs, TPC and Projects.  I went down the path to hard code them and not take them as arguments so users did not have to enter anything.  The script will throw errors if the query being migrated is not valid on the target project.
function ProcessQueryItem ($queryFolder, $parentFolder, $sourceProjectName)
{
 $newItem = $null
 foreach ($subQuery in $queryFolder)
 {
  if ($subQuery -is [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder])
  { 
   $newItem = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder -ArgumentList    @($subQuery.Name)
   if (-not $parentFolder.Contains($subQuery.Name))
   {
    Write-Host "Adding Query Folder $subQuery"
    $parentFolder.Add($newItem);
    $WIStoreRight.Projects[$RightProject].QueryHierarchy.Save()
    ProcessQueryItem $subQuery $newItem $sourceProjectName
   }
   else
   {
    Write-Host "Query Folder "$subQuery.name" already exists" -foregroundcolor Yellow
   }

  }
  else
  {
   Write-Host "Creating query "$subQuery.Name
   $oldDef = [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryDefinition]$subQuery
   $queryText = $oldDef.QueryText.Replace("$sourceProjectName\", "$RightProject\")
   $queryText = $queryText.Replace("[System.IterationPath] under '" + $sourceProjectName + "'", "[System.IterationPath] under '" + $RightProject +"'");
   $newItem = new-object Microsoft.TeamFoundation.WorkItemTracking.Client.QueryDefinition -ArgumentList @($subQuery.Name,$queryText)
      
    if ($parentFolder.Contains($subQuery.Name) -ne $true )
    {
    $parentFolder.Add($newItem)
    $WIStoreRight.Projects[$RightProject].QueryHierarchy.Save()
    }
    else
    {
    Write-Host "Query Definition $subQuery already exists" -foregroundcolor Yellow
    }
  
  }
 }
}

[URI]$Projecturileft = "http://sourceServer:8080/tfs/SourceTPC/"
$leftProject = "SourceProject"
[URI]$ProjecturiRight = "http://targetServer:8080/tfs/TargetTPC/"
$RightProject = "TargetProject"

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.WorkItemTracking.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Server')

$tfsleft = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($Projecturileft) 
$WIStoreleft = $tfsleft.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]) 
$WIStoreleft.RefreshCache($true)
$WIStoreleft.SyncToCache() | out-null

$LeftQuerystore = $WIStoreleft.Projects[$LeftProject].QueryHierarchy

$tfsRight = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($ProjecturiRight) 
$WIStoreRight = $tfsRight.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]) 
$WIStoreRight.RefreshCache($true)
$WIStoreRight.SyncToCache() | out-null
foreach ($queryFolder in $LeftQuerystore)
{
 if ($queryFolder.IsPersonal)
 {
  $RightMyQueriesFolder = [Microsoft.TeamFoundation.WorkItemTracking.Client.QueryFolder]$WIStoreRight.Projects[$RightProject].QueryHierarchy["My Queries"];
  
  ProcessQueryItem $queryFolder $RightMyQueriesFolder $leftProject
  Write-Host "Personal Queries Migrated"
 }
}

Monday, July 15, 2013

How to use WIQL to query for work items in TFS 2012 via the URL

These days I spend a large amount of time in TFS 2012. 

In TFS 2008 and 2010 users could use the q.aspx page to run a query using WIQL as defined by the 2010 WIQL spec (http://msdn.microsoft.com/en-us/library/bb130306(v=vs.100).aspx). Ken O' Bonn has a great post on this at http://blogs.msdn.com/b/kenobonn/archive/2009/05/28/how-to-link-directly-to-tswa-2008-or-2010-beta1-work-items-or-queries.aspx

However in TFS 2012 the URLs for everything changed and I was unable to find any information on how to accomplish the same task. How can you provide a URL to users that queries for work items without writing and saving a query to the TFS server. 

The key is I don't want to save a query to TFS.   

So after some trial and error I found the way!

http://<ServerAddress>:8080/tfs/<TPC Name>/<Project>/_workitems#_a=query&wiql=<WIQL>

An example:

http://tfstest:8080/TPC1/project1/_workItems#_a=query&wiql=select id, [System.areapath] from workitems

The WIQL for 2012 is defined at http://msdn.microsoft.com/en-us/library/bb130306.aspx#language






10 Years from last post

 Well world!   After the last almost 10 years I have been up to a few things in life and work.  Most recently I was working at Microsoft on ...