Powershell WPF Runspace - Changing values in SyncHashtable is slow? - wpf

im building a tool with powershell wpf.
I finally managed to get my head around runspaces somewhat and am successfully updating the main forms progress bar from a runspace, woo!
However, it is really slow when i touch anything in the sync hash table it seems. Without updating a label &/or the progress bar in the synchash the function completes WAY faster. Am I missing something still?
My Runspace code is basically this:
$SyncHash = [hashtable]::Synchronized(#{
ProgressBar = $ProgressBar
Window = $Window
})
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("syncHash", $syncHash)
$SessionScript = [powershell]::Create().AddScript( {
for ($i = 0; $i -lt 100 ; $i++) {
Start-Sleep -Seconds .1
$SyncHash.Progress.Dispatcher.Invoke([action] { $SyncHash.Progress.Value = $i })
}
})
$SessionScript.Runspace = $Runspace
$SessionScript.BeginInvoke()
So for example if i comment out the line to update the progress bar that will complete way faster.... whyyy????

Related

Draw multiple circles on an image in windows.forms using powershell

I am mostly done creating a GUI that will pop up with an image in a picturebox, and allow the user to draw multiple red circles on it by clicking (where I'll then have it save to a passed filepath/name).
User draws the circle by clicking where on the image they want the top left starting point to be, and dragging to resize it (where the previous circle disappears and the correctly sized one is displayed) until they release the mouse.
The first circle appears correctly, but for all following circles it does not refresh properly and thus rather than resizing one circle dragging the mouse displays a bunch of different sized circles corresponding to all the past dragged mouse positions.
I have been banging my head on this one all day, what am I missing that will make the follow-up circles display correctly?
$file = "...TestImage.PNG"
$script:DrawingStatus = "In_Progress" #switch case for mouse drag
$panel1 = New-Object Windows.Forms.Panel
$Panel1.AutoScroll = $true;
$win = New-Object Windows.Forms.Form
$box = New-Object Windows.Forms.PictureBox
$box.Image = [System.Drawing.Image]::FromFile($file)
$box.AutoSize = $true
$src = $box.Image
$bmp=new-object System.Drawing.Bitmap $src.width, $src.height
$script:graphics=[System.Drawing.Graphics]::FromImage($bmp)
$units = [System.Drawing.GraphicsUnit]::Pixel
$destRect = new-object Drawing.Rectangle 0, 0, $src.width, $src.height
$srcRect = new-object Drawing.Rectangle 0, 0, $src.width, $src.height
$graphics.DrawImage($src, $destRect, $srcRect.X, $srcRect.Y, $srcRect.width, $srcRect.height, $units)
$image = $src
$Clicky4 = { #Executed on Mouse Drag while mouse is down
param($point)
$graphics.DrawImage($image, $destRect, $srcRect.X, $srcRect.Y, $srcRect.width, $srcRect.height, $units)
$box.image = $bmp
$mouse = [System.Windows.Forms.Cursor]::Position;
$point2 = $box.PointToClient($mouse);
write-host $point;
$mypen = new-object System.Drawing.Pen black
$mypen.color = "red"
$mypen.width = 3
$graphics.DrawEllipse($mypen, $point.X, $point.Y, ($point2.X-$point.X), ($point2.Y-$point.Y))
}
$clicky2 = { #executed on mouse down, calls clicky4
$script:DrawingStatus = "In_Progress"
write-host "Test 2 Passed!";
$mouse = [System.Windows.Forms.Cursor]::Position;
$point = $box.PointToClient($mouse);
write-host $point;
$box.tag = $point;
$superclick = {
switch ($script:DrawingStatus){
"In_Progress" {
Invoke-Command $clicky4 -ArgumentList $box.tag
}
"Done"{ #does nothing, so dragging mouse with mouse up does not trigger circle resizing
}
}
}
$box.add_MouseMove($superclick)
}
$clicky3 = { #executed on mouse up
write-host "Test 3 Passed!";
$script:DrawingStatus = "Done"
$script:image = $script:BMP #this is my attempt to create a BMP with the drawn circle after resizing so that the box image can be refreshed to this when drawing the next circle
$script:graphics=[System.Drawing.Graphics]::FromImage($script:image)
$mouse = [System.Windows.Forms.Cursor]::Position;
$point = $box.PointToClient($mouse);
write-host $point;
$array = #();
$array += $box.tag;
$array += $point;
$win.tag = $array
}
$Panel1.controls.add($box)
$win.width = 1500
$win.height = 800
$panel1.width = ($win.width - 15)
$panel1.height = ($win.height -50)
$box.add_MouseDown($clicky2)
$box.add_MouseUp($clicky3)
$win.Controls.Add($panel1)
$win.ShowDialog()
write-host "Coords: " $win.tag
$initial = $win.tag[0]
$Final = $win.tag[1]
write-host "Initial: " $initial
write-host "Final: " $Final
write-host "X1: " $initial.X
write-host "Y1: " $initial.Y
write-host "X2: " $final.X
write-host "Y2: " $final.Y
The biggest reason you're getting multiple circles is because your code is not erasing the old circles. Since you want them to be able to draw multiple circles in the window, the best way to do this would be with two System.Drawing.Drawing2D.GraphicsPath objects. One with the permanent circles, and one with the temporary circle being resized. MouseUp would add the current temp ellipse to the permanent GraphicsPath, while mouse move would clear and redraw the temp GraphicsPath and call Invalidate. In an Add_Paint event call, you would draw both GraphicsPaths to the ImageBox directly to draw over your base image. When saving after you can draw the permanent Paths onto the image before saving to file to permanently alter them.

PowerShell Version 4.0 (.PS1 File) - Add Text from TextBox into an Array to be printed on the console

Please Note this is PowerShell 4.0. I don't think Convert-String is available to , but granted, answers for other versions of PowerShell are still worth the read.
Alright. I'm very new to PowerShell, but interestingly enough both through Google and StackOverFlow I haven't been able to find the answer to this; I'm just not sure where to go yet.
Please keep in mind that i want to launch the file example.ps1 from the PowerShell prompt.
Function Show_Form
{
Add-Type -AssemblyName System.Windows.Forms
$global:Y_Position = 10
$global:X_Position = 5
$Form = New-Object system.Windows.Forms.Form
$Form.Text = "Sample Form"
$Form.Width = 400
$Form.Height = 450
$Form.AutoScroll = $True
$Form.AutoSize = $True
$Form.AutoSizeMode = "GrowOnly"
$Form.MinimizeBox = $False
$Form.MaximizeBox = $False
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Please Fill in the Required Information:`n"
$Label.AutoSize = $True
$Form.Controls.Add($Label)
$LabelTextBox = New-Object System.Windows.Forms.Label
$LabelTextBox.Location = New-Object System.Drawing.Point(10,40)
$LabelTextBox.Text = $Text
$LabelTextBox.AutoSize = $True
$Form.Controls.Add($LabelTextBox)
$global:textBox = New-Object System.Windows.Forms.TextBox
$X_Position = $X_Position+90
$textBox.Location = New-Object System.Drawing.Point(10,40)
$textBox.Size = New-Object System.Drawing.Size(260,20)
$Form.Controls.Add($textBox)
$Okbutton = New-Object System.Windows.Forms.Button
$Okbutton.Location = New-Object System.Drawing.Size(250,570)
$Okbutton.Size = New-Object System.Drawing.Size(100,30)
$Okbutton.Text = "OK"
<#-----------------------------------#>
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
<#-----------------------------------#>
$Okbutton.Add_Click({$Form.Close()})
$Form.Controls.Add($Okbutton)
$Form.ShowDialog()
}
Show_Form
My problem is with these lines:
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
The Add_Click({Write-Host $textBox.text}) displays in the Console but when I add $textBox.text to an array (object array or otherwise) or replace another element with it then print the Array, it is always just blank.
.
And if I do the following (Note: Arr[0] AND Arr[1])
$global:Arr = #("lll", "ppp", 2)
$OkButton.Add_Click({[System.Windows.Forms.MessageBox]::Show($textBox.text, "My Dialog Box")})
$OkButton.Add_Click({Write-Host $textBox.text})
$global:Arr[0] = $textBox.text
$OkButton.Add_Click({Write-Host $Arr})
$global:Arr[1] = "$textBox.text"
$OkButton.Add_Click({Write-Host $Arr})
Both elements get written as the value System.Windows.Forms.TextBox, Text: .text
I'm wondering how to make $textBox.text display in the Console in the array?
The problem you face is simple.
You assumed that $global:Arr[1] = "$textBox.text" would be set the actual text value from when you click the Ok button in your form.
But actually, this section of code is called before $Form.ShowDialog(). Therefore, at that point in time, your textbox is indeed empty, which explains what you get.
If you want your global array to be set to the textbox text after the Ok button is pressed, you need to set the array value when the user click the button through the Add_Click event.
Like this:
$OkButton.Add_Click( { $global:Arr[0] = $textBox.text })
Additional note
If you're not familiar already with how debugging work in Powershell, I'd recommend you to look at some debugging tutorials.
You would have immediately seen that the script did go to your variable assignment before the form is even shown, which would have possibly clued you in on the situation.
--
You can see below a debug point on the assignment line. The execution is stopped before I had a chance to see or enter anything in the form, which indicates that the value clearly can't be the one from the form. Querying $textBox.text at that point from the console shows the value to be empty, as we would expect from a newly initialized form.
Some documentation: how to debug scripts (even though it says ISE, all of this should be good with VSCode too)

In PowerShell and Windows Forms, how to capture user-entered data using multiple dynamically created input controls

I have a script I need to use for multiple parameter data collection, as follows:
function Build-FormPanel($FormTitle){
Add-Type -Assembly System.Windows.Forms ## Load the Windows Forms assembly
## Create the main form
$form = New-Object Windows.Forms.Form
$form.FormBorderStyle = "FixedToolWindow"
$form.Text = $FormTitle
$form.AutoScroll = $True
$form.StartPosition = "CenterScreen"
$form.Width = 740 ; $form.Height = 480 # Make the form wider
#Add Buttons- ## Create the button panel to hold the OK and Cancel buttons
$buttonPanel = New-Object Windows.Forms.Panel
$buttonPanel.Size = New-Object Drawing.Size #(400,40)
$buttonPanel.Dock = "Bottom"
$cancelButton = New-Object Windows.Forms.Button
$cancelButton.Top = $buttonPanel.Height - $cancelButton.Height - 10; $cancelButton.Left = $buttonPanel.Width - $cancelButton.Width - 10
$cancelButton.Text = "Cancel"
$cancelButton.DialogResult = "Cancel"
$cancelButton.Anchor = "Right"
## Create the OK button, which will anchor to the left of Cancel
$okButton = New-Object Windows.Forms.Button
$okButton.Top = $cancelButton.Top ; $okButton.Left = $cancelButton.Left - $okButton.Width - 5
$okButton.Text = "Ok"
$okButton.DialogResult = "Ok"
$okButton.Anchor = "Right"
## Add the buttons to the button panel
$buttonPanel.Controls.Add($okButton)
$buttonPanel.Controls.Add($cancelButton)
## Add the button panel to the form
$form.Controls.Add($buttonPanel)
## Set Default actions for the buttons
$form.AcceptButton = $okButton # ENTER = Ok
$form.CancelButton = $cancelButton # ESCAPE = Cancel
return $form
}
$LeftMargin = 25
$BottomMargin = 30
$i = 0
$form = Build-FormPanel "Please update server configurations"
foreach($param in $hash){#Where $hash is an "dictionary" of key/value pairs
$k = $param.Key
$v = $param.Value
$lblValue = New-Object System.Windows.Forms.Label
$lblValue.Text = $k+":"
$lblValue.Top = 20*$i ; $lblValue.Left = $LeftMargin; $lblValue.Width=150 ;$lblValue.AutoSize = $true
$form.Controls.Add($lblValue) # Add to Form
#
$txtValue = New-Object Windows.Forms.TextBox
$txtValue.Top = 20*$i; $txtValue.Left = 160; $txtValue.Width = 320;
$txtValue.Text = $v
$form.Controls.Add($txtValue) # Add to Form
$i++
}
$form.Topmost = $True
$form.Add_Shown( { $form.Activate(); } )
$result = $form.ShowDialog()
if($result -eq "OK")
{
$j = 0;
foreach($param in $hash){
${"txtValue_$j"}.Text
$j++
}
}
else {Write-Host "Cancel"}
Basically, this works OK to display the form and inputs. But after submission, I am unable to capture all the user inputs. Only the last input value is captured, obviously because the variables get overwritten in the loop.
How can I achieve capturing the data as described?
The issue as you have mentioned is because of the its getting overwritten.
I can give you a logical set off.
YOu can use it in a loop and in the loop , you store all the data either in array or if its dynamic then you can use arraylist by using
New-Object System.Collections.ArrayList
. But recommended is to create PSCustomObject , store in that and add that to the arraylist each time.
Finally you can get the output captured in the arraylist.
Further you can try making the arraylist Global so that it will be available for the entire script.
Hope it helps.

Powershell Write-Host showing only dataTable name instead of data

I'm trying to write a Powershell script that executes a SQL query contained in a .sql file
Function RunSQLScript ($connstring, $filePath)
{
$query = get-content $filePath;
$DTSet = New-Object System.Data.DataSet;
$Conn=New-Object System.Data.SQLClient.SQLConnection $connstring;
$Conn.Open();
try
{
$DataCmd = New-Object System.Data.SqlClient.SqlCommand;
$MyQuery = $query;
$DataCmd.CommandText = $MyQuery;
$DataCmd.Connection = $Conn;
$DAadapter = New-Object System.Data.SqlClient.SqlDataAdapter;
$DAadapter.SelectCommand = $DataCmd;
$DAadapter.Fill($DTSet) | Out-Null;
for ($i = 0; $i -lt $DTSet.Tables.Count; $i++) {
Write-Host $DTSet.Tables[$i];
}
}
finally
{
$Conn.Close();
$Conn.Dispose();
}
return $DTSet;
}
The internal Write-Host is showing the DataTable name instead of the DataRows.
If I manually create a DataSet with a DataTable in Powershell Console, Write-Host shows me the data in the DataTable rows, so I can't really figure out why it is not doing that in the previous script.
Can you give me some clues on how to show the data contained in the datatables instead of the table names?
Thank you
This piece of code was quite helpful for me, posting it here if anybody needs it.
for ($i = 0; $i -lt $DTSet.Tables.Count; $i++) {
$DTSet.Tables[$i] | format-table | out-host
}
That produces a nice table-like output on screen.

PowerShell Forms - Vertical Progress Bar

This is regarding Windows Forms in PowerShell and the System.Windows.Forms.ProgressBar.
I am looking all over and cannot find anything that allows the progress bar to fill vertically. I've considered alternatives (such as resizing a label with a background color), but I prefer to use something that already has a class if possible. I could have sworn I had seen something out there like this before, even if it wasn't a true progress bar. I am not using it to track progress, but more for CPU usage, RAM usage, and drive space for server statuses. This is for a useful GUI for a quick report of servers from a dropdown list, something where I don't have to open another complete shell session (I have enough shells open as it is between O365 eDiscovery, data analysis, and other needs). Thanks for any suggestions in advance.
Here is a very good C# answer How do I make a winforms progress bar move vertically in C#?
It overrides the CreateParams method to set the PBS_VERTICAL flag in Style.
To make it work in PowerShell you will unfortunately have to use a bit of C# code.
This works for me:
$type = #'
using System;
using System.Windows.Forms;
public class VerticalProgressBar : ProgressBar {
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.Style |= 0x04;
return cp;
}
}
}
'#
Add-Type -TypeDefinition $type -ReferencedAssemblies System.Drawing,System.Data,System.Windows.Forms
$userForm = New-Object System.Windows.Forms.Form
$userForm.Text = "$title"
$userForm.Size = New-Object System.Drawing.Size(230,300)
$userForm.StartPosition = "CenterScreen"
$userForm.AutoSize = $False
$userForm.MinimizeBox = $False
$userForm.MaximizeBox = $False
$userForm.SizeGripStyle= "Hide"
$userForm.WindowState = "Normal"
$userForm.FormBorderStyle="Fixed3D"
$progressbar = New-Object 'VerticalProgressBar'
$progressbar.Location = New-Object System.Drawing.Point(180, 50);
$progressbar.Width = 20
$progressbar.Height = 200
$userForm.Controls.Add($progressbar)
$TrackBar = New-Object 'System.Windows.Forms.TrackBar'
$TrackBar.Location = New-Object System.Drawing.Point(10, 10);
$TrackBar.Width = 200
$TrackBar.add_ValueChanged({$progressbar.Value = $this.value*10})
$userForm.Controls.Add($TrackBar)
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(10,220)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.Add_Click({$userForm.Close()})
$userForm.Controls.Add($OKButton)
$userForm.ShowIcon = $False
$userForm.Add_Shown({$userForm.Activate()})
$userForm.AcceptButton = $OKButton
[void] $userForm.ShowDialog()

Resources