Invoking a post-build batch file in TFS 2010

The scenario: you are using TFS 2010 as your build server, and you would like to deploy files to a remote server upon successful build by invoking a batch file.

Do note that the following steps work great in conjunction with Visual Studio T4 Templates, whereby multiple .config files (e.g. Web.Dev.config, Web.Test.config) can be generated from a master template to reflect different connection strings, etc. Oleg Sych has a nice write-up on this. Hint: hand-edit your .csproj file and use the DependentUpon element to keep your project view clean in Visual Studio—multiple .tt files can be nested under the main .tt file.

  1. Make a copy of DefaultTemplate.xaml and check it in. Let’s call it BatchFilePostBuild.xaml.
  2. Open BatchFilePostBuild.xaml in the designer and add 2 new Arguments:
    • BatchFile (Direction = In, Argument type = String, Default value = [blank])
    • BatchFileArguments (Direction = In, Argument type = String, Default value = [blank])
  3. Expand “Run On Agent”. Drag “InvokeProcess” from the Toolbox and place it after “Try Compile, Test, and Associate …”
  4. In the Properties pane for InvokeProcess:
    Property Value
    Arguments BatchFileArguments
    EnvironmentVariables New Dictionary(Of String, String) From {{"SourcesDirectory", SourcesDirectory}, {"BinariesDirectory", BinariesDirectory}}
    FileName BatchFile.Replace("$(SourceDir)", SourcesDirectory)
  5. Double-click InvokeProcess:
    • Drag “WriteBuildMessage” from the Toolbox and place it under stdOutput. Set Importance = High, Message = stdOutput.
    • Drag “WriteBuildWarning” from the Toolbox and place it under stdError. Set Message = stdError.
  6. Save and check in BatchFilePostBuild.xaml.
  7. In Team Explorer, create a new build definition, with its process template being BatchFilePostBuild.xaml.
  8. In the Process tab, there will be 2 properties under Misc:
    Property Value
    BatchFile Here you can specify the path and file name to your batch file relative to $(SourceDir). Refer to the folder structures in the Workspace tab.
    BatchFileArguments A list of arguments separated by spaces. In your batch file, they will be %1, %2, %3, and so on.
  9. Create and check in your batch file. In this batch file you will be able to refer to %SourcesDirectory% and %BinariesDirectory%, in addition to %1, %2, %3, etc. above. Assuming %1 is the environment type (e.g. “Test”) and %2 is the server name, here are some useful commands:
    Clean target folder del /s /q "\\%2\path\to\remote\folder\*"
    Copy directory structure xcopy "%BinariesDirectory%\path\to\folder\*" "\\%2\path\to\remote\folder" /s /y
    Copy Web.Test.config to the Test server copy /y "%BinariesDirectory%\_PublishedWebsites\MyProject\Web.%1.config" "\\%2\path\to\remote\folder\Web.config"
    Start remote Windows service sc \\%2 start "My Windows Service"
    Stop remote Windows service sc \\%2 stop "My Windows Service"
    Dirty wait for around 30 seconds ping 127.0.0.1 -n 30 > NUL

4 June 2012 | .NET | 6 Comments

Exception-handling wrappers for Task.ContinueWith()

The .NET 4 Task Parallel Library is great because you can specify continuations for new threads:

private Task<WebResponse> GetWebResponseAsync(string url)
{
    var webRequest = WebRequest.Create(url);
    return Task.Factory.FromAsync<WebResponse>(
        webRequest.BeginGetResponse,
        webRequest.EndGetResponse,
        null);
}

public void Run(string url)
{
    Task.Factory.StartNew(StartBusyIndicator)
        .ContinueWith(task => GetWebResponseAsync(url)).Unwrap()
        .ContinueWith(task => Console.WriteLine(task.Result.Headers))
        .ContinueWith(task => StopBusyIndicator());
}

Otherwise we’d have to handle the result of the async call either in callbacks or in event handlers—messy.

There are two caveats, though, when using Task.ContinueWith(). The first is that we have to sometimes use .Unwrap(). The second is that we have to handle exceptions in the next .ContinueWith(). Otherwise, our exceptions will just get swallowed.

So now:

public void Run(string url)
{
    Task.Factory.StartNew(StartBusyIndicator)
        .ContinueWith(task => GetWebResponseAsync(url)).Unwrap()
        .ContinueWith(task =>
        {
            if (task.IsFaulted) // handle errors
            else Console.WriteLine(task.Result.Headers);
        })
        .ContinueWith(task => StopBusyIndicator());
}

There goes our pretty code. Also, imagine the duplication if we do this for every .ContinueWith().

And so, inspired by Stephen Toub’s Processing Sequences of Asynchronous Operations with Tasks, I’ve written drop-in replacements for .ContinueWith() and .Unwrap() that will bubble up exceptions to a .Finally() extension method. Now our code can be clean again:

public void Run(string url)
{
    Task.Factory.StartNew(StartBusyIndicator)
        .Then(task => GetWebResponseAsync(url))
        .Then(task => Console.WriteLine(task.Result.Headers))
        .Finally(ExceptionHandler, StopBusyIndicator);
}

Do help yourself to the full source and example usage at github:gist.

28 May 2012 | .NET, C# | 2 Comments

Calling an Oracle function using NHibernate

  1. Tested with NHibernate 2.1.2 and Oracle 11g.
  2. Needs Oracle.DataAccess (not System.Data.OracleClient). Tested with Oracle.DataAccess 2.111.6.0 AMD64.
  3. If your Oracle function returns a scalar value of datatype number, the .NET object will be a Decimal.

NHibernate config:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.driver_class">
      NHibernate.Driver.OracleDataClientDriver
    </property>
  </session-factory>
</hibernate-configuration>

C# code:

var result = nhibernateSession
    .CreateSQLQuery("select GetSomeValue(:p_parameter1) from dual")
    .SetParameter("p_parameter1", parameter1)
    .UniqueResult();

// GetSomeValue returns a scalar of datatype number; result will be object of type Decimal

var someValue = Convert.ToInt32(result);

17 January 2012 | Uncategorized | No Comments

Calling an Oracle stored procedure using NHibernate

  1. The stored procedure must have an out sys_refcursor parameter as the first argument.
  2. Tested with NHibernate 2.1.2 and Oracle 11g.
  3. Needs Oracle.DataAccess (not System.Data.OracleClient). Tested with Oracle.DataAccess 2.111.6.0 AMD64.

NHibernate config:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.driver_class">
      NHibernate.Driver.OracleDataClientDriver
    </property>
  </session-factory>
</hibernate-configuration>

NHibernate mapping:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="SampleProject" namespace="SampleProject">
  <sql-query name="GetEmployeesByDepartmentId">
    <return class="Employee" />
    { call GetEmployeesByDepartmentId(:p_departmentid) }
  </sql-query>
</hibernate-mapping>

C# code:

var employees = nhibernateSession
    .GetNamedQuery("GetEmployeesByDepartmentId")
    .SetParameter("p_departmentid", departmentId)
    .List<Employee>();

To execute a stored procedure that has no out parameters:

nhibernateSession
    .CreateSQLQuery("call DoSomething(:p_parameter1)")
    .SetParameter("p_parameter1", parameter1)
    .ExecuteUpdate();

17 January 2012 | .NET, C# | No Comments

LINQPad – a worthy successor to Snippet Compiler

In 2009 I blogged about how Snippet Compiler allows us to test out C# code without having to create a project in Visual Studio just for that. Now there’s something better: LINQPad. Don’t be misled by its name; it’s not just for LINQ, it can run any C#, VB, or even SQL code. I particularly like its results view. A screenshot is worth a thousand words:

5 August 2011 | .NET, C#, Applications | No Comments