Display output of the CMD in real time inside TextBox

Ask questions about creating Graphical User Interfaces (GUI) in PowerShell and using WinForms controls.
Forum rules
Do not post any licensing information in this forum.

Any code longer than three lines should be added as code using the 'Select Code' dropdown menu or attached as a file.
jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

To get base events in a form you will need to use a runspace.

To use the process object you will need to use a runspace.

User avatar
ALIENQuake
Posts: 80
Joined: Sun Mar 03, 2013 12:45 pm
Has voted: 3 times

Re: Display output of the CMD in real time inside TextBox

Post by ALIENQuake »

Ok but since all child runspace proces are executed inside runspace, does using runspace won't prevent access to "$process.SynchronizingObject", "OutputDataReceived" and "Exited" properly?
Last edited by ALIENQuake on Sun Sep 02, 2018 3:02 am, edited 2 times in total.

jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

??? "does using runspace won't prevent setting " ???

User avatar
ALIENQuake
Posts: 80
Joined: Sun Mar 03, 2013 12:45 pm
Has voted: 3 times

Re: Display output of the CMD in real time inside TextBox

Post by ALIENQuake »

Look at this code:

Code: Select all

$script:process = New-Object System.Diagnostics.Process
$process.add_OutputDataReceived($process_OutputReceived)
$process.add_Exited($process_Exited)
$process.SynchronizingObject = $formAsyncPingerTest
Does runspace will have access to those extra functions: $process_OutputReceived, $process_Exited and $formAsyncPingerTest for $process.SynchronizingObject property?

jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

Here. Play with this fr a bit until you see how the object can get wired up.

You can use "location" and "size" to set the Window position of the console output or just comment out the "SetHanlde call.
Attachments
Test-WB1.psf
(12.17 KiB) Downloaded 92 times

User avatar
ALIENQuake
Posts: 80
Joined: Sun Mar 03, 2013 12:45 pm
Has voted: 3 times

Re: Display output of the CMD in real time inside TextBox

Post by ALIENQuake »

@jvierra I'm grateful for you commitment but I can't see how waiting for 'MainWindowHandle' can help. The 'do ... until' is cause application to freeze as soon as I want to execute two process, one by one.

This is my attempt to solve this by Runspaces:
- ability to see process output
- ability to send input if it will be required
- if process execution code is triggered outside of the form then everything works as desired: multiple process are executed one by one
- but as soon as I use the same code for Form Button2, the whole application hangs even if it's inside runspace
- removing loop from Button2_Click code unfreeze application but both process are executed at once

Now, I need that last missing piece to have working solution. Can you take look one more time?

Code: Select all


$script:SyncHashTable = [Hashtable]::Synchronized(@{ Exited = $false })

function DisplayLogSendInput {
    
    $RunSpace = [RunspaceFactory]::CreateRunspace()
    $RunSpace.ApartmentState = "STA"
    $RunSpace.ThreadOptions = "ReuseThread"
    
    $RunSpace.Open()
    $RunSpace.SessionStateProxy.SetVariable("SyncHashTable",$script:SyncHashTable)

    $PowerShellCmd = [PowerShell]::Create().AddScript({
        [reflection.assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
        [reflection.assembly]::LoadWithPartialName("System.Drawing") | Out-Null

        $script:SyncHashTable.Button2_Click = {
            # code below will hang application
            'ping','ping' | % {
                $script:SyncHashTable.Exited = $false
                $initialSessionState = [InitialSessionState]::CreateDefault()
                $RunSpace2 = [runspacefactory]::CreateRunspace($initialSessionState)
                $RunSpace2.ApartmentState = "STA"
                $RunSpace2.ThreadOptions = "ReuseThread"
                $RunSpace2.Open()
                $RunSpace2.SessionStateProxy.SetVariable("SyncHashTable", $script:SyncHashTable)
                $PowerShellCmd2 = [Management.Automation.PowerShell]::Create().AddScript( {
                    param($ProcessName)

                    $process_OutputReceived = {
                        $script:SyncHashTable.TextBox1.Lines += $_.Data
                        $script:SyncHashTable.TextBox1.Select($script:SyncHashTable.TextBox1.Text.Length, 0)
                        $script:SyncHashTable.TextBox1.ScrollToCaret()
                    }

                    $process_Exited = {
                        Write-Host "Process has ended"
                        $script:SyncHashTable.Exited = $true
                        $script:SyncHashTable.TextBox1.Lines += "Process has ended"
                        $process.CancelOutputRead()
                        $process.Dispose()
                    }

                    $process = New-Object System.Diagnostics.Process
                    $script:SyncHashTable.process = $process
                    $process.EnableRaisingEvents = $true
                    $process.StartInfo.RedirectStandardError = $true
                    $process.StartInfo.RedirectStandardOutput = $true
                    $process.StartInfo.RedirectStandardInput = $true
                    $process.StartInfo.FileName = $processName
                    $process.StartInfo.UseShellExecute = $false
                    $process.StartInfo.CreateNoWindow = $true
                    $process.StartInfo.Arguments = '-n 3 google.com'


                    $process.add_OutputDataReceived($process_OutputReceived)
                    $process.add_Exited($process_Exited)
                    $process.SynchronizingObject = $script:SyncHashTable.Form1

                    $process.Start() | Out-Null
                    $process.BeginOutputReadLine()
                }, $True)
                $PowerShellCmd2.AddArgument($_)
                $PowerShellCmd2.Runspace = $RunSpace2
                $PowerShellCmd2.BeginInvoke()
                
                Write-Host "Exited: $($script:SyncHashTable.Exited)"
                # removing this loop unfreeze application but both process are executed at once
                do {
                    Write-Host $script:SyncHashTable.Exited
                    sleep -Milliseconds 1000
                } until ( $script:SyncHashTable.Exited -eq $true)

                SendToTextBox 'End loop'
            }
        }
        
        #region Form Controls
        $script:SyncHashTable.Button1_Click = {
            #TODO: Place custom script here
            $script:SyncHashTable.TextBox1.Text = $script:SyncHashTable.TextBox1.Text + 'Input was:' + $script:SyncHashTable.TextBox2.Text + "`n"
            $script:SyncHashTable.process.StandardInput.WriteLine($script:SyncHashTable.TextBox2.Text)
            $script:SyncHashTable.process.StandardInput.WriteLine("`n") # Simulate 'Enter' keypress
            $script:SyncHashTable.TextBox1.Select($script:SyncHashTable.TextBox1.Text.Length, 0)
            $script:SyncHashTable.TextBox1.ScrollToCaret()
        }
        $script:SyncHashTable.Form1 = New-Object System.Windows.Forms.Form
        $script:SyncHashTable.TextBox1 = New-Object System.Windows.Forms.RichTextBox
        $script:SyncHashTable.InitialFormWindowState = New-Object System.Windows.Forms.FormWindowState
        
        $script:SyncHashTable.System_Drawing_Size = New-Object System.Drawing.Size
        $script:SyncHashTable.System_Drawing_Size.Height = 320
        $script:SyncHashTable.System_Drawing_Size.Width = 360
        $script:SyncHashTable.Form1.ClientSize = $script:SyncHashTable.System_Drawing_Size
        $script:SyncHashTable.Form1.DataBindings.DefaultDataSourceUpdateMode = 0
        $script:SyncHashTable.Form1.Name = "Form1"
        $script:SyncHashTable.Form1.Text = "Log Box"
        
        $script:SyncHashTable.TextBox1.Anchor = 15
        $script:SyncHashTable.TextBox1.DataBindings.DefaultDataSourceUpdateMode = 0
        $script:SyncHashTable.System_Drawing_Point = New-Object System.Drawing.Point
        $script:SyncHashTable.System_Drawing_Point.X = 12
        $script:SyncHashTable.System_Drawing_Point.Y = 48
        $script:SyncHashTable.TextBox1.Location = $script:SyncHashTable.System_Drawing_Point
        $script:SyncHashTable.TextBox1.Name = "TextBox1"
        $script:SyncHashTable.System_Drawing_Size = New-Object System.Drawing.Size
        $script:SyncHashTable.System_Drawing_Size.Height = 200
        $script:SyncHashTable.System_Drawing_Size.Width = 336
        $script:SyncHashTable.TextBox1.Size = $script:SyncHashTable.System_Drawing_Size
        $script:SyncHashTable.TextBox1.TabIndex = 0
        $script:SyncHashTable.TextBox1.Text = ""
        
        $script:SyncHashTable.TextBox2 = New-Object System.Windows.Forms.RichTextBox
        $script:SyncHashTable.TextBox2.Anchor = 15
        $script:SyncHashTable.TextBox2.DataBindings.DefaultDataSourceUpdateMode = 0
        $script:SyncHashTable.System_Drawing_Point = New-Object System.Drawing.Point
        $script:SyncHashTable.System_Drawing_Point.X = 12
        $script:SyncHashTable.System_Drawing_Point.Y = 250
        $script:SyncHashTable.TextBox2.Location = $script:SyncHashTable.System_Drawing_Point
        $script:SyncHashTable.TextBox2.Name = "TextBox1"
        $script:SyncHashTable.System_Drawing_Size = New-Object System.Drawing.Size
        $script:SyncHashTable.System_Drawing_Size.Height = 24
        $script:SyncHashTable.System_Drawing_Size.Width = 220
        $script:SyncHashTable.TextBox2.Size = $script:SyncHashTable.System_Drawing_Size
        $script:SyncHashTable.TextBox2.TabIndex = 0
        $script:SyncHashTable.TextBox2.Text = ""

        $script:SyncHashTable.Button1 = New-Object System.Windows.Forms.Button
        $script:SyncHashTable.System_Drawing_Point = New-Object System.Drawing.Point
        $script:SyncHashTable.System_Drawing_Point.X = 260
        $script:SyncHashTable.System_Drawing_Point.Y = 250
        $script:SyncHashTable.Button1.Location = $script:SyncHashTable.System_Drawing_Point
        $script:SyncHashTable.Button1.Name = "Button1"
        $script:SyncHashTable.Button1.Text = "SendInput"

        $script:SyncHashTable.Button2 = New-Object System.Windows.Forms.Button
        $script:SyncHashTable.System_Drawing_Point = New-Object System.Drawing.Point
        $script:SyncHashTable.System_Drawing_Point.X = 12
        $script:SyncHashTable.System_Drawing_Point.Y = 12
        $script:SyncHashTable.Button2.Location = $script:SyncHashTable.System_Drawing_Point
        $script:SyncHashTable.Button2.Name = "Button2"
        $script:SyncHashTable.Button2.Text = "Start-OneByOneFromForm"


        $script:SyncHashTable.Form1.Controls.Add($script:SyncHashTable.Button1)
        $script:SyncHashTable.Form1.Controls.Add($script:SyncHashTable.Button2)
        $script:SyncHashTable.Form1.Controls.Add($script:SyncHashTable.TextBox1)
        $script:SyncHashTable.Form1.Controls.Add($script:SyncHashTable.TextBox2)

        $script:SyncHashTable.Button1.Add_Click($script:SyncHashTable.Button1_Click)
        $script:SyncHashTable.Button2.Add_Click($script:SyncHashTable.Button2_Click)

        $script:SyncHashTable.InitialFormWindowState = $script:SyncHashTable.Form1.WindowState
        $script:SyncHashTable.Form1.ShowDialog() | Out-Null
        #endregion
    })
        
    $PowerShellCmd.Runspace = $RunSpace
    $PowerShellCmd.BeginInvoke()
}

function SendToTextBox ($Message) {
    $script:SyncHashTable.TextBox1.Text = $script:SyncHashTable.TextBox1.Text + $Message + "`n"
    $script:SyncHashTable.TextBox1.Select($script:SyncHashTable.TextBox1.Text.Length, 0)
    $script:SyncHashTable.TextBox1.ScrollToCaret()
}

DisplayLogSendInput | Out-Null
Start-Sleep 1
SendToTextBox 'Start loop'

function Start-OneByOne ($ProcessName) {
    $script:SyncHashTable.Exited = $false
    $initialSessionState = [InitialSessionState]::CreateDefault()
    $RunSpace2 = [runspacefactory]::CreateRunspace($initialSessionState)
    $RunSpace2.ApartmentState = "STA"
    $RunSpace2.ThreadOptions = "ReuseThread"
    $RunSpace2.Open()
    $RunSpace2.SessionStateProxy.SetVariable("SyncHashTable", $script:SyncHashTable)
    $PowerShellCmd2 = [Management.Automation.PowerShell]::Create().AddScript( {
        param($processName)

            $process_OutputReceived = {
                $script:SyncHashTable.TextBox1.Lines += $_.Data
                $script:SyncHashTable.TextBox1.Select($script:SyncHashTable.TextBox1.Text.Length, 0)
                $script:SyncHashTable.TextBox1.ScrollToCaret()
            }

            $process_Exited = {
                Write-Host "Process has ended"
                $script:SyncHashTable.Exited = $true
                $script:SyncHashTable.TextBox1.Lines += "Process has ended"
                $process.CancelOutputRead()
                $process.Dispose()
            }

            $process = New-Object System.Diagnostics.Process
            $script:SyncHashTable.process = $process
            $process.EnableRaisingEvents = $true
            $process.StartInfo.RedirectStandardError = $true
            $process.StartInfo.RedirectStandardOutput = $true
            $process.StartInfo.RedirectStandardInput = $true
            $process.StartInfo.FileName = $processName
            $process.StartInfo.UseShellExecute = $false
            $process.StartInfo.CreateNoWindow = $false
            $process.StartInfo.Arguments = '-n 3 google.com'


            $process.add_OutputDataReceived($process_OutputReceived)
            $process.add_Exited($process_Exited)
            $process.SynchronizingObject = $script:SyncHashTable.Form1

            $process.Start() | Out-Null
            $process.BeginOutputReadLine()
        })
    $PowerShellCmd2.AddArgument($_)
    $PowerShellCmd2.Runspace = $RunSpace2
    $PowerShellCmd2.BeginInvoke()

    Write-Host "Exited: $($script:SyncHashTable.Exited)"
    do {
        Write-Host $script:SyncHashTable.Exited
        Start-Sleep -Milliseconds 1000
    } until ( $script:SyncHashTable.Exited -eq $true)
}

# This code executes without app freezing, one proces at the time
'ping','ping' | % {
    Start-OneByOne -ProcessName $_
}

SendToTextBox 'End loop'

jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

It works fine for me. Please run the example. Some external programs may misbehave if they take a long time to start. If that is the case then don't use the SetLocation. The rest works just fine without it.

jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

I should also not that my previous solution does not require a runspace and can be used for multiple processes. All you need to do is supply event code to handle the process events and run.

The reason you say my code blocks is because a runspace has no WindowHandle to wait on. Only a full process will have a WindowHandle that is non-zero. In a runspace you cannot assign the output Window because there is no form and assigning to the main form will run into cross thread issues.

User avatar
ALIENQuake
Posts: 80
Joined: Sun Mar 03, 2013 12:45 pm
Has voted: 3 times

Re: Display output of the CMD in real time inside TextBox

Post by ALIENQuake »

@jvierra
Ofc I try you example. I'm on Windows 10 1803, I even try inside Windows 7 VM. But it just executes two process at the same time. There is no waiting for previous process to complete.

Code: Select all

$code = @'
[DllImport("user32.dll", SetLastError = true)]
 public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
'@
Add-Type -MemberDefinition $code -Name Win32SetParent -Namespace Win32Functions
$form1_Load={
	#TODO: Initialize Form Controls here
	#$webbrowser1.Document.Window.Open('http://www.google.com',"_self", "status=yes,resizable=yes", $true)
    $p = [System.Diagnostics.Process]::New()
    $p.add_Exited({
        [System.Windows.Forms.MessageBox]::Show('Process Exited!')
    })
   'ping', 'ping' | % {
		$p.add_OutputDataReceived({
				$textbox2.Lines += $_.Data
			})
		$p.StartInfo.RedirectStandardOutput = $true
		$p.StartInfo.UseShellExecute = $false
		$p.StartInfo.FileName = $_
		$p.StartInfo.Arguments = 'localhost'
		$p.EnableRaisingEvents = $true
		$p.SynchronizingObject = $form1
		$p.Start()

		do {
		Start-Sleep -Milliseconds 50
		} until ($p.MainWindowHandle)
		$p.BeginOutputReadLine()
		[Win32Functions.Win32SetParent]::SetParent($p.MainWindowHandle, $textbox1.Handle)
	}
}

$form1_FormClosing=[System.Windows.Forms.FormClosingEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosingEventArgs]
	Write-Host Form closing
}

$form1_FormClosed=[System.Windows.Forms.FormClosedEventHandler]{
#Event Argument: $_ = [System.Windows.Forms.FormClosedEventArgs]
	Write-Host Form Closed
}

$form1_ParentChanged={
	Write-Host Parent
}

jvierra
Posts: 14674
Joined: Tue May 22, 2007 9:57 am
Answers: 6
Has voted: 1 time
Been upvoted: 5 times
Contact:

Re: Display output of the CMD in real time inside TextBox

Post by jvierra »

You have to code it to do that. Just use the process end event to start the next process.

Locked