如何在powershell中异步捕获进程输出?

omhiaaxx  于 2023-06-23  发布在  Shell
关注(0)|答案(7)|浏览(113)

我想从我在PowerShell脚本中启动的进程中捕获stdout和stderr,并将其异步显示到控制台。我已经找到了一些通过MSDN和other blogs来实现这一点的文档。
在创建并运行下面的示例之后,我似乎无法获得任何要异步显示的输出。所有输出仅在进程终止时显示。

$ps = new-object System.Diagnostics.Process
$ps.StartInfo.Filename = "cmd.exe"
$ps.StartInfo.UseShellExecute = $false
$ps.StartInfo.RedirectStandardOutput = $true
$ps.StartInfo.Arguments = "/c echo `"hi`" `& timeout 5"

$action = { Write-Host $EventArgs.Data  }
Register-ObjectEvent -InputObject $ps -EventName OutputDataReceived -Action $action | Out-Null

$ps.start() | Out-Null
$ps.BeginOutputReadLine()
$ps.WaitForExit()

在这个例子中,我希望在程序执行结束之前在命令行上看到“hi”的输出,因为OutputDataReceived事件应该已经被触发了。
我尝试过使用其他可执行文件- java.exe,git.exe等。所有这些都有同样的效果,所以我只能认为有一些简单的东西我不理解或错过了。异步读取stdout还需要做什么?

6qftjkof

6qftjkof1#

我遇到了这个线程,并想分享我的解决方案,为任何人可能需要在未来。这是在PowerShell Core 7.3.4上运行的。

<#
.Synopsis
    This function will run a provided command and arguments.
.DESCRIPTION
    This function was created due to the inconsistencies of running Start-Process in Linux. This function provides a 
    consistent way of running non-PowerShell commands that require many parameters/arguments to run (e.g., docker).
    
    PowerShell commands or aliases will NOT work with this function. For example commands such as: echo, history, or cp
    will NOT work. Use the build-in PowerShell commands for those.
.PARAMETER Name
    The path or name of the command to be ran.
.PARAMETER Arguments
    The optional parameters/arguments to be added with your command.
.PARAMETER WorkingDirectory
    The current WorkingDirectory to run said Command. If you are not using the full path to files, you should probably
    use this parameter. 
.PARAMETER LoadUserProfile
    Gets or sets a value that indicates whether the Windows user profile is to be loaded from the registry.

    This will NOT work on Unix/Linux.
.PARAMETER Timer
    Provide a timer (in ms) for how long you want to wait for the process to exit/end.
.PARAMETER Verb
    Specifies a verb to use when this cmdlet starts the process. The verbs that are available are determined by the filename extension of the file that runs in the process.

    The following table shows the verbs for some common process file types.

    File type   Verbs
    .cmd    Edit, Open, Print, RunAs, RunAsUser
    .exe    Open, RunAs, RunAsUser
    .txt    Open, Print, PrintTo
    .wav    Open, Play
    To find the verbs that can be used with the file that runs in a process, use the New-Object cmdlet to create a System.Diagnostics.ProcessStartInfo object for the file. The available verbs are in the Verbs property of the ProcessStartInfo object. For details, see the examples.

    This will NOT work on Unix/Linux.
.PARAMETER Passthru
    Pass the object into the pipeline. Using -Passthru will ignore error-handling.
.NOTES
    Author - Zack Flowers
.LINK
    GitHub: https://github.com/zackshomelab
.EXAMPLE
    Start-Command -Name 'docker' -CommandArguments "container ls --all"
    
    Example #1:
    This example executes command 'docker' and passes arguments 'container ls --all' to display the offline/online containers.
.EXAMPLE
    Start-Command -Name 'docker' -CommandArguments "container", "ls", "--all"

    Example #2:
    This example is simular to Example #1, except it accepts comma-separated arguments.
.EXAMPLE
    $whoami = Start-Command -Name 'whoami' -Passthru

    $whoami

    Title        : whoami
    OutputStream : System.Management.Automation.PSEventJob
    OutputData   : zac
    ErrorStream  : 
    ErrorData    : 
    ExitCode     : 0

    Example #3:
    This example utilizes the -Passthru feature of this script.
.INPUTS
    None
.OUTPUTS
    System.String
    System.Management.Automation.PSCustomObject
#>
function Start-Command {
    [cmdletbinding(DefaultParameterSetName="default")]
    param (
        [parameter(Mandatory,
            Position=0,
            ValueFromPipelineByPropertyName)]
            [ValidateNotNullOrEmpty()]
        [string]$Name,

        [parameter(Mandatory=$false,
            Position=1,
            ValueFromPipelineByPropertyName)]
            [ValidateNotNullOrEmpty()]
        [object]$Arguments,

        [parameter(Mandatory=$false,
            ValueFromPipelineByPropertyName)]
            [ValidateScript({Test-Path $_})]
        [string]$WorkingDirectory,

        [parameter(Mandatory=$false)]
            [ValidateScript({
                if ($PSVersionTable.Platform -eq "Unix") {
                    Throw "-LoadUserProfile cannot be used on Unix/Linux."
                }
            })]
        [switch]$LoadUserProfile,

        [parameter(Mandatory,
            ValueFromPipelineByPropertyName,
            ParameterSetName="timer")]
            [ValidateRange(1, 600000)]
        [int]$Timer,

        [parameter(Mandatory=$false,
            ValueFromPipelineByPropertyName)]
            [ValidateScript({
                if ($PSVersionTable.Platform -eq "Unix") {
                    Throw "-Verb cannot be used on Unix/Linux."
                }
            })]
        [string]$Verb,

        [parameter(Mandatory=$false)]
        [switch]$Passthru
    )

    begin {
        $FileName = (Get-Command -Name $Name -ErrorAction SilentlyContinue).Source

        # If we cannot find the provided FileName, this could be due to the user providing..
        # ..a command that is a PowerShell Alias (e.g., echo, history, cp)
        if ($null -eq $FileName -or $FileName -eq "") {
            
            # Source doesn't exist. Let's see if the provided command is a PowerShell command
            $getPSCommand = (Get-Command -Name $Name -ErrorAction SilentlyContinue)

            if ($null -eq $getPSCommand -or $getPSCommand -eq "") {
                Throw "Start-Command: Could not find command $Name nor could we find its PowerShell equivalent."
            }

            # Stop the script if the command was found but it returned an alias. 
            # Sometimes, a command may not return a source but WILL return an alias. This will cause issues with incompatibility with..
            # ..parameters for said commands.
            #
            # Example commands that will not work: echo, history, and cd
            if ($getPSCommand.CommandType -eq 'Alias') {
                Throw "Start-Command: This function does not support Aliases. Command $Name matches $($getPSCommand.ResolvedCommand.Name)."
            }

            # This function does not support Microsoft PowerShell commands.
            if ($getPSCommand.Source -like "Microsoft.PowerShell*") {
                Throw "Start-Command: This function should only be used for Non-PowerShell commands (e.g., wget, touch, mkdir, etc.)"
            }

            # Retrieve the version of PowerShell and its location and replace $FileName with it
            $FileName = $PSVersionTable.PSEdition -eq 'Core' ? (Get-Command -Name 'pwsh').Source : (Get-Command -Name 'powershell').Source
            
            # Reconfigure Arguments to execute PowerShell
            $Arguments = "-noprofile -Command `"& {$($getPSCommand.ReferencedCommand.Name) $Arguments}`""
        }

        # Data Object will store all streams of data from our command
        $dataObject = [pscustomobject]@{
            Title        = $Name
            OutputStream = ''
            OutputData   = ''
            ErrorData    = ''
            ExitCode     = 0
        }
    }
    process {

        $processStartInfoProps = @{
            Arguments               = $null -ne $Arguments ? $Arguments : $null
            CreateNoWindow          = $true
            ErrorDialog             = $false
            FileName                = $FileName
            RedirectStandardError   = $true
            RedirectStandardInput   = $true
            RedirectStandardOutput  = $true
            UseShellExecute         = $false
            WindowStyle             = [System.Diagnostics.ProcessWindowStyle]::Hidden
            WorkingDirectory        = $PSBoundParameters.ContainsKey('WorkingDirectory') ? $WorkingDirectory : $PSScriptRoot
            Verb                    = $PSBoundParameters.ContainsKey('Verb') ? $Verb : $null
        }

        # This will Error on Unix/Linux Systems if property LoadUserProfile is added regardless if it's null or false.
        if ($PSBoundParameters.ContainsKey('LoadUserProfile')) {
            $processStartInfoProps.Add('LoadUserProfile', $LoadUserProfile)
        }

        try {

            $process = New-Object System.Diagnostics.Process
            $process.EnableRaisingEvents = $true

            $processStartInfo = New-Object System.Diagnostics.ProcessStartInfo -Property $processStartInfoProps
            $process.StartInfo = $processStartInfo

            # Register Process OutputDataReceived:
            #   This will create a background job to capture output data
            #   Reference: https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardoutput?redirectedfrom=MSDN&view=net-7.0#System_Diagnostics_Process_StandardOutput
            $outputEventParams = @{
                InputObject = $process
                SourceIdentifier = 'OnOutputDataReceived '
                EventName = 'OutputDataReceived'
                Action = {
                    param (
                        [System.Object]$sender,
                        [System.Diagnostics.DataReceivedEventArgs]$e
                    )

                    foreach ($data in $e.Data) { 
                        if ($null -ne $data -and $data -ne "") { 
                            $($data).Trim()
                        } 
                    }
                }
            }
            $dataObject.OutputStream = Register-ObjectEvent @outputEventParams

            # Start the process/command
            if ($process.Start()) {
                $process.BeginOutputReadLine()
                $dataObject.ErrorData = $process.StandardError.ReadToEnd()

                if ($PSCmdlet.ParameterSetName -eq 'timer') {
                    $process.WaitForExit($Timer) | Out-Null
                } else {
                    $process.WaitForExit()
                }
            }
            
            # Retrieve the exit code and the OutputStream Job
            $dataObject.ExitCode = $process.ExitCode
            $dataObject.OutputData = Receive-Job -id $($dataObject.OutputStream.id)

            [bool]$hasError = ($null -ne $($dataObject.ErrorData) -and $($dataObject.ErrorData) -ne "" -and $($dataObject.ExitCode) -ne 0) ? $true : $false
            [bool]$hasOutput = ($null -ne $($dataObject.OutputData) -and $($dataObject.OutputData) -ne "") ? $true : $false

            # Output the PSCustomObject if -Passthru is provided.
            if ($Passthru) {
                if ($hasError) {
                    $dataObject.ErrorData = $($dataObject.ErrorData.Trim())
                }
                $dataObject
            } else {

                if ($hasError) {
                    if ($($ErrorActionPreference) -ne 'Stop') {
                        Write-Error "Exit Code $($dataObject.ExitCode): $($dataObject.ErrorData.Trim())"
                    } else {
                        Throw "Exit Code $($dataObject.ExitCode): $($dataObject.ErrorData.Trim())"
                    }
                }

                if ($hasOutput) {
                    $($dataObject.OutputData)
                }
            }
        }
        finally {

            # Cleanup
            $process.Close()
            Unregister-Event -SourceIdentifier $($dataObject.OutputStream.Name) -Force | Out-Null
            Remove-Job -Id $($dataObject.OutputStream.Id) -Force
        }
    }
}

示例1:常规使用

Start-Command -Name 'docker' -Arguments 'container ls --all'

示例2:逗号分隔的参数

Start-Command -Name 'docker' -Arguments 'container', 'ls', '--all'

实施例3:通过使用

$whoami = Start-Command -Name 'whoami' -Passthru

$whoami

Title        : whoami
OutputStream : System.Management.Automation.PSEventJob
OutputData   : zac
ErrorStream  : 
ErrorData    : 
ExitCode     : 0

示例4:错误示例

Start-Command -Name 'docker' -Arguments 'force' -ErrorAction Stop

Output: 
Line |
 245 |  …             Throw "Exit Code $($dataObject.ExitCode): $($dataObject.E …
     |                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Exit Code 1: docker: 'force' is not a docker command. See 'docker --help'
kmpatx3s

kmpatx3s2#

不幸的是,如果你想正确地进行异步阅读,并不是那么容易。如果你调用WaitForExit()而不超时,你可以使用类似我写的这个函数(基于C#代码):

function Invoke-Executable {
    # Runs the specified executable and captures its exit code, stdout
    # and stderr.
    # Returns: custom object.
    param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$sExeFile,
        [Parameter(Mandatory=$false)]
        [String[]]$cArgs,
        [Parameter(Mandatory=$false)]
        [String]$sVerb
    )

    # Setting process invocation parameters.
    $oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
    $oPsi.CreateNoWindow = $true
    $oPsi.UseShellExecute = $false
    $oPsi.RedirectStandardOutput = $true
    $oPsi.RedirectStandardError = $true
    $oPsi.FileName = $sExeFile
    if (! [String]::IsNullOrEmpty($cArgs)) {
        $oPsi.Arguments = $cArgs
    }
    if (! [String]::IsNullOrEmpty($sVerb)) {
        $oPsi.Verb = $sVerb
    }

    # Creating process object.
    $oProcess = New-Object -TypeName System.Diagnostics.Process
    $oProcess.StartInfo = $oPsi

    # Creating string builders to store stdout and stderr.
    $oStdOutBuilder = New-Object -TypeName System.Text.StringBuilder
    $oStdErrBuilder = New-Object -TypeName System.Text.StringBuilder

    # Adding event handers for stdout and stderr.
    $sScripBlock = {
        if (! [String]::IsNullOrEmpty($EventArgs.Data)) {
            $Event.MessageData.AppendLine($EventArgs.Data)
        }
    }
    $oStdOutEvent = Register-ObjectEvent -InputObject $oProcess `
        -Action $sScripBlock -EventName 'OutputDataReceived' `
        -MessageData $oStdOutBuilder
    $oStdErrEvent = Register-ObjectEvent -InputObject $oProcess `
        -Action $sScripBlock -EventName 'ErrorDataReceived' `
        -MessageData $oStdErrBuilder

    # Starting process.
    [Void]$oProcess.Start()
    $oProcess.BeginOutputReadLine()
    $oProcess.BeginErrorReadLine()
    [Void]$oProcess.WaitForExit()

    # Unregistering events to retrieve process output.
    Unregister-Event -SourceIdentifier $oStdOutEvent.Name
    Unregister-Event -SourceIdentifier $oStdErrEvent.Name

    $oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
        "ExeFile"  = $sExeFile;
        "Args"     = $cArgs -join " ";
        "ExitCode" = $oProcess.ExitCode;
        "StdOut"   = $oStdOutBuilder.ToString().Trim();
        "StdErr"   = $oStdErrBuilder.ToString().Trim()
    })

    return $oResult
}

它捕获stdout、stderr和退出代码。示例用法:

$oResult = Invoke-Executable -sExeFile 'ping.exe' -cArgs @('8.8.8.8', '-a')
$oResult | Format-List -Force

有关更多信息和替代实现(在C#中),请阅读this blog post

j8ag8udp

j8ag8udp3#

基于Alexander Obersht's answer,我创建了一个函数,它使用超时和异步Task类而不是事件处理程序。根据Mike Adelson
不幸的是,此方法(事件处理程序)无法知道何时收到最后一位数据。因为一切都是异步的,所以在WaitForExit()返回之后,事件有可能被触发(我观察到了这一点)。

function Invoke-Executable {
# from https://stackoverflow.com/a/24371479/52277
    # Runs the specified executable and captures its exit code, stdout
    # and stderr.
    # Returns: custom object.
# from http://www.codeducky.org/process-handling-net/ added timeout, using tasks
param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [String]$sExeFile,
        [Parameter(Mandatory=$false)]
        [String[]]$cArgs,
        [Parameter(Mandatory=$false)]
        [String]$sVerb,
        [Parameter(Mandatory=$false)]
        [Int]$TimeoutMilliseconds=1800000 #30min
    )
    Write-Host $sExeFile $cArgs

    # Setting process invocation parameters.
    $oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
    $oPsi.CreateNoWindow = $true
    $oPsi.UseShellExecute = $false
    $oPsi.RedirectStandardOutput = $true
    $oPsi.RedirectStandardError = $true
    $oPsi.FileName = $sExeFile
    if (! [String]::IsNullOrEmpty($cArgs)) {
        $oPsi.Arguments = $cArgs
    }
    if (! [String]::IsNullOrEmpty($sVerb)) {
        $oPsi.Verb = $sVerb
    }

    # Creating process object.
    $oProcess = New-Object -TypeName System.Diagnostics.Process
    $oProcess.StartInfo = $oPsi

    # Starting process.
    [Void]$oProcess.Start()
# Tasks used based on http://www.codeducky.org/process-handling-net/    
 $outTask = $oProcess.StandardOutput.ReadToEndAsync();
 $errTask = $oProcess.StandardError.ReadToEndAsync();
 $bRet=$oProcess.WaitForExit($TimeoutMilliseconds)
    if (-Not $bRet)
    {
     $oProcess.Kill();
    #  throw [System.TimeoutException] ($sExeFile + " was killed due to timeout after " + ($TimeoutMilliseconds/1000) + " sec ") 
    }
    $outText = $outTask.Result;
    $errText = $errTask.Result;
    if (-Not $bRet)
    {
        $errText =$errText + ($sExeFile + " was killed due to timeout after " + ($TimeoutMilliseconds/1000) + " sec ") 
    }
    $oResult = New-Object -TypeName PSObject -Property ([Ordered]@{
        "ExeFile"  = $sExeFile;
        "Args"     = $cArgs -join " ";
        "ExitCode" = $oProcess.ExitCode;
        "StdOut"   = $outText;
        "StdErr"   = $errText
    })

    return $oResult
}
5n0oy7gb

5n0oy7gb4#

我无法让这些例子中的任何一个在PS4.0上工作。
我想从Octopus Deploy包(通过Deploy.ps1)运行puppet apply,并“真实的”查看输出,而不是等待过程完成(一个小时后),所以我想出了以下方法:

# Deploy.ps1

$procTools = @"

using System;
using System.Diagnostics;

namespace Proc.Tools
{
  public static class exec
  {
    public static int runCommand(string executable, string args = "", string cwd = "", string verb = "runas") {

      //* Create your Process
      Process process = new Process();
      process.StartInfo.FileName = executable;
      process.StartInfo.UseShellExecute = false;
      process.StartInfo.CreateNoWindow = true;
      process.StartInfo.RedirectStandardOutput = true;
      process.StartInfo.RedirectStandardError = true;

      //* Optional process configuration
      if (!String.IsNullOrEmpty(args)) { process.StartInfo.Arguments = args; }
      if (!String.IsNullOrEmpty(cwd)) { process.StartInfo.WorkingDirectory = cwd; }
      if (!String.IsNullOrEmpty(verb)) { process.StartInfo.Verb = verb; }

      //* Set your output and error (asynchronous) handlers
      process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
      process.ErrorDataReceived += new DataReceivedEventHandler(OutputHandler);

      //* Start process and handlers
      process.Start();
      process.BeginOutputReadLine();
      process.BeginErrorReadLine();
      process.WaitForExit();

      //* Return the commands exit code
      return process.ExitCode;
    }
    public static void OutputHandler(object sendingProcess, DataReceivedEventArgs outLine) {
      //* Do your stuff with the output (write to console/log/StringBuilder)
      Console.WriteLine(outLine.Data);
    }
  }
}
"@

Add-Type -TypeDefinition $procTools -Language CSharp

$puppetApplyRc = [Proc.Tools.exec]::runCommand("ruby", "-S -- puppet apply --test --color false ./manifests/site.pp", "C:\ProgramData\PuppetLabs\code\environments\production");

if ( $puppetApplyRc -eq 0 ) {
  Write-Host "The run succeeded with no changes or failures; the system was already in the desired state."
} elseif ( $puppetApplyRc -eq 1 ) {
  throw "The run failed; halt"
} elseif ( $puppetApplyRc -eq 2) {
  Write-Host "The run succeeded, and some resources were changed."
} elseif ( $puppetApplyRc -eq 4 ) {
  Write-Warning "WARNING: The run succeeded, and some resources failed."
} elseif ( $puppetApplyRc -eq 6 ) {
  Write-Warning "WARNING: The run succeeded, and included both changes and failures."
} else {
  throw "Un-recognised return code RC: $puppetApplyRc"
}

感谢T30Stefan Goßner

ars1skjm

ars1skjm5#

这里的例子都很有用,但并不完全适合我的用例。我不想调用命令并退出。我想打开一个命令提示符,发送输入,读取输出,然后重复。这是我的解决方案。
创建Utils.CmdManager.cs

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace Utils
{
    public class CmdManager : IDisposable
    {
        const int DEFAULT_WAIT_CHECK_TIME = 100;
        const int DEFAULT_COMMAND_TIMEOUT = 3000;

        public int WaitTime { get; set; }
        public int CommandTimeout { get; set; }

        Process _process;
        StringBuilder output;

        public CmdManager() : this("cmd.exe", null, null) { }
        public CmdManager(string filename) : this(filename, null, null) { }
        public CmdManager(string filename, string arguments) : this(filename, arguments, null) { }

        public CmdManager(string filename, string arguments, string verb)
        {
            WaitTime = DEFAULT_WAIT_CHECK_TIME;
            CommandTimeout = DEFAULT_COMMAND_TIMEOUT;

            output = new StringBuilder();

            _process = new Process();
            _process.StartInfo.FileName = filename;
            _process.StartInfo.RedirectStandardInput = true;
            _process.StartInfo.RedirectStandardOutput = true;
            _process.StartInfo.RedirectStandardError = true;
            _process.StartInfo.CreateNoWindow = true;
            _process.StartInfo.UseShellExecute = false;
            _process.StartInfo.ErrorDialog = false;
            _process.StartInfo.Arguments = arguments != null ? arguments : null;
            _process.StartInfo.Verb = verb != null ? verb : null;

            _process.EnableRaisingEvents = true;
            _process.OutputDataReceived += (s, e) =>
            {
                lock (output)
                {
                    output.AppendLine(e.Data);
                };
            };
            _process.ErrorDataReceived += (s, e) =>
            {
                lock (output)
                {
                    output.AppendLine(e.Data);
                };
            };

            _process.Start();
            _process.BeginOutputReadLine();
            _process.BeginErrorReadLine();
            _process.StandardInput.AutoFlush = true;
        }

        public void RunCommand(string command)
        {
            _process.StandardInput.WriteLine(command);
        }

        public string GetOutput()
        {
            return GetOutput(null, CommandTimeout, WaitTime);
        }

        public string GetOutput(string endingOutput)
        {
            return GetOutput(endingOutput, CommandTimeout, WaitTime);
        }

        public string GetOutput(string endingOutput, int commandTimeout)
        {
            return GetOutput(endingOutput, commandTimeout, WaitTime);
        }

        public string GetOutput(string endingOutput, int commandTimeout, int waitTime)
        {
            string tempOutput = "";
            int tempOutputLength = 0;
            int amountOfTimeSlept = 0;

            // Loop until
            //  a) command timeout is reached
            //  b) some output is seen
            while (output.ToString() == "")
            {
                if (amountOfTimeSlept >= commandTimeout)
                {
                    break;
                }

                Thread.Sleep(waitTime);
                amountOfTimeSlept += waitTime;
            }

            // Loop until:
            //  a) command timeout is reached
            //  b) endingOutput is found
            //  c) OR endingOutput is null and there is no new output for at least waitTime
            while (amountOfTimeSlept < commandTimeout)
            {
                if (endingOutput != null && output.ToString().Contains(endingOutput))
                {
                    break;
                }
                else if(endingOutput == null && tempOutputLength == output.ToString().Length)
                {
                    break;
                }

                tempOutputLength = output.ToString().Length;

                Thread.Sleep(waitTime);
                amountOfTimeSlept += waitTime;
            }

            // Return the output and clear the buffer
            lock (output)
            {
                tempOutput = output.ToString();
                output.Clear();
                return tempOutput.TrimEnd();
            }
        }

        public void Dispose()
        {
            _process.Kill();
        }
    }
}

然后从PowerShell中添加该类并使用它。

Add-Type -Path ".\Utils.CmdManager.cs"

$cmd = new-object Utils.CmdManager
$cmd.GetOutput() | Out-Null

$cmd.RunCommand("whoami")
$cmd.GetOutput()

$cmd.RunCommand("cd")
$cmd.GetOutput()

$cmd.RunCommand("dir")
$cmd.GetOutput()

$cmd.RunCommand("cd Desktop")
$cmd.GetOutput()

$cmd.RunCommand("cd")
$cmd.GetOutput()

$cmd.RunCommand("dir")
$cmd.GetOutput()

$cmd.Dispose()

不要忘记在最后调用Dispose()函数来清理在后台运行的进程。或者,您可以通过运行类似$cmd.RunCommand("exit")的程序来关闭该进程

rta7y2nd

rta7y2nd6#

我来到这里寻找一个解决方案,创建一个 Package 器,记录过程,并将其输出到屏幕。这些对我都不起作用。我做了这个代码,看起来工作得很好。
PSDataCollection允许您继续执行脚本,而不必等待进程完成。

Using namespace System.Diagnostics;
Using namespace System.Management.Automation;

$Global:Dir = Convert-Path "."
$Global:LogPath = "$global:Dir\logs\mylog.log"
[Process]$Process = [Process]::New();
[ProcessStartInfo]$info = [ProcessStartInfo]::New();
$info.UseShellExecute = $false
$info.Verb = "runas"
$info.WorkingDirectory = "$Global:Dir\process.exe"
$info.FileName = "$Global:Dir\folder\process.exe"
$info.Arguments = "-myarg yes -another_arg no"
$info.RedirectStandardOutput = $true
$info.RedirectStandardError  = $true
$Process.StartInfo = $info;
$Process.EnableRaisingEvents = $true
$Global:DataStream = [PSDataCollection[string]]::New()
$Global:DataStream.add_DataAdded(
    {
        $line = $this[0];
        [IO.File]::AppendAllLines($LogPath, [string[]]$line);
        [Console]::WriteLine($line)
        $this.Remove($line);
    }
)
$script = {
    param([Object]$sender, [DataReceivedEventArgs]$e) 
    $global:Datastream.Add($e.Data)
}
Register-ObjectEvent -InputObject $Process -Action $script -EventName 'OutputDataReceived' | Out-Null
Register-ObjectEvent -InputObject $Process -Action $script -EventName 'ErrorDataReceived' | Out-Null
$Process.Start()
$Process.BeginOutputReadLine()
$Process.BeginErrorReadLine()
vd2z7a6w

vd2z7a6w7#

如果你只是想动态地将其转储到PowerShell控制台,请执行以下操作:
my.exe | Out-Default
我不能说我已经弄明白了。
请看这篇Technet文章的底部:https://social.technet.microsoft.com/Forums/windowsserver/en-US/b6691fba-0e92-4e9d-aec2-47f3d5a17419/start-process-and-redirect-output-to-powershell-window?forum=winserverpowershell
它也指this stackoverflow post
$LASTEXITCODE也填充了我的exe的退出代码,这也是我所需要的。

相关问题