From 6e00a710e2d2b26e7bf97b1c520ef3372cebfe1f Mon Sep 17 00:00:00 2001 From: sheastrickland Date: Mon, 18 May 2015 10:52:26 +1000 Subject: [PATCH 1/3] - Added support for /changesets=int[] and /workitems=int[] similar to: http://blogs.msmvps.com/vstsblog/2011/04/26/creating-fake-builds-in-tfs-build-2010-using-the-command-line/ - Added support for /autoAssocChangesetWorkItems=true to auto-associate changeset workitems to the build - Added support for /buildQueueDisabled=true to create the build defintion with a disabled queue, to stop users queueing builds. - Added app.config commented out binding re-directs for using the TFS2013 assemblies --- TfsCreateBuild/BuildCreator.cs | 3 +- TfsCreateBuild/Configuration.cs | 4 ++ TfsCreateBuild/ConfigurationProvider.cs | 4 ++ TfsCreateBuild/ITfsManualBuildCreator.cs | 2 +- TfsCreateBuild/TfsBuildResultPublisher.csproj | 7 ++- TfsCreateBuild/TfsManualBuildCreator.cs | 42 ++++++++++++++--- TfsCreateBuild/app.config | 45 ++++++++++++++++++- 7 files changed, 96 insertions(+), 11 deletions(-) diff --git a/TfsCreateBuild/BuildCreator.cs b/TfsCreateBuild/BuildCreator.cs index 5843d92..bc011fd 100644 --- a/TfsCreateBuild/BuildCreator.cs +++ b/TfsCreateBuild/BuildCreator.cs @@ -40,7 +40,8 @@ public int Execute(string[] args) configuration.BuildStatus, configuration.Collection, configuration.BuildLog, configuration.DropPath, configuration.BuildFlavor, configuration.LocalPath, configuration.BuildPlatform, configuration.BuildTarget, configuration.Project, configuration.BuildDefinition, configuration.CreateBuildDefinitionIfNotExists, configuration.BuildController, configuration.BuildNumber, configuration.ServerPath, - configuration.KeepForever); + configuration.KeepForever, configuration.AssociatedChangesetIds, configuration.AssociatedWorkitemIds, configuration.AutoIncludeChangesetWorkItems, + configuration.BuildQueueDisabled); } if (!string.IsNullOrEmpty(configuration.TestResults) && File.Exists(configuration.TestResults)) diff --git a/TfsCreateBuild/Configuration.cs b/TfsCreateBuild/Configuration.cs index c4f910f..8cc88ab 100644 --- a/TfsCreateBuild/Configuration.cs +++ b/TfsCreateBuild/Configuration.cs @@ -29,5 +29,9 @@ public class Configuration public string TeamCityPassword { get; set; } public bool TriggerBuild { get; set; } public bool KeepForever { get; set; } + public int[] AssociatedChangesetIds { get; set; } + public int[] AssociatedWorkitemIds { get; set; } + public bool AutoIncludeChangesetWorkItems { get; set; } + public bool BuildQueueDisabled { get; set; } } } \ No newline at end of file diff --git a/TfsCreateBuild/ConfigurationProvider.cs b/TfsCreateBuild/ConfigurationProvider.cs index 346f8d2..11590a8 100644 --- a/TfsCreateBuild/ConfigurationProvider.cs +++ b/TfsCreateBuild/ConfigurationProvider.cs @@ -45,6 +45,10 @@ public bool TryProvide(string[] args, out Configuration configuration) {"testConfigid=", @"The Test Configuration to publish the results of the test run to [tcm /configId]", v => localConfiguration.TestConfigId = int.Parse(v)}, {"testRunTitle=", @"The title of the test run [tcm /title]", v => localConfiguration.TestRunTitle = v}, {"testRunResultOwner=", @"The result owner of the test run [tcm /resultOwner]", v => localConfiguration.TestRunResultOwner = v}, + {"changeSets=", @"The changeset Id's to associate the build with", v => localConfiguration.AssociatedChangesetIds = Array.ConvertAll(v.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries), int.Parse)}, + {"workItems=", @"The workiem Id's to associate the build with", v => localConfiguration.AssociatedWorkitemIds = Array.ConvertAll(v.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries), int.Parse)}, + {"autoAssocChangesetWorkItems=", @"Whether to auto-associate changeset workitems to the build", v => localConfiguration.AutoIncludeChangesetWorkItems = (v !=null)}, + {"buildQueueDisabled=", @"Whether the build definition's queue is disabled", v => localConfiguration.BuildQueueDisabled = (v !=null)}, }; try diff --git a/TfsCreateBuild/ITfsManualBuildCreator.cs b/TfsCreateBuild/ITfsManualBuildCreator.cs index c9757fb..9485c3c 100644 --- a/TfsCreateBuild/ITfsManualBuildCreator.cs +++ b/TfsCreateBuild/ITfsManualBuildCreator.cs @@ -2,6 +2,6 @@ { public interface ITfsManualBuildCreator { - void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever); + void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever, int[] associatedChangesetIds, int[] associatedWorkitemIds, bool autoIncludeChangesetWorkItems, bool buildQueueDisabled); } } \ No newline at end of file diff --git a/TfsCreateBuild/TfsBuildResultPublisher.csproj b/TfsCreateBuild/TfsBuildResultPublisher.csproj index 9b85582..5a8b044 100644 --- a/TfsCreateBuild/TfsBuildResultPublisher.csproj +++ b/TfsCreateBuild/TfsBuildResultPublisher.csproj @@ -58,6 +58,9 @@ + + + ..\packages\NDesk.Options.0.2.1\lib\NDesk.Options.dll @@ -102,7 +105,9 @@ - + + Designer + Designer diff --git a/TfsCreateBuild/TfsManualBuildCreator.cs b/TfsCreateBuild/TfsManualBuildCreator.cs index 9fb6066..2c2961b 100644 --- a/TfsCreateBuild/TfsManualBuildCreator.cs +++ b/TfsCreateBuild/TfsManualBuildCreator.cs @@ -1,13 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; using Microsoft.TeamFoundation.Build.Client; using Microsoft.TeamFoundation.Client; +using Microsoft.TeamFoundation.VersionControl.Client; +using Microsoft.TeamFoundation.WorkItemTracking.Client; namespace TfsBuildResultPublisher { public class TfsManualBuildCreator : ITfsManualBuildCreator { - public void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever) + public void CreateManualBuild(string buildStatus, string collection, string buildLog, string dropPath, string buildFlavour, string localPath, string buildPlatform, string buildTarget, string project, string buildDefinition, bool createBuildDefinitionIfNotExists, string buildController, string buildNumber, string serverPath, bool keepForever, int[] associatedChangesetIds, int[] associatedWorkitemIds, bool autoIncludeChangesetWorkItems, bool buildQueueDisabled) { // Get the TeamFoundation Server var tfsCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(collection)); @@ -17,7 +20,7 @@ public void CreateManualBuild(string buildStatus, string collection, string buil // Create a fake definition var definition = CreateOrGetBuildDefinition(buildServer, project, buildDefinition, createBuildDefinitionIfNotExists, - buildController, dropPath); + buildController, dropPath, buildQueueDisabled); // Create the build detail object var buildDetail = definition.CreateManualBuild(buildNumber); @@ -32,6 +35,31 @@ public void CreateManualBuild(string buildStatus, string collection, string buil if (!string.IsNullOrEmpty(buildLog)) buildDetail.LogLocation = buildLog; + WorkItem[] workItems = {}; + if (associatedChangesetIds != null) + { + var service = tfsCollection.GetService(); + var changesets = associatedChangesetIds.Select(changesetId => service.GetChangeset(changesetId, false, false)).ToArray(); + buildProjectNode.Node.Children.AddAssociatedChangesets(changesets); + + if (autoIncludeChangesetWorkItems) + workItems = workItems.Concat(changesets.SelectMany(c => c.WorkItems)).ToArray(); + } + + if (associatedWorkitemIds != null) + { + var service = tfsCollection.GetService(); + workItems = workItems.Concat(associatedWorkitemIds.Select(id => service.GetWorkItem(id))).ToArray(); + } + + buildProjectNode.Node.Children.AddAssociatedWorkItems(workItems); + const string integrationBuild = "Integration Build"; + foreach (var wi in workItems.Where(wi => wi.Fields.Contains(integrationBuild))) + { + wi.Fields[integrationBuild].Value = definition.Name + "/" + buildDetail.BuildNumber; + wi.Save(); + } + buildProjectNode.Save(); // Complete the build by setting the status to succeeded @@ -42,8 +70,8 @@ public void CreateManualBuild(string buildStatus, string collection, string buil private IBuildDefinition CreateOrGetBuildDefinition( IBuildServer buildServer, string project, string buildDefinition, - bool createBuildDefinitionIfNotExists, - string buildController, string dropLocation) + bool createBuildDefinitionIfNotExists, + string buildController, string dropLocation, bool buildQueueDisabled) { try { @@ -56,10 +84,10 @@ private IBuildDefinition CreateOrGetBuildDefinition( } Console.WriteLine("'{0}' does not exist, trying to create build definition", buildDefinition); - return CreateBuildDefinition(buildServer, buildController, project, dropLocation, buildDefinition); + return CreateBuildDefinition(buildServer, buildController, project, dropLocation, buildDefinition, buildQueueDisabled); } - private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string buildController, string project, string dropLocation, string buildDefinition) + private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string buildController, string project, string dropLocation, string buildDefinition, bool buildQueueDisabled) { var controller = GetBuildController(buildServer, buildController); @@ -72,7 +100,7 @@ private IBuildDefinition CreateBuildDefinition(IBuildServer buildServer, string definition.BuildController = controller; definition.DefaultDropLocation = dropLocation; definition.Description = "Fake build definition used to create fake builds."; - definition.QueueStatus = DefinitionQueueStatus.Enabled; + definition.QueueStatus = buildQueueDisabled ? DefinitionQueueStatus.Disabled : DefinitionQueueStatus.Enabled; definition.Workspace.AddMapping("$/", "c:\\fake", WorkspaceMappingType.Map); definition.Process = processTemplate; definition.Save(); diff --git a/TfsCreateBuild/app.config b/TfsCreateBuild/app.config index e365603..c5015c5 100644 --- a/TfsCreateBuild/app.config +++ b/TfsCreateBuild/app.config @@ -1,3 +1,46 @@ - + + + + + From 7813d3dbea693e226ec18503d919ea157ac6ac23 Mon Sep 17 00:00:00 2001 From: sheastrickland Date: Mon, 18 May 2015 12:32:10 +1000 Subject: [PATCH 2/3] Added new switches to readme --- README.md | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index d4c55c6..266606d 100644 --- a/README.md +++ b/README.md @@ -13,29 +13,33 @@ TfsBuildResultPublisher allows you to create a Fake build, and publish test resu /buildnumber:"MyApplication_Daily_1.0" Options: - -c, --collection=VALUE The collection - -p, --project=VALUE The team project - -b, --builddefinition=VALUE The build definition - -n, --buildnumber=VALUE The build number to assign the build\ - -s, --status=VALUE Status of the build (Succeeded, Failed, Stopped, PartiallySucceeded, default: Succeeded) - -f, --flavor=VALUE Flavor of the build (to track test results against, default: Debug) - -l, --platform=VALUE Platform of the build (to track test results against, AnyCPU) - -t, --target=VALUE Target of the build (shown on build report, default: default) - --localpath=VALUE Local path of solution file. (default: Solutio n.sln) - --serverpath=VALUE Version Control path for solution file. (e.g. $/Solution.sln) - --droplocation=VALUE Location where builds are dropped (default: \\server\drops\) - --buildlog=VALUE Location of build log file. (e.g. \\server\folder\build.log) - --testResults=VALUE Test results file to publish (*.trx, requires MSTest installed) - --create Should the build definition be created if it does not exist - --trigger Instead of creating a manual build, we should trigger the build - --keepForever Does the build participates in the retention policy of the build definition or to keep the build forever - --buildController=VALUE The name of the build controller to use when creating the build definition (default, first controller) - --publishTestRun Creates a test run in Test Manager (requires tcm.exe installed) - --fixTestIds If the .trx file comes from VSTest.Console.exe, the testId's will not be recognised by Test Runs (for associated automation) - --testSuiteId=VALUE The Test Suite to publish the results of the test run to [tcm /suiteId] - --testConfigid=VALUE The Test Configuration to publish the results of the test run to [tcm /configId] - --testRunTitle=VALUE The title of the test run [tcm /title] - --testRunResultOwner=VALUE The result owner of the test run [tcm /resultOwner] + -c, --collection=VALUE The collection + -p, --project=VALUE The team project + -b, --builddefinition=VALUE The build definition + -n, --buildnumber=VALUE The build number to assign the build\ + -s, --status=VALUE Status of the build (Succeeded, Failed, Stopped, PartiallySucceeded, default: Succeeded) + -f, --flavor=VALUE Flavor of the build (to track test results against, default: Debug) + -l, --platform=VALUE Platform of the build (to track test results against, AnyCPU) + -t, --target=VALUE Target of the build (shown on build report, default: default) + --localpath=VALUE Local path of solution file. (default: Solutio n.sln) + --serverpath=VALUE Version Control path for solution file. (e.g. $/Solution.sln) + --droplocation=VALUE Location where builds are dropped (default: \\server\drops\) + --buildlog=VALUE Location of build log file. (e.g. \\server\folder\build.log) + --testResults=VALUE Test results file to publish (*.trx, requires MSTest installed) + --create Should the build definition be created if it does not exist + --trigger Instead of creating a manual build, we should trigger the build + --keepForever Does the build participates in the retention policy of the build definition or to keep the build forever + --buildController=VALUE The name of the build controller to use when creating the build definition (default, first controller) + --publishTestRun Creates a test run in Test Manager (requires tcm.exe installed) + --fixTestIds If the .trx file comes from VSTest.Console.exe, the testId's will not be recognised by Test Runs (for associated automation) + --testSuiteId=VALUE The Test Suite to publish the results of the test run to [tcm /suiteId] + --testConfigid=VALUE The Test Configuration to publish the results of the test run to [tcm /configId] + --testRunTitle=VALUE The title of the test run [tcm /title] + --testRunResultOwner=VALUE The result owner of the test run [tcm /resultOwner] + --changeSets=VALUE The changeset Id's to associate the build with (value is comma delimited: 1,2,3) + --workItems=VALUE The workiem Id's to associate the build with (value is comma delimited: 1,2,3) + --autoAssocChangesetWorkItems=VALUE Whether to auto-associate changeset workitems to the build (true if a value is present) + --buildQueueDisabled=VALUE Whether the build definition's queue is created disabled (true if a value is present) ## References **Original Blog post:** [http://blogs.msdn.com/b/jpricket/archive/2010/02/23/creating-fake-builds-in-tfs-build-2010.aspx](http://blogs.msdn.com/b/jpricket/archive/2010/02/23/creating-fake-builds-in-tfs-build-2010.aspx) From 3f20a73b87aa7e037fdabfd951f62d8fd3c15836 Mon Sep 17 00:00:00 2001 From: sheastrickland Date: Thu, 28 Jan 2016 15:23:38 +1000 Subject: [PATCH 3/3] Added 2015 MSTest probe paths --- TfsCreateBuild/BuildTestResultPublisher.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TfsCreateBuild/BuildTestResultPublisher.cs b/TfsCreateBuild/BuildTestResultPublisher.cs index b8136ac..2a67dc4 100644 --- a/TfsCreateBuild/BuildTestResultPublisher.cs +++ b/TfsCreateBuild/BuildTestResultPublisher.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; namespace TfsBuildResultPublisher { @@ -10,10 +11,12 @@ public bool PublishTestResultsToBuild(string collection, string testResultsFile, { var paths = new[] { + @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe", + @"C:\Program Files\Microsoft Visual Studio 14.0\Common7\IDE\MSTest.exe", @"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\MSTest.exe", @"C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE\MSTest.exe" }; - var msTest = File.Exists(paths[0]) ? paths[0] : paths[1]; + var msTest = paths.FirstOrDefault(File.Exists); const string argsFormat = "/publish:\"{0}\" /publishresultsfile:\"{1}\" /teamproject:\"{2}\" /publishbuild:\"{3}\" /platform:\"{4}\" /flavor:\"{5}\""; var args = string.Format(argsFormat, collection, testResultsFile, project, buildNumber, buildPlatform, buildFlavour); string stdOut;