不阻塞的MSBuild exec任务

18

请问有没有什么技巧可以让这个MSBuild任务不阻塞?我只想打开资源管理器并让构建脚本继续执行。当前它会在Exec任务处阻塞,直到资源管理器窗口关闭。

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

谢谢!

编辑: 我本来希望避免创建自定义任务。也许有一些命令行技巧可以内联放置到Command里面?

5个回答

19

以下是一种仅使用 MSBuild 和内联任务来异步执行进程的简单方法。 这仅适用于MSBuild V4.0及以上版本(感谢 MSBuild 团队添加了此功能!)。 您不需要任何外部扩展包。

实际上,我们将上面建议的代码放在内联任务中。 随意调整代码以满足您的需求。

这种解决方案的重点是,让您在不必为自定义任务创建单独的 dll 而达到结果。 扩展包中的实现肯定更牢固,但这是解决此问题的快捷方式。 您还可以自定义其运行方式。

  <!--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>
您可以按照以下方式从您的常规脚本中调用ExecAsync任务。注意:下面我的脚本用于收集应用程序的代码覆盖率。

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

以下是那里正在发生的事情的描述:

  1. 首先,我启动性能工具以便它监听覆盖率。我使用我们的AsyncExec任务来完成这个操作,因为通常当在MSBuild中运行时该工具会阻塞(请参见这里)。
  2. 接下来,我们启动我们想要收集覆盖率数据的程序。
  3. 然后,一旦完成,我们关闭覆盖工具。

我在使用 GetFileNameWithoutExtension() 函数时遇到了问题,所以我删除了日志记录行,然后它就完美地工作了。甚至在 <ExecAsync Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' " 下也能正常工作。谢谢! - CAD bloke
这是一个极好的答案。非常感谢。 - Ocean Airdrop
4
请注意,对于.NET Core项目,您需要像这样调整任务工厂和程序集文件:<UsingTask TaskName="ExecAsync" TaskFactory="RoslynCodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll"> - 0xced

8

使用原生的Exec是无法实现的。但是你可以编写自己的异步执行程序,例如这个例子

  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;
    }
  }

然后您可以使用以下命令运行:

<AsyncExec Command="..." />

如果这最终是我唯一的选择,那么这是一个很好的答案。虽然我希望能够避免这种情况发生。 - Rob
1
请注意,您可以使用MSBuild 4.0内联任务(http://msdn.microsoft.com/en-us/library/dd722601.aspx)将此代码嵌入到代码中,而无需编译单独的DLL。(编辑:忽略我,在另一个答案中Luke提到了这一点!) - Duncan Smart

5

2
如果你不希望任务在超时时信号错误,那么你还应该设置ContinueOnError="true"。 - Lewis Jubb
@LewisJubb,你能再解释一下吗?对我来说,它只是给了一个警告,似乎没有什么影响。你使用的MS Build版本是什么? - Keith Pinson
4
看起来进程在超时发生时被杀死了,这可能不是你想要的。 - Nikolaj

5

似乎已经被移动到这里:https://github.com/mikefourie/MSBuildExtensionPack - gojimmypi

3

在Exec中的命令被放置在批处理文件中并执行。因此,您可以像在控制台窗口中一样在命令中使用"start"关键字。这将解决问题。


2
根据我的经验,这似乎不起作用。我怀疑任务在某种程度上阻止了它。 - Janik Zikovsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接