18

Would anyone happen to know a trick that will keep this MSBuild task from blocking? I really just want the explorer to open and the build script to keep on going. Currently it blocks at the Exec task until the explorer window is closed.

<Target Name="OpenExplorer">
    <Exec Command='explorer.exe "$(DestinationDir)"' IgnoreExitCode="true" />
</Target>

Thanks!

Edit: I was hoping to avoid creating a custom task for this. Perhaps some command line magic exists that could be placed inline for the Command?

5 Answers 5

19

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:

  1. 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).
  2. Next, we start our program that we want to gather coverage on.
  3. Then we shut down the coverage tool once we are done.
Sign up to request clarification or add additional context in comments.

2 Comments

I had issues with GetFileNameWithoutExtension() so I removed the logging lines and it worked perfectly. It even works fine as <ExecAsync Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ".... Thanks !
Note that for .NET Core projects you need to adjust the task factory and the assembly file like this: <UsingTask TaskName="ExecAsync" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
8

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="..." />

2 Comments

Great answer if this ends up being my only option. Was hoping to avoid it though.
Note you could include this code inline without needing to compile a separate DLL with MSBuild 4.0 Inline tasks (msdn.microsoft.com/en-us/library/dd722601.aspx) (EDIT: Ignore me, I see Luke mentions this in another answer!)
5

Answered at Starting a program with MSBuild/Web Deployment Project and not waiting for it

<Exec Command="..." Timeout="2000"></Exec>

3 Comments

You should also set ContinueOnError="true" if you don't want the task to signal an error when it timesout.
@LewisJubb Could you explain further? For me, it only gives a warning anyway, and that seems to have no effect. What version of MS build are you using?
Looks like the process is killed, when the timeout occurs. This might not be what you want.
5

Try AsyncExec in MSBuild Extension Pack.

1 Comment

3

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.

1 Comment

This does not seem to work, in my experience. I suspect the task is preventing it somehow.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.