从C#在同一环境中执行多个命令

joh*_*nes 5 c# windows process visual-studio

我正在开发一个小型的C#GUI工具,它应该获取一些C++代码并在完成一些向导后编译它.如果我在运行着名的程序后从命令提示符运行它,这一切都很好vcvarsall.bat.现在我希望用户不要先进入命令提示符,而是让程序调用vcvars后跟nmake我需要的其他工具.为了工作,vcvars显然应该保留设置的环境变量.

我怎样才能做到这一点?

我能找到的最好的解决方案是创建一个临时cmd/ bat脚本来调用其他工具,但我想知道是否有更好的方法.


更新:我同时试验了批处理文件和cmd.当使用批处理文件时,vcvars将终止完整的批处理执行,因此我的第二个命令(即nmake)将不会被执行.我目前的解决方法是这样的(缩短):

string command = "nmake";
string args = "";
string vcvars = "...vcvarsall.bat";
ProcessStartInfo info = new ProcessStartInfo();
info.WorkingDirectory = workingdir;
info.FileName = "cmd";
info.Arguments = "/c \"" + vcvars + " x86 && " + command + " " + args + "\"";
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
Process p = Process.Start(info);
Run Code Online (Sandbox Code Playgroud)

这样可行,但不会捕获cmd调用的输出.还在寻找更好的东西

Jon*_*ers 3

我有几个不同的建议

  1. 您可能想使用 MSBuild 而不是 NMake 进行研究

    它更复杂,但可以直接从.Net控制,并且它是VS项目文件的格式,适用于从VS 2010开始的所有项目以及C#/VB/等。早于该项目的项目

  2. 您可以使用小型帮助程序捕获环境并将其注入您的进程中

    这可能有点矫枉过正,但它会起作用。vsvarsall.bat 不会做任何比设置一些环境变量更神奇的事情,因此您所要做的就是记录运行它的结果,然后将其重播到您创建的进程的环境中。

帮助程序(envcapture.exe)很简单。它只是列出其环境中的所有变量并将它们打印到标准输出。这是整个程序代码;把它粘在Main()

XElement documentElement = new XElement("Environment");
foreach (DictionaryEntry envVariable in Environment.GetEnvironmentVariables())
{
    documentElement.Add(new XElement(
        "Variable",
        new XAttribute("Name", envVariable.Key),
        envVariable.Value
        ));
}

Console.WriteLine(documentElement);
Run Code Online (Sandbox Code Playgroud)

您也许可以只调用set而不是这个程序并解析该输出,但是如果任何环境变量包含换行符,这可能会中断。

在你的主程序中:

首先,必须捕获由 vcvarsall.bat 初始化的环境。为此,我们将使用类似于 的命令行cmd.exe /s /c " "...\vcvarsall.bat" x86 && "...\envcapture.exe" "。vcvarsall.bat 修改环境,然后 envcapture.exe 将其打印出来。然后,主程序捕获该输出并将其解析为字典。(注:vsVersion这里可能是 90 或 100 或 110)

private static Dictionary<string, string> CaptureBuildEnvironment(
    int vsVersion, 
    string architectureName
    )
{
    // assume the helper is in the same directory as this exe
    string myExeDir = Path.GetDirectoryName(
        Assembly.GetExecutingAssembly().Location
        );
    string envCaptureExe = Path.Combine(myExeDir, "envcapture.exe");
    string vsToolsVariableName = String.Format("VS{0}COMNTOOLS", vsVersion);
    string envSetupScript = Path.Combine(
        Environment.GetEnvironmentVariable(vsToolsVariableName),
        @"..\..\VC\vcvarsall.bat"
        );

    using (Process envCaptureProcess = new Process())
    {
        envCaptureProcess.StartInfo.FileName = "cmd.exe";
        // the /s and the extra quotes make sure that paths with
        // spaces in the names are handled properly
        envCaptureProcess.StartInfo.Arguments = String.Format(
            "/s /c \" \"{0}\" {1} && \"{2}\" \"",
            envSetupScript,
            architectureName,
            envCaptureExe
            );
        envCaptureProcess.StartInfo.RedirectStandardOutput = true;
        envCaptureProcess.StartInfo.RedirectStandardError = true;
        envCaptureProcess.StartInfo.UseShellExecute = false;
        envCaptureProcess.StartInfo.CreateNoWindow = true;

        envCaptureProcess.Start();

        // read and discard standard error, or else we won't get output from
        // envcapture.exe at all
        envCaptureProcess.ErrorDataReceived += (sender, e) => { };
        envCaptureProcess.BeginErrorReadLine();

        string outputString = envCaptureProcess.StandardOutput.ReadToEnd();

        // vsVersion < 110 prints out a line in vcvars*.bat. Ignore 
        // everything before the first '<'.
        int xmlStartIndex = outputString.IndexOf('<');
        if (xmlStartIndex == -1)
        {
            throw new Exception("No environment block was captured");
        }
        XElement documentElement = XElement.Parse(
            outputString.Substring(xmlStartIndex)
            );

        Dictionary<string, string> capturedVars 
            = new Dictionary<string, string>();

        foreach (XElement variable in documentElement.Elements("Variable"))
        {
            capturedVars.Add(
                (string)variable.Attribute("Name"),
                (string)variable
                );
        }
        return capturedVars;
    }
}
Run Code Online (Sandbox Code Playgroud)

稍后,当您想在构建环境中运行命令时,只需将新进程中的环境变量替换为之前捕获的环境变量即可。CaptureBuildEnvironment每次运行程序时,每个参数组合只需调用一次。但不要尝试在运行之间保存它,否则它会变得陈旧。

static void Main()
{
    string command = "nmake";
    string args = "";

    Dictionary<string, string> buildEnvironment = 
        CaptureBuildEnvironment(100, "x86");

    ProcessStartInfo info = new ProcessStartInfo();
    // the search path from the adjusted environment doesn't seem
    // to get used in Process.Start, but cmd will use it.
    info.FileName = "cmd.exe";
    info.Arguments = String.Format(
        "/s /c \" \"{0}\" {1} \"",
        command,
        args
        );
    info.CreateNoWindow = true;
    info.UseShellExecute = false;
    info.RedirectStandardOutput = true;
    info.RedirectStandardError = true;
    foreach (var i in buildEnvironment)
    {
        info.EnvironmentVariables[(string)i.Key] = (string)i.Value;
    }

    using (Process p = Process.Start(info))
    {
        // do something with your process. If you're capturing standard output,
        // you'll also need to capture standard error. Be careful to avoid the
        // deadlock bug mentioned in the docs for
        // ProcessStartInfo.RedirectStandardOutput. 
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您使用它,请注意,如果 vcvarsall.bat 丢失或失败,它可能会严重死机,并且区域设置不是 en-US 的系统可能会出现问题。