How to redirect Powershell output from a script run by TaskScheduler and override default width of 80 characters のような同様の、しかし異なる質問があります。
私はずっと前に書かれたカスタムインストーラーフレームワークを持っています。その中で「タスク」を実行できます。最近、PowerShell スクリプトを実行するタスクを追加する必要がありました。タスクは C# で記述されていますが、PowerShell スクリプトのコマンドを直接呼び出すことはできません。残念ながら、それは検討の余地がありません。
つまり、C# から PowerShell 実行可能ファイルを呼び出し、その出力をアプリケーションにリダイレクトしたいと考えています。これまでに行ったことは次のとおりです。
次のコードを使用して、PowerShell を正常に呼び出すことができます (作成したテスト プロジェクトから)。
string powerShellExeLocation = null;
RegistryKey localKey = Registry.LocalMachine;
RegistryKey subKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine");
powerShellExeLocation = subKey.GetValue("ApplicationBase").ToString();
if (!Directory.Exists(powerShellExeLocation))
throw new Exception("Cannot locate the PowerShell dir.");
powerShellExeLocation = Path.Combine(powerShellExeLocation, "powershell.exe");
if (!File.Exists(powerShellExeLocation))
throw new Exception("Cannot locate the PowerShell executable.");
string scriptLocation = Path.Combine(Environment.CurrentDirectory, "PowerShellScript.ps1");
if (!File.Exists(scriptLocation))
throw new Exception("Cannot locate the PowerShell script.");
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.Verb = "runas";
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardOutput = true;
processInfo.WorkingDirectory = Environment.CurrentDirectory;
processInfo.FileName = powerShellExeLocation;
processInfo.Arguments = "-NoLogo -OutputFormat Text -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + scriptLocation + "\" ";
Process powerShellProcess = new Process();
powerShellProcess.StartInfo = processInfo;
powerShellProcess.OutputDataReceived += new DataReceivedEventHandler(powerShellProcess_OutputDataReceived);
powerShellProcess.ErrorDataReceived += new DataReceivedEventHandler(powerShellProcess_ErrorDataReceived);
powerShellProcess.Start();
while (!powerShellProcess.HasExited)
{
Thread.Sleep(500);
}
リダイレクトされた出力のハンドラーが呼び出されることはありません。
void powerShellProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
と
void powerShellProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
スクリプトが実行されていることにはかなりの自信があります。現在、PATH の内容を出力するテスト スクリプトがあります。
$a = $env:path; $a.Split(";")
これを PowerShell インスタンス内からテストしたところ、正しく出力されました。
PowerShell の出力を C# コードにリダイレクトするにはどうすればよいですか? 独自のログを処理するために実行するスクリプトに依存したくありません。それらの出力を収集し、フレームワークのロギング メカニズム内で処理したいと思います。
EDIT そのコード例で、終了を待つために奇妙なループを残しました。「WaitForExit」プロセスがありました:
powerShellProcess.WaitForExit();
編集
@MiniBill からの提案を使用します。次のコード スニピットを思いつきました。
powerShellProcess.Start();
while (!powerShellProcess.HasExited)
{
string line;
while (!string.IsNullOrEmpty((line = powerShellProcess.StandardOutput.ReadLine())))
this.LogInfo("PowerShell running: " + line);
}
これにより、出力がうまく処理されます。それは私の行を80文字でカットしています:(、しかし、誰かがそれを修正するための提案を提供できない限り、私はそれで暮らすことができます!
ソリューションの更新
/*
* The next few lines define the process start info for the PowerShell executable.
*/
ProcessStartInfo processInfo = new ProcessStartInfo();
processInfo.Verb = "runas";
processInfo.UseShellExecute = false;
processInfo.RedirectStandardError = true;
processInfo.RedirectStandardOutput = true;
processInfo.WorkingDirectory = Environment.CurrentDirectory;
//this FileName was retrieved earlier by looking in the registry for key "HKLM\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine" and the value for "ApplicationBase"
processInfo.FileName = powerShellExeLocation;
//if we're going to use script arguments build up the arguments from the process start correctly.
if (!string.IsNullOrEmpty(this.ScriptArguments))
processInfo.Arguments = "-NoLogo -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + ScriptLocation + "\" '" + ScriptArguments + "'";
else
processInfo.Arguments = "-NoLogo -NonInteractive -WindowStyle Hidden -ExecutionPolicy Unrestricted -File \"" + ScriptLocation + "\"";
//create the Process object, set the start info, and start the process
Process powerShellProcess = new Process();
powerShellProcess.StartInfo = processInfo;
powerShellProcess.Start();
/*
* While the PowerShell process hasn't exited do the following:
*
* Read from the current position of the StandardOutput to the end by each line and
* update the tasks progress with this information
*
* Read from the current position of StandardError to the end by each line, update
* the task's progress with this information and set that we have an error.
*
* Sleep for 250 milliseconds (1/4 of a sec)
*/
bool isError = false;
while (!powerShellProcess.HasExited)
{
string standardOuputLine, standardErrorLine;
while (!string.IsNullOrEmpty((standardOuputLine = powerShellProcess.StandardOutput.ReadLine())))
{
this.UpdateTaskProgress(standardOuputLine);
}
while (!string.IsNullOrEmpty((standardErrorLine = powerShellProcess.StandardError.ReadLine())))
{
this.UpdateTaskProgress(standardErrorLine);
isError = true;
}
Thread.Sleep(250);
}
/*
* Now that the process has completed read to the end of StandardOutput and StandardError.
* Update the task progress with this information.
*/
string finalStdOuputLine = powerShellProcess.StandardOutput.ReadToEnd();
string finalStdErrorLine = powerShellProcess.StandardError.ReadToEnd();
if (!string.IsNullOrEmpty(finalStdOuputLine))
this.UpdateTaskProgress(finalStdOuputLine);
if (!string.IsNullOrEmpty(finalStdErrorLine))
{
this.UpdateTaskProgress(finalStdErrorLine);
isError = true;
}
// there was an error during the run of PowerShell that was output to StandardError. This doesn't necessarily mean that
// the script error'd but there was a problem. Throw an exception for this.
if (isError)
throw new Exception(string.Format(CultureInfo.InvariantCulture, "Error during the execution of {0}.", this.ScriptLocation));