Paul Selles

Computers and cats

Tag Archives: .NET

Running Command Line Executables in C#

The Problem

A co-worker wanted to build a tool in C# which involves running a command line executable. Since an API is not available to them, they ask for a quick way to launch a command line executable with the option of parsing through the standard output.

 

The Solution

My solution for them that will allow them to:

  • Launch a command line executable with arguments
  • Have the option to retrieve Standard Output
  • Have the option to retrieve Standard Error
  • Have the option to throw exception on Standard Error
  • Forward any exceptions (ex: executable does not exist)

The solution is a single static function that uses the System.Diagnostic.Process class to perform the execution [1]. The executable name and the arguments are fed into the Process.StartInfo property as a System.Diagnostic.ProcessStartInfo object [2][3]. We want to set the Process.StartInfo.UseShellExecute to false since we want the option to retrieve both standard output and standard error [4]. Standard output and standard error can be redirected with Process.StartInfo.RedirectStandardOutput and Process.StartInfo.RedirectStandardOutput respectively, which we will turn on or off with flags[5][6]. Finally, in the event that we are reading the Process.StandardOutput or Process.StandardOutput, we need to invoke the method Process.WaitForExit [6][7][8].

 

The Implementation

public static class CommandLine
{
    /// <summary>
    /// Run console command in C#
    /// </summary>
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    public static string Execute(string executable,
        string arguments,
        bool standardOutput = false,
        bool standardError = false,
        bool throwOnError = false)
    {
        // This will be out return string
        string standardOutputString = string.Empty;
        string standardErrorString = string.Empty;

        // Use process
        Process process;

        try
        {
            // Setup our process with the executable and it's arguments
            process = new Process();
            process.StartInfo = new ProcessStartInfo(executable, arguments);

            // To get IO streams set use shell to false
            process.StartInfo.UseShellExecute = false;

            // If we want to return the output then redirect standard output
            if (standardOutput) process.StartInfo.RedirectStandardOutput = true;

            // If we std error or to throw on error then redirect error
            if (standardError || throwOnError) process.StartInfo.RedirectStandardError = true;

            // Run the process
            process.Start();

            // Get the standard error
            if (standardError || throwOnError) standardErrorString = process.StandardError.ReadToEnd();

            // If we want to throw on error and there is an error
            if (throwOnError &amp;&amp; !string.IsNullOrEmpty(standardErrorString))
                throw new Exception(
                    string.Format("Error in ConsoleCommand while executing {0} with arguments {1}.",
                    executable, arguments, Environment.NewLine, standardErrorString));

            // If we want to return the output then get it
            if (standardOutput) standardOutputString = process.StandardOutput.ReadToEnd();

            // If we want standard error then append it to our output string
            if (standardError) standardOutputString += standardErrorString;

            // Wait for the process to finish
            process.WaitForExit();
        }
        catch (Exception e)
        {
            // Encapsulate and throw
            throw new Exception(
                string.Format("Error in ConsoleCommand while executing {0} with arguments {1}.", executable, arguments), e);
        }

        // Return the output string
        return standardOutputString;
    }
}

 

The Example

As an example here are the unit tests all centered around git.exe:

[TestClass]
public class CommandLineTests
{
    private const string GIT_PATH = @"C:\Program Files (x86)\Git\bin\git.exe";
    private const string GIT_VALID = "git version 1.9.2.msysgit.0";
    private const string GIT_USAGE = "usage: git [--version] [--help] [-C ] [-c name=value]";
    private const string GIT_ERROR = "git: 'kitten' is not a git command. See 'git --help'.";

    private const string GIT_ERRONIOUS_ARG = "kitten";
    private const string GIT_VALID_ARG = "version";

    [TestMethod]
    public void GitNoAgurments()
    {
        /// Act: Call command with no argument
        string value = CommandLine.Execute(GIT_PATH, "");

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgument()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument
        string value = CommandLine.Execute(GIT_PATH, argument);

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitErroniousArgument()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument
        string value = CommandLine.Execute(GIT_PATH, argument);

        /// Assert: Output and error off expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputOnly()
    {
        /// Act: Call command with no argument standard output on
        string value = CommandLine.Execute(GIT_PATH, "", true);

        /// Assert: Output expected
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard output on
        string value = CommandLine.Execute(GIT_PATH, argument, true);

        /// Assert: Error expect output expected to be empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardOutputOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard output on
        string value = CommandLine.Execute(GIT_PATH, argument, true);

        /// Assert: Error expect output expected to be empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardErrorOnly()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", standardError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardErrorOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, standardError: true);

        /// Assert: Expect error expect not empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardErrorOnly()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, standardError: true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_ERROR);
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputAndStandardError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputAndStandardError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    public void GitErroniousArgumentWithStandardOutputAndStandardError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true);

        /// Assert: Expect error expect not empty
        StringAssert.StartsWith(value, GIT_ERROR);
    }

    [TestMethod]
    public void GitNoAgurmentsWithThrowOnError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", throwOnError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    public void GitValidArgumentWithThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, throwOnError: true);

        /// Assert: No error expect empty
        Assert.AreEqual(string.Empty, value);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void GitErroniousArgumentWithThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, throwOnError: true);      
    }

    [TestMethod]
    public void GitNoAgurmentsWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Act: Call command no with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, "", true, true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_USAGE);
    }

    [TestMethod]
    public void GitValidArgumentWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_VALID_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true, true);

        /// Assert: No error expect empty
        StringAssert.StartsWith(value, GIT_VALID);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void GitErroniousArgumentWithStandardOutputAndStandardErrorAndThrowOnError()
    {
        /// Arrange: Our argument
        string argument = GIT_ERRONIOUS_ARG;

        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH, argument, true, true, true);
    }

    [TestMethod]
    [ExpectedException(typeof(Exception))]
    public void ExecutableNameExeption()
    {
        /// Act: Call command with argument standard error on
        string value = CommandLine.Execute(GIT_PATH + "a", "");
    }
}

 

Enjoy

 

References

[1] Process Class. MSDN Libarary.

[2] Process.StartInfo Property. MSDN Libarary.

[3] ProcessStartInfo Class. MSDN Libarary.

[4] ProcessStartInfo.UseShellExecute Property. MSDN Libarary.

[5] ProcessStartInfo.RedirectStandardOutput Property. MSDN Libarary.

[6] ProcessStartInfo.RedirectStandardError Property. MSDN Libarary.

[7] Process.StandardOutput Property. MSDN Libarary.

[8] Process.StandardError Property. MSDN Libarary.

[9] Process.WaitForExit Method. MSDN Libarary.

 

Paul

Advertisements

TFS API: TFS User Email Address Lookup and Reverse Lookup

Occasionally I need to develop a tool that requires sending and receiving emails to and from developers. To do this I will need to lookup email addresses based on a TFS user’s display or account name and vice versa. Lucky for us this is painfully easy to do using IIdentityManagementService.ReadIdentity and the TeamFoundationIdentity class [1][2]:

private static readonly string TeamProjectUriString = "http://tfs.yourtfsurl.com";

// Team Project Collection getter
private static TfsTeamProjectCollection _tfsTeamProjectCollection;
public static TfsTeamProjectCollection TfsTeamProjectCollection
{
    get
    {
        return _tfsTeamProjectCollection ??
               (_tfsTeamProjectCollection = new TfsTeamProjectCollection(new Uri(TeamProjectUriString)));
    }
}

// Identity Management Service getter
private static IIdentityManagementService _identityManagementService;
public static IIdentityManagementService IdentityManagementService
{
    get
    {
        return _identityManagementService ??
               (_identityManagementService = TfsTeamProjectCollection.GetService<IIdentityManagementService>());
    }
}

// Get Email Address from TFS Account or Display Name
public static string GetEmailAddress(string userName)
{
    TeamFoundationIdentity teamFoundationIdentity =
        IdentityManagementService.ReadIdentity(
            IdentitySearchFactor.AccountName | IdentitySearchFactor.DisplayName,
            userName,
            MembershipQuery.None,
            ReadIdentityOptions.None);

    return teamFoundationIdentity.GetAttribute("Mail", null);
}

// Get TFS Display Name from and Email Address
public static string GetDisplayName(string emailAddress)
{
    TeamFoundationIdentity teamFoundationIdentity =
        IdentityManagementService.ReadIdentity(
            IdentitySearchFactor.MailAddress,
            emailAddress,
            MembershipQuery.None,
            ReadIdentityOptions.None);

    return teamFoundationIdentity.DisplayName;
}

References

[1] IIdentityManagementService.ReadIdentity Method. MSDN

[2] TeamFoundationIdentity Class. MSDN

VS2013: Opening Build In Browser

I have just started working with VS2013 and the ability to open a build in a browser is my favorite new feature so far.

vs2013_OpenInBrowser

Paul

Task Parallelism: Passing Values into a Tasks

One road block that I stubble upon when starting to work with Task Parallelism is passing values into my Tasks. Here is a quick guide to help get you head around this issue.

 

We can easily create a single task that takes in non-changing values within a function and run it getting expected results:

int i = 0;
int j = 1;
int k = 2;

Task task = Task.Factory.StartNew(() =>
    {
        Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, i, j, k));
    });

task.Wait();
Console.ReadLine();

Results:

i=’0′
j=’1′
k=’2′

 

Once the the values start changing then the Tasks will run against the latest values rather than the original value when the Task was created. The values are not fixed within the scope of the Task itself but rather in the scope of the parent function, so the variables do not preserve the value from when the Task was created:

int i = 0;
int j = 1;
int k = 2;

Task task = Task.Factory.StartNew(() =>
    {
        Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, i, j, k));
    });

i = -1;
j = -1;
k = -1;

task.Wait();
Console.ReadLine();

Results:

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

i=’2′
j=’2′
k=’2′

What happened here is that all the loops completed before any of the tasks had a chance to run (all the loops incrementing incremented to 2 before failing the for condition).

 

We can get around the problem a couple of ways, first we can reassign the values to an unchanging variable that shares the same scope as our Task. This will ensure that when ours Tasks are created the values that they use will be assigned uniquely for that instance. If we choose this route then our code will look like this:

List<Task> tasks = new List<Task>();

for (int i = 0; i < 2; i++)
{
	for (int j = 0; j < 2; j++)
	{
		for (int k = 0; k < 2; k++)
		{
			int I = i;
			int J = j;
			int K = k;

			tasks.Add(Task.Factory.StartNew(() =>
			{
				Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, I, K, J));
			}));
		}
	}
}

Results:

i=’0′
j=’0′
k=’0′

i=’0′
j=’1′
k=’0′

i=’0′
j=’1′
k=’1′

i=’1′
j=’0′
k=’0′

i=’1′
j=’1′
k=’0′

i=’1′
j=’0′
k=’1′

i=’0′
j=’0′
k=’1′

i=’1′
j=’1′
k=’1′

Though the Tasks may not have completed in order, all the tasks are present and all the values have been accounted for.

 

Finally, my favourite solution, passing all the variables as an Anonymous Type to the Task [1]. Within the Task the object will have to be casted to a dynamic type to access all the Anonymous Type’s members [2]. The reason why I like this solution is that it lends itself well to working with more complicated data types without having to reassign a large number of variables locally. For our above example our solution will look like this:

List<Task> tasks = new List<Task>();

for (int i = 0; i < 2; i++)
{
    for (int j = 0; j < 2; j++)
    {
        for (int k = 0; k < 2; k++)
        {
            tasks.Add(Task.Factory.StartNew((Object obj) =>
            {
                var data = (dynamic)obj;
                Console.WriteLine(string.Format("i='{1}'{0}j='{2}'{0}k='{3}'{0}{0}", Environment.NewLine, data.i, data.k, data.j));
            }, new { i = i, j = j, k = k }));
        }
    }
}

Task.WaitAll(tasks.ToArray());
Console.ReadLine();

And the results will be similar to those above.

 

Paul

 

References

 

[1] Anonymous Types (C# Programming Guide). MSDN Library.

[2] Using Type dynamic (C# Programming Guide). MSDN Library.

Team Foundation Server API: Programmatically Downloading Files From Source Control

For several of my custom check-in polices, I need to compare the developer’s local copy to a server copy of the same file. The challenge then becomes how to I find and download a file from source control programmatically using the Team Foundation Server API.

Finding the Server Items in Source Control

The first thing that you need to do is find file on the source control server that we want to download, all our searches will return an Item object or an ItemSet object (I will call this object our Server Item) [1][2]. This Server Item can be found multiple ways for example, the Item ID, the Server Item path, the Local Item Path, the Changeset Number, the Changeset Owner, or a Date Range.

Server Item Path Within Custom Check-in Policies

Within my custom check-in policy the path and ID of our Server Item can be easily found from the IPendingCheckin Interface, within the PendingChange array IPendingCheckinPendingChanges.CheckedPendingChanges Property (IPendingCheckin.PendingChanges.CheckedPendingChanges) [3][4][5]. Each CheckedPendingChanges will reference a PendingChange object that is associated with each checked out, included item. The PendingChange.ServerItem Property gives us the path to our Server Item and the PendingChange.ItemId Property gives us the Server Item ID [6][7].

Creating an Instance of VersionControlServer

All our Server Item Queries are done using VersionControlServer object and all methods mentioned below are fromthe VersionControlServer class [8]. First we need an instance of VersionControlServer:

string teamProjectCollectionUrl = "https://YourTfsUrl.com/tfs/YourTfsProjectCollection";

TfsTeamProjectCollection teamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamProjectCollectionUrl));
VersionControlServer versionControlServer = teamProjectCollection.GetService<VersionControlServer>();

GetItem Method

The VersionControlServer.GetItem Method have several options, but the first three are the ones that I most commonly use [9]. They are:

Examples of usage:

// Get the latest Item for local path "C:\projects\myfiles.cs"
Item item1 = versionControlServer.GetItem("C:\projects\myfiles.cs");

// Get ItemId = 12345 for changesetId = 54321
Item item2 = versionControlServer.GetItem(12345,54321);

// Get the latest Item for server path "$/ProjectName/myfile.cs"
Item item1 = versionControlServer.GetItem("$/ProjectName/myfile.cs", VersionSpec.Latest);

GetItems Method

The VersionControlServer.GetItems methods are very similar to the GetItem methods with the exception that it will return a collection of Server Items [11]. The most useful GetItems methods I have found, are those that provide a RecursionType option to get a collection Server Items within a directory structure [12].

QueryHistory Method

The added benefit of using VersionControlServer.QueryHistory Method is to use the QueryHistoryParameters [13][14]. QueryHistoryParameters allows for substantial customization of our search parameters, for example, if we wanted to download the the content of a project file a the time of a given Changeset that is not associated with the project file, we could. Since the project file in this example is not referenced to that particular Changeset, we will have to perform our query based on the DateTime of the Changeset.

// Example ChangsetNumber and ServerItemPath
int changesetNumber = 12345;
string serverItemPath = "$/ProjectName/myproject.csproj";

// Get the changeset for a given Changeset Number
Changeset changeset = versionControlServer.GetChangeset(changesetNumber);

// Create a queryHistoryParameters class based on our desired project file server path
QueryHistoryParameters queryHistoryParameters = mew QueryHistoryParameters(serverItemPath,RecursionType.None);

// We want to sort descending
queryHistoryParameters.SortAscending = false;

// Interested only one result
queryHistoryParameters.MaxResults = 1;

// Set the version end date to that of the changeset date
queryHistoryParameters.VersionEnd = new DateVersionSpec(changeset.CreationDate);

// Perform Query
ItemSet items = versionControlServer.QueryHistory(queryHistoryParameters);

Downloading the Sever Item Content

Once we have the specific Item or ItemSet of interest it’s just a matter of downloading the file contents locally. This process is quite simple and my methods make uses of the Stream and MemoryStream [15][16]. After some difficulties with the downloaded file’s encoding I use StreamReader to download the Server Item to a string [17]. Here is the method I use to retrieve the Server Item as a byte array:

public byte[] GetFileByteArray(Item item)
{  
	// create a container
	byte[] content;

	// Download file into stream
	using (Stream stream = item.DownloadFile())
	{
		// Use MemoryStream to copy steam into a byte array
		using (MemoryStream memoryStream = new MemoryStream())
		{
			stream.CopyTo(memoryStream);
			content = memoryStream.ToArray();
		}
	}

	// return byte array
	return content;       
}

Here is the method I use to retrieve the Server Item as a string:

public string GetFileString(Item item)
{
	// Setup string container
	string content  = string.Empty;

	// Download file into stream
	using (Stream stream = item.DownloadFile())
	{
		// Use MemoryStream to copy downloaded Stream
		using (MemoryStream memoryStream = new MemoryStream())
		{
			stream.CopyTo(memoryStream);
                   
			// Use StreamReader to read MemoryStream created from byte array
			using (StreamReader streamReader = new StreamReader(new MemoryStream(memoryStream.ToArray())))
			{
				content = streamReader.ReadToEnd();
			}
		}
	}

	// return string
	return content ;
}

Putting it all Together

With our new found knowledge we can create a simple console application that will retrieve the latest version for a given file.

using System;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string teamProjectCollectionUrl = "https://YourTfsUrl/tfs/YourTeamProjectCollection";
            string filePath = @"C:\project\myfile.cs";

            // Get the version control server
            TfsTeamProjectCollection teamProjectCollection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(teamProjectCollectionUrl));
            VersionControlServer versionControlServer = teamProjectCollection.GetService<VersionControlServer>();

            // Get the latest Item for filePath
            Item item = versionControlServer.GetItem(filePath, VersionSpec.Latest);

            // Download and display content to console
            string fileString = string.Empty;

            using (Stream stream = item.DownloadFile())
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    stream.CopyTo(memoryStream);
                   
                    // Use StreamReader to read MemoryStream created from byte array
                    using (StreamReader streamReader = new StreamReader(new MemoryStream(memoryStream.ToArray())))
                    {
                        fileString = streamReader.ReadToEnd();
                    }
                }
            }

            Console.WriteLine(fileString);
            Console.ReadLine();
        }
    }
}

Paul

Reference

[1] Item Class. MSDN Library

[2] ItemSet Class. MSDN Library

[3] IPendingCheckin Interface. MSDN Library

[4] PendingChange Class. MSDN Libary

[5] IPendingCheckinPendingChanges.CheckedPendingChanges Property. MSDN Library

[6] PendingChange.ServerItem Property. MSDN Library

[7] PendingChange.ItemId Property. MSDN Library

[8] VersionControlServer Class. MSDN Library

[9] VersionControlServer.GetItem Method. MSDN Library

[10] VersionSpec Class. MSDN Library

[11] VersionControlServer.GetItems Method. MSDN Library

[12] RecursionType Enumeration. MSDN Library

[13] VersionControlServer.QueryHistory Method. MSDN Library

[14] QueryHistoryParameters Class. MSDN Library

[15] Stream Class. MSDN Library

[16] MemoryStream Class. MSDN Library

[17] StreamReader Class. MSDN Library

%d bloggers like this: