[英]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調用的輸出。 還在尋找更好的東西
我有幾個不同的建議
您可能希望使用MSBuild而不是NMake進行研究
它更復雜,但它可以直接從.Net控制,它是VS項目文件的格式,適用於從VS 2010開始的所有項目,以及C#/ VB /等。 項目早於此
您可以使用一個小幫助程序捕獲環境並將其注入您的進程
這可能有點矯枉過正,但它會起作用。 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.