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.
- Make a copy of DefaultTemplate.xaml and check it in. Let’s call it BatchFilePostBuild.xaml.
- 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])
- Expand “Run On Agent”. Drag “InvokeProcess” from the Toolbox and place it after “Try Compile, Test, and Associate …”
- In the Properties pane for InvokeProcess:
Property Value Arguments BatchFileArgumentsEnvironmentVariables New Dictionary(Of String, String) From {{"SourcesDirectory", SourcesDirectory}, {"BinariesDirectory", BinariesDirectory}}FileName BatchFile.Replace("$(SourceDir)", SourcesDirectory) - 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.
- Save and check in BatchFilePostBuild.xaml.
- In Team Explorer, create a new build definition, with its process template being BatchFilePostBuild.xaml.
- 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. - 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%1is the environment type (e.g. “Test”) and%2is 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 /yCopy 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
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.
Calling an Oracle function using NHibernate
- Tested with NHibernate 2.1.2 and Oracle 11g.
- Needs Oracle.DataAccess (not System.Data.OracleClient). Tested with Oracle.DataAccess 2.111.6.0 AMD64.
- 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);
Calling an Oracle stored procedure using NHibernate
- The stored procedure must have an out sys_refcursor parameter as the first argument.
- Tested with NHibernate 2.1.2 and Oracle 11g.
- 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();
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:
