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?

Rob
  • 1,983
  • 2
  • 20
  • 29

5 Answers5

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.
Community
  • 1
  • 1
Luke Machowski
  • 3,983
  • 2
  • 31
  • 28
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="..." />
John Feminella
  • 303,634
  • 46
  • 339
  • 357
  • Great answer if this ends up being my only option. Was hoping to avoid it though. – Rob Mar 05 '10 at 18:37
  • 1
    Note you could include this code inline without needing to compile a separate DLL with MSBuild 4.0 Inline tasks (http://msdn.microsoft.com/en-us/library/dd722601.aspx) (EDIT: Ignore me, I see Luke mentions this in another answer!) – Duncan Smart Nov 25 '14 at 11:52
5

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

<Exec Command="..." Timeout="2000"></Exec>
Community
  • 1
  • 1
Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170
  • 2
    You should also set ContinueOnError="true" if you don't want the task to signal an error when it timesout. – Lewis Jubb May 30 '13 at 12:59
  • @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? – Keith Pinson May 21 '14 at 21:00
  • 4
    Looks like the process is killed, when the timeout occurs. This might not be what you want. – Nikolaj Jul 27 '17 at 11:28
5

Try AsyncExec in MSBuild Extension Pack.

tronda
  • 3,902
  • 4
  • 33
  • 55
Bahribayli
  • 346
  • 2
  • 11
  • Appears to have been moved here: [https://github.com/mikefourie/MSBuildExtensionPack](https://github.com/mikefourie/MSBuildExtensionPack) – gojimmypi Jun 08 '20 at 04:55
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.

cheerless bog
  • 914
  • 8
  • 11