簡體   English   中英

從C#在同一環境中執行多個命令

[英]Execute multiple commands in same environment from C#

我正在開發一個小型的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);

這樣可行,但不會捕獲cmd調用的輸出。 還在尋找更好的東西

我有幾個不同的建議

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

您可能只需調用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之外,系統可能存在問題。

可能沒有比收集所需數據更好的方法,生成bat文件並使用Process類運行它。 正如您所寫,您正在重定向輸出,這意味着您必須設置UseShellExecute = false; 所以我認為沒有辦法設置你的變量,然后從bat文件中調用SET。

編輯:為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);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM