Access TFS Team Query from Client Object API - c#

Is there a way to run a shared team query, by name, through the TFS 2013 client object API
I'm working on a C# script that will do some work based off of the results of a shared team query. I don't want to have to maintain the query in the TFS UI as well as in my script; I'd prefer to just run the registered query that my team uses, but then just play with the results. When I write "registered query" I'm just referring to a query that I wrote in the TFS UI and saved as a shared query.
In other words: I'd like to use the TFS UI to create a query, save the file in my "shared queries" list, call it "foo", then access foo from the client object API in my script.
I see that there is a GetQueryDefinition(GUID) method off of WorkItemStore, but where would I get the GUID for a shared team query?

Sample code that should do what you need
///Handles nested query folders
private static Guid FindQuery(QueryFolder folder, string queryName)
{
foreach (var item in folder)
{
if (item.Name.Equals(queryName, StringComparison.InvariantCultureIgnoreCase))
{
return item.Id;
}
var itemFolder = item as QueryFolder;
if (itemFolder != null)
{
var result = FindQuery(itemFolder, queryName);
if (!result.Equals(Guid.Empty))
{
return result;
}
}
}
return Guid.Empty;
}
static void Main(string[] args)
{
var collectionUri = new Uri("http://TFS/tfs/DefaultCollection");
var server = new TfsTeamProjectCollection(collectionUri);
var workItemStore = server.GetService<WorkItemStore>();
var teamProject = workItemStore.Projects["TeamProjectName"];
var x = teamProject.QueryHierarchy;
var queryId = FindQuery(x, "QueryNameHere");
var queryDefinition = workItemStore.GetQueryDefinition(queryId);
var variables = new Dictionary<string, string>() {{"project", "TeamProjectName"}};
var result = workItemStore.Query(queryDefinition.QueryText,variables);
}

Related

AWS Cost Explorer API doesn't return Resource ID

I'm testing the AWS Cost Explorer API (I'm using the .NET SDK), in particular the GetCostAndUsageWithResources method to get the costs split by resource.
This is the code I'm testing with:
string nextPageToken = null;
do
{
var costRequest = new GetCostAndUsageWithResourcesRequest()
{
Granularity = Granularity.HOURLY,
GroupBy = {
new GroupDefinition() {
Key = "RESOURCE_ID",
Type = GroupDefinitionType.DIMENSION
}
},
Metrics = { "BlendedCost" },
NextPageToken = nextPageToken
};
var costResponse = await client.GetCostAndUsageWithResourcesAsync(costRequest);
nextPageToken = costResponse.NextPageToken;
foreach (var resultByTime in costResponse.ResultsByTime)
{
foreach (var instanceGroup in resultByTime.Groups)
{
var instanceId = instanceGroup.Keys.First();
if(g.Keys.First() != "NoResourceId" && !g.Keys.First().StartsWith("i-"))
{
Debugger.Break(); //NEVER gets hit
}
}
}
} while (!string.IsNullOrEmpty(nextPageToken));
However, as you can see from the comment in the code, I have an issue: the resource ID (which is the dimension I'm grouping by) seems to only be retrieved correctly for EC2 machine instances (IDs that start with i-). Otherwise, all other results have the ID key set to NoResourceId
What am I doing wrong here? Why does the Cost Explorer API only populate the Resource ID of EC2 instances, and all others are not identified? What if I want to know the costs of all other AWS services, how do I identify to which service the result belongs?
Am I doing something wrong here in the way I invoke the API? What am I missing?

Cross-workspace queries in azure log analytics .NET SDK

I'm using azure log analytics .NET SDK to execute some log analytics queries.
The nugget package I'm using for this SDK is Microsoft.Azure.OperationalInsights.
This allows me to issue some simple queries like in the following code sample :
Authentication & Simple query :
partial class QueryProvider
{
private OperationalInsightsDataClient _operationalInsightsDataClient;
private async void Authenticate()
{
// Retrieving the credentials and settings data from the app settings .
var domain = SettingsHelpers.PullSettingsByKey("domain");
var clientId = SettingsHelpers.PullSettingsByKey("clientId");
var workspaceId = SettingsHelpers.PullSettingsByKey("workspaceId");
var authEndpoint = SettingsHelpers.PullSettingsByKey("authEndpoint");
var clientSecret = SettingsHelpers.PullSettingsByKey("clientSecret");
var tokenAudience = SettingsHelpers.PullSettingsByKey("tokenAudience");
// Authenticating to the azure log analytics service .
var azureActiveDirectorySettings = new ActiveDirectoryServiceSettings
{
AuthenticationEndpoint = new Uri(authEndpoint),
TokenAudience = new Uri(tokenAudience),
ValidateAuthority = true
};
var credentials = await ApplicationTokenProvider.LoginSilentAsync
(
domain
, clientId
, clientSecret
, azureActiveDirectorySettings
);
_operationalInsightsDataClient = new OperationalInsightsDataClient(credentials);
_operationalInsightsDataClient.WorkspaceId = workspaceId;
}
public async Task<string> LogAnalyticsSamleQuery()
{
var query = #"Usage
| where TimeGenerated > ago(3h)
| where DataType == 'Perf'
| where QuantityUnit == 'MBytes'
| summarize avg(Quantity) by Computer
| sort by avg_Quantity desc nulls last";
var jsonResult = await _operationalInsightsDataClient.QueryAsync(query);
return JsonConvert.SerializeObject(jsonResult.Results);
}
}
Now I want to write a method that runs a cross-workspace query , I get the workspaces Ids dynamically and I want to build o query that references all those workspaces .
I did not find any sample in the doc to build such queries .
I found an attribute of OperationalInsightDataClient class called AdditionalWorkspaces but it's unclear how to use it to achieve the goal .
Any help would be very appreciated .
Use the ListWorkspaces method, store workspace Id , CustomerIdor Name in List.
var ws = new List<string>();
foreach (var w in workspaces)
{
ws.Add(w.Id);
}
AdditionalWorkspaces is used to store workspaces you want to query, but has no influence on the query result.
_operationalInsightsDataClient.AdditionalWorkspaces = ws;
To cross-workspace query, add the list of workspace-Id in the query method.
var jsonResult = await _operationalInsightsDataClient.QueryAsync(query,null, _operationalInsightsDataClient.AdditionalWorkspaces);

Sharepoint 2013 On-Premises C# CSOM Cross Site Collection List Access

I see this has sorta been asked a bunch times before, but all the examples I am running across will not work or are in JavaScript , I NEED HELP WITH C# please.
I have a farm with server site collections on it, I successfully created a provider hosted addin/App, When it trys to access lists on the Web that launched it everything is fine! I need to try to access lists on other webs in the same farm and on the same domain does anyone have an example of C# code that can do this
You can create a repository method like this:
public class SharepointRepository
{
public ListItemCollection ListTopN(string urlSite, string listName, bool ascending, string column, int rowLimit)
{
using (var context = new ClientContext(urlSite))
{
context.Credentials = CredentialCache.DefaultCredentials;
List list = context.Web.Lists.GetByTitle(listName);
string myQuery = string.Format("<View><Query><OrderBy><FieldRef Name='{0}' Ascending='{1}' /></OrderBy></Query><RowLimit>{2}</RowLimit></View>", column, ascending.ToString(), rowLimit);
CamlQuery query = new CamlQuery();
query.ViewXml = myQuery;
ListItemCollection collection = list.GetItems(query);
context.Load(list);
context.Load(collection);
context.ExecuteQuery();
return collection;
}
}
}
this approach uses the managed csom.
and if you are facing problems with ADFS, try adding after this line
context.Credentials = CredentialCache.DefaultCredentials;
this
context.ExecutingWebRequest += new EventHandler<WebRequestEventArgs>(MixedAuthRequestMethod);
and this function
void MixedAuthRequestMethod(object sender, WebRequestEventArgs e)
{
e.WebRequestExecutor.RequestHeaders.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
}
This is a basic referente:
https://msdn.microsoft.com/en-us/library/office/fp179912.aspx
You should also look at the Sharepoint App Model and the Rest OData API.
I figured this out, so I am posting it just in case someone else needs it, also please understand that I am total noob to SharePoint and this may not be the best way or even the SharePoint Accepted way of doing things.
First you need to give you app Tenant Permission (Full Control or Manage) ! - Very Important
Second I created this function that make a n SharePoint Context to a site other then the one the app is running on
public ClientContext CreateRemoteSharePointContext(string TargetWebURL, SharePointContext CurrentSharePointContext)
{
//In order for us to create a share point client context that points to
//site other then the site that this app is running we need to copy some url parameters from the current
//context. These parameters are found on the current share-point context
NameValueCollection QueryString = Request.QueryString;
//Since, The Query string is a read only collection, a use of reflection is required to update the
//values on the request object, we must use the current request object because it contains
//security and other headers/cookies that we need for the context to be created, Grab the url params that we need
//other then TargetWebUrl, that will be the url of the site we want to manipulate
Utility.AddToReadonlyQueryString(QueryString, "SPHostUrl", CurrentSharePointContext.SPHostUrl.ToString(), System.Web.HttpContext.Current.Request);
Utility.AddToReadonlyQueryString(QueryString, "SPAppWebUrl", TargetWebURL, System.Web.HttpContext.Current.Request);
Utility.AddToReadonlyQueryString(QueryString, "SPLanguage", CurrentSharePointContext.SPLanguage, System.Web.HttpContext.Current.Request);
Utility.AddToReadonlyQueryString(QueryString, "SPClientTag", CurrentSharePointContext.SPClientTag, System.Web.HttpContext.Current.Request);
Utility.AddToReadonlyQueryString(QueryString, "SPProductNumber", CurrentSharePointContext.SPProductNumber, System.Web.HttpContext.Current.Request);
//This is a special line, we need to get the AppOnly access token and pass it along to the target site, its is a little counter intuitive
//Because we are using TokenHelper.GetS2SAccessToeknWithWindowsIdentity - but we pass NULL as the User identity, this will
//check the app manifest and if the app has a CERT and AppOnly Permission it will return a valid app only token to use
Utility.AddToReadonlyQueryString(QueryString, "AppContextToken", TokenHelper.GetS2SAccessTokenWithWindowsIdentity(new Uri(TargetWebURL), null), System.Web.HttpContext.Current.Request);
//Return the newly created context
return SharePointContextProvider.Current.CreateSharePointContext(HttpContext.Request, TargetWebURL).CreateAppOnlyClientContextForSPAppWeb();
}
As you can see the I had to kinda hack up the Querystring and grab some values so here is the Utility class that does that :
public class Utility
{
public static void UpdateReadonlyQueryString(NameValueCollection collectionToUpdate, string paramName, string paramValue, HttpRequest Request)
{
collectionToUpdate = (NameValueCollection)Request.GetType().GetField("_queryString", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Request);
PropertyInfo readOnlyInfo = collectionToUpdate.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
readOnlyInfo.SetValue(collectionToUpdate, false, null);
collectionToUpdate[paramName] = paramValue;
readOnlyInfo.SetValue(collectionToUpdate, true, null);
}
public static void AddToReadonlyQueryString(NameValueCollection collectionToUpdate, string paramName, string paramValue, HttpRequest Request)
{
collectionToUpdate = Request.QueryString;
collectionToUpdate = (NameValueCollection)Request.GetType().GetField("_queryString", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(Request);
PropertyInfo readOnlyInfo = collectionToUpdate.GetType().GetProperty("IsReadOnly", BindingFlags.NonPublic | BindingFlags.Instance);
readOnlyInfo.SetValue(collectionToUpdate, false, null);
collectionToUpdate.Add( paramName, paramValue);
readOnlyInfo.SetValue(collectionToUpdate, true, null);
}
}
and Finally the SharePoint access code the looks like much of the same SharePoint code out there on the web, I had to remove some stuff from it that would identify the project or who its for, but it should be easy to pick out what you need from in side
try
{
//Get the name of the sharepoint list that needs to be updated from settings
var ListName = ConfigurationManager.AppSettings[Constants.SettingsConstants.SPLaunchQueList];
var TargetSiteToUpdate = "URL TO THE SITE YOUR TRYING TO UPDATE";
//Get the sharepoint context from session
var spContext = <SOME HOW CREATE YOUR CONTEXT>
//Lets create a client context from the current sharepoint context to the target site
//NOTE this requires the application to HAVE Tenant level permission, it must be trusted by
//the farm admin
using (var spClientContext = CreateRemoteSharePointContext(TargetSiteToUpdate, spContext))
{
//Get the current Web (Sharepoint Web) from the client context
var web = spClientContext.Web;
//Load all the webs properties including title , url all the lists and get the subwebs if any as well
spClientContext.Load(web, x => x.Title, x => x.Url, x => x.Lists, x => x.Webs.Include(w => w.Title, w => w.Url));
spClientContext.ExecuteQuery();
//Lets grab the list that needs to be updated
SP.List OrgList = web.Lists.GetByTitle(ListName);
//Construct a caml query Where the groupID of the SQL Server record is the same
//as the list GroupID
var caml = new Caml<DetailParts>().Where(o => o.GroupID == updateRecord.GroupID);
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = caml.ToString();
//Load the CAML query
ListItemCollection Rows = OrgList.GetItems(camlQuery);
spClientContext.Load(Rows);
spClientContext.ExecuteQuery();
//The CAML Query should only return one row because GroupID should be UNQIUE
//however due to how sharepoint returns list data we are forcing the first value
//here
ListItem RowToUpdate = Rows[0];
//Get a list of sharepoint columns that match the local detail parts
var ColumnsToUpdate = GetSharePointColumns(typeof(DetailParts));
RowToUpDate["SomeColumn"] = "YOUR NEW VALUE";
RowToUpdate.Update();
//Commit the changes
spClientContext.ExecuteQuery();
}
}
}
catch (Exception ex)
{
//Log any exception and then throw to the caller
logger.Error("Sharepoint exception", ex);
}
That last section of code should be in a function or method of some sort I just pull out the relevant parts. As I Stated this is the only way I found that works and if someone has a better way please share it as I am not a SharePoint expert.

Get the current iteration path from TFS

I'm trying to get the current iteration path for the teams TFS project. The way I'm trying to do this is by using the blog from http://blog.johnsworkshop.net/tfs11-api-reading-the-team-configuration-iterations-and-areas/ . I start by getting the team configurations from the following code:
TfsTeamProjectCollection tpc = TFSConncetion(#"http://tfs/url");
var configSvc = tpc.GetService<TeamSettingsConfigurationService>();
var configs = configSvc.GetTeamConfigurationsForUser(projectUri);
The problem with this is that my configs is always null, even though I'm a member of the team. I'm positive my projects URI is correct as well. After this I would get the team settings and use that to display the current iteration path.
TeamSettings ts = config.TeamSettings;
Console.WriteLine(ts.CurrentIterationPath);
Even if this didn't work I can still query the iteration dates from the team setting to get the one iteration that has a start date before today and finish date after today. The main problem is that I can't get my TeamSettingsConfigurationService to return anything but null when I try to get the team configurations with my projects URI.
There must be something wrong with your server connection or the project uri you're passing as the other code looks okay.
Maybe try something like this:
TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri("http://server:8080/tfs/collection"),
new System.Net.NetworkCredential(tfsUserName, tfsPassword));
tpc.EnsureAuthenticated();
Connect to Team Foundation Server from a Console Application
There is a good sample here which you can download (WPF client) and it will allow you to select a server connection, Team Project and Team:
TFS API Part 46 (VS11) – Team Settings
You can step through it and check the values you're passing into your code.
The sample gets the team configuration information is the same way you have in your code.
TeamSettingsConfigurationService teamConfig = tfs.GetService<TeamSettingsConfigurationService>();
var configs = teamConfig.GetTeamConfigurationsForUser(new[] { projectInfo.Uri });
Once you have the collection of TeamConfiguration items then you need TeamSettings.CurrentIterationPath
I actually got the answer myself without using TeamSettingsConfigurationService at all. Here's how I did it:
private static XmlNode currentIterationNode;
TfsTeamProjectCollection tpc = TFSConncetion(#"http://tfs/url");
ICommonStructureService4 css = tpc.GetService<ICommonStructureService4>();;
WorkItemStore workItemStore = new WorkItemStore(tpc);
foreach (Project teamProject in workItemStore.Projects)
{
if (teamProject.Name.Equals("TeamProjectNameGoesHere"))
{
NodeInfo[] structures = css.ListStructures(teamProject.Uri.ToString());
NodeInfo iterations = structures.FirstOrDefault(n => n.StructureType.Equals("ProjectLifecycle"));
if (iterations != null)
{
XmlElement iterationsTree = css.GetNodesXml(new[] { iterations.Uri }, true);
XmlNodeList nodeList = iterationsTree.ChildNodes;
currentIterationNode = FindCurrentIteration(nodeList);
String currentIterationPath = currentIterationNode.Attributes["Path"].Value;
}
}
}
Where currentIterationPath is the current iteration path from TFS. The key to doing this was to get the NodeInfo[] array of structures and the NodeInfo iterations from these two lines of code I got from chamindacNavantis https://social.msdn.microsoft.com/Forums/vstudio/en-US/4b785ae7-66c0-47ee-a6d2-c0ad8a3bd420/tfs-get-iteration-dates-metadata?forum=tfsgeneral:
NodeInfo[] structures = css.ListStructures(teamProject.Uri.ToString());
NodeInfo iterations = structures.FirstOrDefault(n => n.StructureType.Equals("ProjectLifecycle"));
After that I created an xml with nodes of every iteration inside the team project. These nodes also have the start date and end dates of each iteration. So I checked each node for a start date before DateTime.Now and finish date after DateTime.Now, which is all FindCurrentIteration(nodeList) does.
And that will give you the current iteration node.
The simplest way I've found to do it was by using ICommonStructureService4 and TeamSettingsConfigurationService methods:
static TfsTeamProjectCollection _tfs = TfsTeamProjectCollectionFactory
.GetTeamProjectCollection("<tfsUri>")
(...)
static string GetCurrentIterationPath()
{
var css = _tfs.GetService<ICommonStructureService4>();
var teamProjectName = "<teamProjectName>";
var project = css.GetProjectFromName(teamProjectName);
var teamName = "<teamName>";
var teamSettingsStore = _tfs.GetService<TeamSettingsConfigurationService>();
var settings = teamSettingsStore
.GetTeamConfigurationsForUser(new[] { project.Uri })
.Where(c => c.TeamName == teamName)
.FirstOrDefault();
if (settings == null)
{
var currentUser = System.Threading.Thread.CurrentPrincipal.Identity.Name;
throw new InvalidOperationException(
$"User '{currentUser}' doesn't have access to '{teamName}' team project.");
}
return settings.TeamSettings.CurrentIterationPath;
}

How to prevent fill in values when saving over CSOM to a choice field

I am writing some ETL code to move data between an external system and SharePoint Online.
I am using the nuget package Microsoft.SharePointOnline.CSOM to communicate with SP in C#.
I am using the following code to update my field values.
spListItem[fieldName] = "Test Value";
spListItem.Update();
spClientContext.ExecuteQuery();
I noticed with Choice fields, if I save a non existing value SharePoint does not complain and just adds the value even if Allow 'Fill-in' choices is set to NO.
Is there a validate function anywhere in SharePoint? I saw some methods like ValidateUpdateListItem, but they didn't seem to do what I needed.
You could consider to validate choice field value before saving its value as demonstrated below:
static class ListItemExtensions
{
public static bool TryValidateAndUpdateChoiceFieldValue(this ListItem item, string fieldName, string fieldValue)
{
var ctx = item.Context;
var field = item.ParentList.Fields.GetByInternalNameOrTitle(fieldName);
ctx.Load(field);
ctx.ExecuteQuery();
var choiceField = ctx.CastTo<FieldChoice>(field);
if (!choiceField.FillInChoice)
{
var allowedValues = choiceField.Choices;
if (!allowedValues.Contains(fieldValue))
{
return false;
}
}
item.Update();
return true;
}
}
In that case the ListItem will be updated once the validation is
succeeded.
Usage
using (var ctx = new ClientContext(webUri))
{
var list = ctx.Web.Lists.GetByTitle(listTitle);
var listItem = list.GetItemById(itemId);
if(listItem.TryValidateAndUpdateChoiceFieldValue(fieldName,fieldValue))
ctx.ExecuteQuery();
}

Categories