MSBuild exec task without blocking
Here is an easy way to execute processes asynchronously by only using msbuild and inline tasks. This is only for MSBuild V4.0 and up (God bless the MSBuild guys for adding this feature!). You don't need the any external extension packs.
Effectively, we are taking the code suggested above and putting it in an inline task. Feel free to fiddle with the code to suite your needs.
The point of this solution is that it lets you achieve the result without the headache of creating a separate dll for the custom task. The implementation in the extension pack is definitely more solid but this works as a quick 'n dirty way of solving this issue. You can also customise exactly how you want it to run.
<!--Launch a Process in Parallel-->
<UsingTask TaskName="ExecAsync" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<!--The file path is the full path to the executable file to run-->
<FilePath ParameterType="System.String" Required="true" />
<!--The arguments should contain all the command line arguments that need to be sent to the application-->
<Arguments ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<![CDATA[
string name = System.IO.Path.GetFileNameWithoutExtension(FilePath);
Log.LogMessage("Starting {0}...", name);
System.Diagnostics.ProcessStartInfo processStartInfo = new System.Diagnostics.ProcessStartInfo(FilePath, Arguments);
processStartInfo.UseShellExecute = true;
System.Diagnostics.Process.Start(processStartInfo);
Log.LogMessage("Finished running process {0}.", name);
]]>
</Code>
</Task>
</UsingTask>
You can then call the ExecAsync task from within your normal script in the following fashion. Note: My script below is used to gather code coverage for an application.
<!--Start listening for coverage data:-->
<Message Text="Starting to listen for coverage..."/>
<ExecAsync FilePath='$(VSPerfCmdExePath)' Arguments='/start:coverage /output:"$(CoverageFilePath)"' ContinueOnError='true'/>
<Message Text="Listening for coverage..."/>
<!--Start App with Coverage:-->
<Message Text="Starting App..."/>
<Exec Command='"$(AppCoverageLatestExePath)"' ContinueOnError='true' WorkingDirectory='$(AppCoverageLatestFolder)'/>
<Message Text="App shut down."/>
<!--Stop gathering coverage results:-->
<Message Text="Stopping listening for coverage..."/>
<Exec Command='"$(VSPerfCmdExePath)" /shutdown'/>
<Message Text="Coverage shut down."/>
Here is a description of what is happening there:
- First I kick the performance tool off so that it listens for coverage. I do this using our AsyncExec task because normally the tool blocks when running in MSBuild (see here).
- Next, we start our program that we want to gather coverage on.
- Then we shut down the coverage tool once we are done.
You can't do it with the native Exec
. But you can write your own that fires asynchronously, as in this example:
public class AsyncExec : Exec {
protected override int ExecuteTool(string pathToTool,
string responseFileCommands,
string commandLineCommands) {
Process process = new Process();
process.StartInfo = GetProcessStartInfo(pathToTool, commandLineCommands);
process.Start();
return 0;
}
protected virtual ProcessStartInfo GetProcessStartInfo(string executable,
string arguments) {
if (arguments.Length > 0x7d00) {
this.Log.LogWarningWithCodeFromResources("ToolTask.CommandTooLong", new object[] { base.GetType().Name });
}
ProcessStartInfo startInfo = new ProcessStartInfo(executable, arguments);
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = true;
string workingDirectory = this.GetWorkingDirectory();
if (workingDirectory != null) {
startInfo.WorkingDirectory = workingDirectory;
}
StringDictionary environmentOverride = this.EnvironmentOverride;
if (environmentOverride != null) {
foreach (DictionaryEntry entry in environmentOverride) {
startInfo.EnvironmentVariables.Remove(entry.Key.ToString());
startInfo.EnvironmentVariables.Add(entry.Key.ToString(), entry.Value.ToString());
}
}
return startInfo;
}
}
which you can then run with:
<AsyncExec Command="..." />
Answered at Starting a program with MSBuild/Web Deployment Project and not waiting for it
<Exec Command="..." Timeout="2000"></Exec>
Try AsyncExec in MSBuild Extension Pack.
The Command in Exec is placed in a batch file and executed. So you can use the "start" keyword in the Command just the same as in a console window. That will do the trick.