I am a writing a .Net application using the VSTS/TFS Rest .Net libraries and in one place I need to update workitems' System.AssignedTo field values and while I do want to adhere to the new(ish), unique displayname rules for identity work item fields, I have a hard time finding a method to get the Unique display name(s) for given identities.
The old / client object model does have an explicit helper method to get these unique names, but I have not found any rest endpoint nor client api method that would provide the same.
So I am wondering, given a list of identities, how do I get their corresponding unique display names which I can use to unambiguously set identity work item fields?
String collectionUri = "http://collectionurl/";
VssCredentials creds = new VssClientCredentials();
creds.Storage = new VssClientCredentialStorage();
VssConnection connection = new VssConnection(new Uri(collectionUri), creds);
TeamHttpClient thc = connection.GetClient<TeamHttpClient>();
List<IdentityRef> irs = thc.GetTeamMembersAsync("ProjectName","TeamName").Result;
foreach (IdentityRef ir in irs)
{
Console.WriteLine(ir.UniqueName);
Console.WriteLine(ir.DisplayName);
}
You could try the code below to get unique name:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://tfsserver:8080/tfs"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "[TEAM FOUNDATION]\\Team Foundation Valid Users", MembershipQuery.Expanded, ReadIdentityOptions.None);
TeamFoundationIdentity[] ids = ims.ReadIdentities(tfi.Members, MembershipQuery.None, ReadIdentityOptions.None);
foreach (TeamFoundationIdentity id in ids)
{
if (id.Descriptor.IdentityType == "System.Security.Principal.WindowsIdentity")
{
Console.WriteLine(id.DisplayName);
Console.WriteLine(id.UniqueName);
}
}
Console.ReadLine();
}
}
}
foreach (var workItem in workItems)
{
if (workItem.Fields.ContainsKey("System.AssignedTo"))
{
var person = (IdentityRef)workItem.Fields["System.AssignedTo"];
string codereview_reviewer = person.DisplayName;
Console.WriteLine(codereview_reviewer);
}
}
Related
I have a console application that uses credential to connect Share Point Online from "Windows credential manager" and request Share Point Online to download all items from list and further this information is used to start the procedure on the SQL Server
I'm not experienced in writing such application, but particular this application works normally when it executes from Visual Studio or by executing file. But it fails with error code 403 when it executes by SQL Agent on the same computer.
For clarification: SQL Server runs on the same computer where I develop the application and where is Share Point credential is stored.
I start job under my windows account using proxy, so I suppose there is no problem with credential. My windows account has sysadmin permissions on the SQL Server and admin persmissions on the OS.
But after searching for whole day I have no idea where the mistake could be.
Please help me with advice and tell me where I made a mistake.
Thanks in advance.
There is output error from SQL Job:
Message
Executed as user: LAPTOP\username. Unhandled Exception:System.Net.WebException:
The remote server returned an error: (403) Forbidden.
at System.Net.HttpWebRequest.GetResponse()
at Microsoft.SharePoint.Client.SPWebRequestExecutor.Execute()
at Microsoft.SharePoint.Client.ClientContext.GetFormDigestInfoPrivate()
at Microsoft.SharePoint.Client.ClientContext.EnsureFormDigest()
at Microsoft.SharePoint.Client.ClientContext.ExecuteQuery()
at SharePointTrigger.Program.GetNewItem(String targetSiteUrl, String listName,
String filedName) in
C:\Work\Korus\Pernod\Vista\Repose\ETL_LoadDataCSV\SharePointTrigger\Program.cs:line 114
at SharePointTrigger.Program.Main(String[] args) in
C:\Work\Korus\Pernod\Vista\Repose\ETL_LoadDataCSV\SharePointTrigger\Program.cs:line 40.
Process Exit Code -532462766. The step failed.
And there is console application (there is no App.config file and DatabaseOperation class. However DatabaseOperation class contains only method for executing procedure on the SQL Server):
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using System.Web.UI.WebControls;
using Microsoft.Graph;
using Microsoft.SharePoint.Client;
using OfficeDevPnP.Core.Utilities;
using SP = Microsoft.SharePoint.Client;
using System.Configuration;
using System.Collections.Specialized;
using static SharePointTrigger.DatabaseOperation;
namespace SharePointTrigger
{
class Program
{
static void Main(string[] args)
{
//To get values from App.config
string SQLServerName = ConfigurationManager.AppSettings.Get("SQLServerName");
string SQLDatabaseName = ConfigurationManager.AppSettings.Get("SQLDatabaseName");
string SQLProcedureName = ConfigurationManager.AppSettings.Get("SQLProcedureName");
string SQLJobName = ConfigurationManager.AppSettings.Get("SQLJobName");
string SPSiteURL = ConfigurationManager.AppSettings.Get("SPSiteURL");
string SPListName = ConfigurationManager.AppSettings.Get("SPListName");
string SPStatusColumnName = ConfigurationManager.AppSettings.Get("SPStatusColumnName");
string SPLoadIdColumnName = ConfigurationManager.AppSettings.Get("SPLoadIdColumnName");
string SSISVariable_name = ConfigurationManager.AppSettings.Get("SSISVariable_name");
string SSISEnvironment_name = ConfigurationManager.AppSettings.Get("SSISEnvironment_name");
string SSISFolder_name = ConfigurationManager.AppSettings.Get("SSISFolder_name");
int itemCounter = 0;
JobResult jobResult = new JobResult();
List<MyItem> myItemList = GetNewItem(SPSiteURL, SPListName, SPStatusColumnName);
myItemList.Sort((x, y) => y.id.CompareTo(x.id));
if (myItemList.Count() > 0)
{
//To avoid reload the same items
foreach (MyItem item in myItemList)
{
List<FieldValue> fieldValueList = new List<FieldValue>();
fieldValueList.Add(new FieldValue() { fieldName = SPStatusColumnName, fieldValue = "In progress" });
UpdateListItem(SPSiteURL, SPListName, item.id, fieldValueList);
}
//To start jon
MyItem myItem = myItemList[0];
int SPListId = myItem.id;
SqlConnection sqlconnection = DatabaseOperation.GetSqlConnection(SQLServerName, SQLDatabaseName);
jobResult = DatabaseOperation.StartSQLJob(sqlconnection, SQLProcedureName, SQLJobName, SSISVariable_name, SSISEnvironment_name, SSISFolder_name, SPListId);
}
//Update items fields Status, Loadid
itemCounter = 0;
foreach (MyItem myItem in myItemList)
{
if (itemCounter == 0)
{
List<FieldValue> fieldValueList = new List<FieldValue>();
fieldValueList.Add(new FieldValue() { fieldName = SPStatusColumnName, fieldValue = jobResult.result });
fieldValueList.Add(new FieldValue() { fieldName = SPLoadIdColumnName, fieldValue = jobResult.loadId });
UpdateListItem(SPSiteURL, SPListName, myItem.id, fieldValueList);
}
else
{
List<FieldValue> fieldValueList = new List<FieldValue>();
fieldValueList.Add(new FieldValue() { fieldName = SPStatusColumnName, fieldValue = "Missed" });
UpdateListItem(SPSiteURL, SPListName, myItem.id, fieldValueList);
}
itemCounter += 1;
}
}
//Get all items fro the list
private static List<MyItem> GetNewItem(string targetSiteUrl, string listName, string filedName)
{
List<MyItem> myItemList = new List<MyItem>();
using (ClientContext context = new ClientContext(targetSiteUrl))
{
context.Credentials = CredentialManager.GetSharePointOnlineCredential(targetSiteUrl);
Web myWeb = context.Web;
SP.List myList = myWeb.Lists.GetByTitle(listName);
SP.ListItemCollection listItemCollection = myList.GetItems(CamlQuery.CreateAllItemsQuery());
context.Load(listItemCollection,
eachItem => eachItem.Include(
item => item,
item => item["Title"],
item => item["ID"],
item => item[filedName] //Field: "Status"
)
);
// ExecuteQuery will pull all data from SharePoint
// which has been staged to Load()
context.ExecuteQuery();
foreach (SP.ListItem listItem in listItemCollection)
{
if ((string)listItem[filedName] == "New")
{
MyItem myItem = new MyItem();
myItem.id = (int)listItem["ID"];
myItem.title = (string)listItem["Title"];
myItem.status = (string)listItem[filedName];
myItemList.Add(myItem);
}
}
}
return myItemList;
}
//Update items fields
static void UpdateListItem(string targetSiteUrl, string listName, int itemId, List<FieldValue> fieldValuesList)
{
using (ClientContext context = new ClientContext(targetSiteUrl))
{
context.Credentials = CredentialManager.GetSharePointOnlineCredential(targetSiteUrl);
//List
SP.List announcementsList = context.Web.Lists.GetByTitle(listName);
//List item
SP.ListItem listItem = announcementsList.GetItemById(itemId);
foreach (FieldValue fieldValue in fieldValuesList)
{
//Change field value
listItem[fieldValue.fieldName] = fieldValue.fieldValue;
}
listItem.Update();
context.ExecuteQuery();
}
}
private class MyItem
{
public int id;
public string title;
public string status;
}
private class FieldValue
{
public string fieldName;
public string fieldValue;
}
}
}
Job step settings look like:
CMD step
When VS is used it uses normally the build-in webserver as fa as I can remember - deploying to a real IIS may end in trouble. Please have a look here
https://en.it1352.com/article/530a9f2f660b4088a9637b7d294194af.html
Anyway - in some cases TLS missing for authentication cause trouble
Have you checked the user permission on SQL?
(here a guide to create and configure an user to use SQL Server Agent Job)
The problem is that I doesn't use access token to enter the site. The application works fine in Visual Studio because previously received access token was kept in the cache. I've rewrite this part using example from here https://www.c-sharpcorner.com/article/sharepoint-csom-for-net-standard/
Like the title says, i need to get the members of a group from my Active directory.
Code:
using(var p_con = new PrincipalContext(ContextType.Machine))
{
var grps = GroupPrincipal.FindByIdentity(p_con, IdentityType.Sid, "S-1-5-21-205523278-2745993604-4001200492-1027");
var users = grps.GetMembers();
}
But my code throws the follwing error in the Membersproperty of the 'grps' var.
Members = 'grps.Members' threw an exception of type
'System.TypeLoadException'
If i try it the other way, searching for the groups of a member, i get the same error.
using (var p_con = new PrincipalContext(ContextType.Machine))
{
var up = new UserPrincipal(p_con);
using (var search = new PrincipalSearcher(up))
{
foreach (var user in search.FindAll())
{
var _grp = user.GetGroups();
}
}
}
The group/user it self is correctly loaded except the Users\Groups.
Am i missing something in the setup?
I am using ASP.NET Core 2 and the current Windows.Compatibility Pack (which includes the current verion of the DirectoryServices).
The authentication runs via Http.sys
I am trying to programmatically add a parent-child relationship between two work items. I am using the Microsoft Team Foundation and Visual Studio Services libraries to export and import TFS 2015 and VSTS backlog objects.
https://learn.microsoft.com/en-us/vsts/integrate/concepts/dotnet-client-libraries
https://www.visualstudio.com/en-us/docs/integrate/api/wit/samples#migrating-work-items
I have worked through obtaining a VssConnection to my servers and getting a WorkItemTrackingHttpClient to execute Wiql queries and create work items. I also have a query to identify the parent of a target work item.
What I cannot figure out is how to add the link between child work items and their parent. I do not know the correct JsonPatchDocument item path to add the parent, or the correct property or method on an existing WorkItem to update it with a parent link. Does anyone have documentation links or specific information on adding a parent relationship to a work item using these libraries?
Here are some code excerpts for context:
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.WebApi;
using Microsoft.VisualStudio.Services.WebApi.Patch;
using Microsoft.VisualStudio.Services.WebApi.Patch.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
// ...
var sourceConnection = new VssConnection(new Uri(_sourceTsUrl), new VssClientCredentials());
var targetConnection = new VssConnection(new Uri(_targetTsUrl), new VssClientCredentials());
var sourceClient = sourceConnection.GetClient<WorkItemTrackingHttpClient>();
var targetClient = targetConnection.GetClient<WorkItemTrackingHttpClient>();
// ...
var queryResults = sourceClient.QueryByWiqlAsync(query).Result;
var ids = queryResults.WorkItems.Select(x => x.Id).ToList();
var items = sourceClient.GetWorkItemsAsync(ids);
foreach (var item in items.Result)
{
// ...
var patchItem = new JsonPatchDocument();
foreach (var fieldName in item.Fields.Keys)
{ patchItem.Add(new JsonPatchOperation() { Path = $"/fields/{fieldName}", Value = item.Fields[fieldName], Operation = Operation.Add }); }
// TODO - add patch field(?) for parent relationship
var parentResults = sourceClient.QueryByWiqlAsync(parentQuery).Result;
// ...
var task = targetClient.CreateWorkItemAsync(patchItem, targetProject, itemType, validateOnly, bypassRules, suppressNotifications);
var newItem = task.Result;
// TODO - alternatively, add parent via the returned newly generated WorkItem
}
Addendum:
I've tried adding the following code, but the changes do not get committed to the remote object, it only exists in local memory, and I cannot find a method to push the changes/updates.
if (!string.IsNullOrWhiteSpace(mappedParentUrl))
{
if (newItem.Relations == null)
{ newItem.Relations = new List<WorkItemRelation>(); }
newItem.Relations.Add(new WorkItemRelation() { Rel = "Parent", Title = mappedParentTitle, Url = mappedParentUrl });
}
Refer to this code to create task work item with parent link (Update it to meet your requirement):
var url = new Uri("https://XXX.visualstudio.com");
var connection = new VssConnection(url, new VssClientCredentials());
var workitemClient = connection.GetClient<WorkItemTrackingHttpClient>();
string projectName = "[project name]";
int parentWITId = 771;//parent work item id
var patchDocument = new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchDocument();
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation() {
Operation=Operation.Add,
Path= "/fields/System.Title",
Value="parentandchildWIT"
});
patchDocument.Add(new Microsoft.VisualStudio.Services.WebApi.Patch.Json.JsonPatchOperation()
{
Operation = Operation.Add,
Path = "/relations/-",
Value = new
{
rel = "System.LinkTypes.Hierarchy-Reverse",
url = connection.Uri.AbsoluteUri+ projectName+ "/_apis/wit/workItems/"+parentWITId,
attributes = new
{
comment = "link parent WIT"
}
}
});
var createResult = workitemClient.CreateWorkItemAsync(patchDocument, projectName, "task").Result;
We have more than one collection under one TFS server and each collection have more than one project. I want to be able to check if entered username is part of any project level group on TFS server.
So far I am able to connect to TFS, and get all project names under the collection. I need help in finding the group name and then querying those groups to check if user is part of that group or not.
Here is the code I tried -
static void Main(string[] args)
{
NetworkCredential netCred = new NetworkCredential(#"username", #"pwd");
TfsConfigurationServer configServ = new TfsConfigurationServer(new Uri("https://my-tfs.schwab.com/tfs"), netCred);
var tfsAllCols = new List<KeyValuePair<Guid, string>>();
try
{
configServ.Authenticate();
Console.WriteLine("Autheticated in server with ad creds...");
ReadOnlyCollection<CatalogNode> colNodes = configServ.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.ProjectCollection },
false,
CatalogQueryOptions.None);
foreach (CatalogNode node in colNodes)
{
var colId = new Guid(node.Resource.Properties["InstanceId"]);
TfsTeamProjectCollection teamProjectCollection =
configServ.GetTeamProjectCollection(colId);
tfsAllCols.Add(new KeyValuePair<Guid, string>(colId, teamProjectCollection.Name));
}
//hardcoding the colname for testing
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("https://ruby-tfs.schwab.com/tfs/colname/"), netCred);
tpc.EnsureAuthenticated();
// Get the catalog of team project collections
ReadOnlyCollection<CatalogNode> projNodes = tpc.CatalogNode.QueryChildren(
new[] { CatalogResourceTypes.TeamProject },
false, CatalogQueryOptions.None);
}
catch (Exception ex)
{
throw ex;
}
Console.ReadLine();
}
Instead of hard coding, you could use TFSSecurity command-line tool to retrieve the groups and members in team project:
Use /g to list the groups in a team project, in a team project collection, or across Team Foundation Server:
tfssecurity /g [scope] [/collection:CollectionURL] [/server:ServerURL]
Use /imx to display information about the identities that compose the expanded membership of a specified group:
> tfssecurity /imx Identity [/collection:CollectionURL][/server:ServerURL]
If you insist hard coding, you could follow the blog below to query TFS for groups and memberships:
https://blogs.msdn.microsoft.com/vasu_sankaran/2010/10/11/querying-tfs-for-groups-and-memberships/
Update:
If you want to check the user's groups, you can call ims.ReadIdentity() method directly. Here is the simple code for your reference:
using System;
using System.Collections.Generic;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.VisualStudio.Services.Client;
using Microsoft.VisualStudio.Services.Common;
namespace ConsoleApplication3
{
class Program
{
static void Main(string[] args)
{
TfsConfigurationServer tcs = new TfsConfigurationServer(new Uri("http://xxxx:8080/tfs/"));
IIdentityManagementService ims = tcs.GetService<IIdentityManagementService>();
TeamFoundationIdentity tfi = ims.ReadIdentity(IdentitySearchFactor.AccountName, "domain\\username", MembershipQuery.Direct, ReadIdentityOptions.None);
Console.WriteLine("Listing Groups for:" + tfi.DisplayName);
Console.WriteLine("Total " + tfi.MemberOf.Length + " groups.");
IdentityDescriptor[] group = tfi.MemberOf;
foreach (IdentityDescriptor id in group)
{
TeamFoundationIdentity detail = ims.ReadIdentity(IdentitySearchFactor.Identifier, id.Identifier, MembershipQuery.None, ReadIdentityOptions.None);
Console.WriteLine(detail.DisplayName);
}
Console.ReadLine();
}
}
}
If this does not meet your requirement, you can also use this method to get the members of each project group.
How do I retrieve the users in a given AD group?
Do I start by instantiating a PrincipalContext with a domain, username and password?
First, find the group. Then enumerate its users using GetMembers().
using (var context = new PrincipalContext( ContextType.Domain ))
{
using (var group = GroupPrincipal.FindByIdentity( context, "groupname" ))
{
var users = group.GetMembers( true ); // recursively enumerate
...
}
}
Note that there is a bug, fixed in .NET 4.0, where it will fail to enumerate more than 1500 members of the group. If you have a large group you need to use an alternative method taking advantage of the older methods in System.DirectoryServices.
Check out this article Managing Directory Security Principals in the .NET Framework 3.5 for a great overview of what you can do with System.DirectoryServices.AccountManagement in .NET 3.5.
As for retrieving the members of a group, you do this:
// build the principal context - use the NetBIOS domain name
PrincipalContext ctx = new PrincipalContext(ContextType.Domain, "DOMAIN");
// get the group you're interested in
GroupPrincipal group = GroupPrincipal.FindByIdentity("cn=YourGroupname");
// iterate over its members
foreach(Principal p in group.Members)
{
// do whatever you need to do to its members here
}
Hope this helps!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.DirectoryServices.AccountManagement;
namespace ExportActiveDirectoryGroupsUsers
{
class Program
{
static void Main(string[] args)
{
if (args == null)
{
Console.WriteLine("args is null, useage: ExportActiveDirectoryGroupsUsers OutputPath"); // Check for null array
}
else
{
Console.Write("args length is ");
Console.WriteLine(args.Length); // Write array length
for (int i = 0; i < args.Length; i++) // Loop through array
{
string argument = args[i];
Console.Write("args index ");
Console.Write(i); // Write index
Console.Write(" is [");
Console.Write(argument); // Write string
Console.WriteLine("]");
}
try
{
using (var ServerContext = new PrincipalContext(ContextType.Domain, ServerAddress, Username, Password))
{
/// define a "query-by-example" principal - here, we search for a GroupPrincipal
GroupPrincipal qbeGroup = new GroupPrincipal(ServerContext, args[0]);
// create your principal searcher passing in the QBE principal
PrincipalSearcher srch = new PrincipalSearcher(qbeGroup);
// find all matches
foreach (var found in srch.FindAll())
{
GroupPrincipal foundGroup = found as GroupPrincipal;
if (foundGroup != null)
{
// iterate over members
foreach (Principal p in foundGroup.GetMembers())
{
Console.WriteLine("{0}|{1}", foundGroup.Name, p.DisplayName);
// do whatever you need to do to those members
}
}
}
}
//Console.WriteLine("end");
}
catch (Exception ex)
{
Console.WriteLine("Something wrong happened in the AD Query module: " + ex.ToString());
}
Console.ReadLine();
}
}
}
}