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

时间:2012-12-22 21:56:20

标签: c# windows visual-studio process

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

我该怎么做?

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


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

bat

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

3 个答案:

答案 0 :(得分:2)

我有几个不同的建议

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

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

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

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

  3. 帮助程序(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);
    

    您可能只需调用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;
        }
    }
    

    稍后,当您想在构建环境中运行命令时,您只需使用先前捕获的环境变量替换新进程中的环境变量。每次运行程序时,每个参数组合只需要调用一次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. 
        }
    }
    

    如果您使用此功能,请注意,如果vcvarsall.bat丢失或失败,它可能会死得很厉害,而且除了en-US之外,系统可能存在问题。

答案 1 :(得分:1)

可能没有比收集所需数据更好的方法,生成bat文件并使用Process类运行它。 正如您所写,您正在重定向输出,这意味着您必须设置UseShellExecute = false;所以我认为除了从bat文件调用SET之外没有办法设置变量。

答案 2 :(得分:0)

编辑:为nmake调用添加特定用例

我过去需要获得各种“构建路径的东西”,这就是我用过的东西 - 你可能需要在这里或那里调整一些东西以适应,但基本上,vcvars唯一能做的就是建立一堆路径;这些辅助方法会获取这些路径名,您只需要将它们传递给您的开始信息:

public static string GetFrameworkPath()
{
    var frameworkVersion = string.Format("v{0}.{1}.{2}", Environment.Version.Major, Environment.Version.Minor, Environment.Version.Build);
    var is64BitProcess = Environment.Is64BitProcess;
    var windowsPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
    return Path.Combine(windowsPath, "Microsoft.NET", is64BitProcess ? "Framework64" : "Framework", frameworkVersion);  
}

public static string GetPathToVisualStudio(string version)
{   
    var is64BitProcess = Environment.Is64BitProcess;
    var registryKeyName = string.Format(@"Software\{0}Microsoft\VisualStudio\SxS\VC7", is64BitProcess ? @"Wow6432Node\" : string.Empty);
    var vsKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(registryKeyName);
    var versionExists = vsKey.GetValueNames().Any(valueName => valueName.Equals(version));
    if(versionExists)
    {
        return vsKey.GetValue(version).ToString();
    }
    else
    {
        return null;
    }
}

你可以通过以下方式利用这些东西:

var paths = new[]
    { 
        GetFrameworkPath(), 
        GetPathToVisualStudio("10.0"),
        Path.Combine(GetPathToVisualStudio("10.0"), "bin"),
    };  

var previousPaths = Environment.GetEnvironmentVariable("PATH").ToString();
var newPaths = string.Join(";", previousPaths.Split(';').Concat(paths));
Environment.SetEnvironmentVariable("PATH", newPaths);

var startInfo = new ProcessStartInfo()
{
    FileName = "nmake",
    Arguments = "whatever you'd pass in here",
};
var process = Process.Start(startInfo);