PowerShell Forms - Vertical Progress Bar - winforms

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()

Related

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)

How to change height of items in ListBox in powershell?

I'm trying to set the height of items to be equal to the height of ListBox. In other words, only one item must be visible in ListBox. Right now, two items are visible.
Add-Type -AssemblyName System.Windows.Forms
[Windows.Forms.Application]::EnableVisualStyles()
# $OwnerDrawVariable = [Windows.Forms.DrawMode]::OwnerDrawVariable
# $OwnerDrawFixed = [Windows.Forms.DrawMode]::OwnerDrawFixed
$form = New-Object Windows.Forms.Form
$form.ClientSize = '400,400'
$form.text = "Form"
$form.TopMost = $false
$listBox = New-Object Windows.Forms.ListBox
$listBox.text = "listBox"
$listBox.width = 80
$listBox.height = 30
$listBox.location = New-Object Drawing.Point(70,10)
# $listBox.IntegralHeight = $false
# $listBox.DrawMode = $OwnerDrawVariable
$listBox.ItemHeight = 30
#('1','2','3') | ForEach-Object {[void] $listBox.Items.Add($_)}
$form.controls.AddRange(#($listBox))
[void]$form.ShowDialog()
I've tried changing DrawMode property as well as IntegralHeight to no avail. Any advice?
As the value name indicates, [DrawMode]::OwnerDrawFixed requires the control owner (that's you!) to explicitly draw the items on screen.
You can do so by adding an event handler to the DrawItem event property:
$listBox.add_DrawItem({
param(
[object]$sender,
[System.Windows.Forms.DrawItemEventArgs]$eargs
)
$eargs.DrawBackground()
$eargs.Graphics.DrawString($listBox.Items[$eargs.Index].ToString(), $eargs.Font, [System.Drawing.Brushes]::Black, $eargs.Bounds.Left, $eargs.Bounds.Top)
$eargs.DrawFocusRectangle()
})
$eargs.Font is inherited from $listbox.Font, so modify that if you want the drawn strings to be larger as well:
$listBox.Font = [System.Drawing.Font]::new($listBox.Font.FontFamily.Name, 18)

How Can I change the color of the Text on TabPage?

What properties need to be applied to change the forecolor and backcolor of the text on Tabpage?
See Picture:
https://imgur.com/a/Su8aSg7
Here is my Code:
$TabControl_Main = New-Object System.Windows.Forms.TabControl
$TabControl_Main.Location = New-Object System.Drawing.Size(20,550)
$TabControl_Main.Size = New-Object System.Drawing.Size(850,270)
$form_MainForm.Controls.Add($TabControl_Main)
$TabPage1 = New-Object System.Windows.Forms.TabPage
$TabPage1.Location = New-Object System.Drawing.Size(20,550)
$TabPage1.Size = New-Object System.Drawing.Size(850,270)
$TabPage1.Text = "Processes"
$TabControl_Main.Controls.Add($TabPage1)
You have to create an event and draw the area. Here is some code based on this example in c#, credits #Fun Mun Pieng.
# assign a color for each tab
$PageColor = #{0 = "lightgreen";
1 = "yellow";
2 = "lightblue"}
# define the event
$tabControl_Drawing = {
param([object]$Sender, [System.EventArgs]$e)
$Background = new-object Drawing.SolidBrush $PageColor[$e.Index]
$Foreground = new-object Drawing.SolidBrush black
$tabGraphics = $e.Graphics
$tabBounds = $e.Bounds
$tabGraphics.FillRectangle($Background,$tabBounds)
$tabTextSize = $tabGraphics.MeasureString($sender.TabPages[$e.Index].text, $e.Font)
$tabGraphics.DrawString($Sender.TabPages[$e.Index].Text,$e.Font,$Foreground,$tabBounds.Left + ($tabBounds.Width - $tabTextSize.Width) / 2,$tabBounds.Top + ($tabBounds.Height -$tabTextSize.Height) / 2 +1)
$e.DrawFocusRectangle()
}
# add the event
$TabControl_Main.add_DrawItem($tabControl_Drawing)
A little easier to use is HotTrack:
$TabControl_Main.HotTrack = $true
You will see the effect when you execute your script with powershell instead of powershell ISE.
BackColor does nothing. To use the words of MSDN:
BackColor > This member is not meaningful for this control.
edit: added the code.

DPI scaling of borderless Winforms window goes wrong

I have a PowerShell script with a borderless window and as more and more people use DPI scaling I tested my form accordingly.
Oddly it seems to scale (somewhat) well up to 150%, but with 175% the form itself (red) grows much more than the richtextbox (gray), as seen in the example below.
Any ideas on how to fix or prevent that?
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$form = New-Object System.Windows.Forms.Form
$form.Size = "200,200"
$form.FormBorderStyle = "None"
$form.BackColor = "#C15959"
$form.TopMost = $true
$form.StartPosition = "CenterScreen"
$form.AutoScalemode = "Dpi"
$form.AutoSize = $true
$form.AutoSizeMode = "GrowOnly"
$rtb = New-Object System.Windows.Forms.RichTextBox
$rtb.BorderStyle = "FixedSingle"
$rtb.BackColor = "#EDEDED"
$rtb.Anchor = "Top,Bottom,Right,Left"
$rtb.Size = "181,155"
$rtb.Location = "1,1"
$rtb.AutoSize = $true
$rtb.add_mouseclick({ $form.close()})
$form.Controls.Add($rtb)
$form.showdialog()
Form at 100% DPI scaling:
Form at 175% DPI scaling:
The solution is not to set size but clientsize for the form itself.

PowerShell WPF Printing - Can Not Get the IDocumentPaginatorSource Interface

I am trying to use WPF printing from PowerShell. I have found a simple example in VB.Net here:
'Create a new Run class, passing it the text.
' The Run class contains 1 line of text
Dim r As New Run(text)
'Create a new paragraph, passing it the new run class instance
Dim ph As New Paragraph(r)
'Create the document, passing a new paragraph
Dim doc As New FlowDocument(ph)
doc.PagePadding = New Thickness(100) 'Creates margin around the page
'Send the document to the printer
diag.PrintDocument( _
CType(doc, IDocumentPaginatorSource).DocumentPaginator, _
printCaption)
but I am unable to convert it to PowerShell. Here is my attempt:
$run = New-Object System.Windows.Documents.Run('text')
$paragraph = New-Object System.Windows.Documents.Paragraph($run)
$flowDocument = New-Object System.Windows.Documents.FlowDocument($paragraph)
$thickness = New-Object System.Windows.Thickness(100)
$flowDocument.PagePadding = $thickness
$flowDocument -is [System.Windows.Documents.IDocumentPaginatorSource]
$source = $flowDocument -as [System.Windows.Documents.IDocumentPaginatorSource]
$source.DocumentPaginator -eq $null
$printDialog = New-Object System.Windows.Controls.PrintDialog
$printDialog.PrintDocument($source.DocumentPaginator, "test")
$source.DocumentPaginator seems to be null, and an exception is raised. (The VB.Net code works)
[Edit]
Here is another attempt that failed:
Add-Type -Assembly 'ReachFramework'
$flowDocument = New-Object System.Windows.Documents.FlowDocument
$textRange = New-Object System.Windows.Documents.TextRange(
$flowDocument.ContentStart, $flowDocument.ContentEnd)
$textRange.Text = 'Text'
$xpsDocument = New-Object System.Windows.Xps.Packaging.XpsDocument(
"C:\scripts\test.xps", [System.IO.FileAccess]::ReadWrite)
$xpsDocumentWriter =
[System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter(
$xpsDocument)
$source = $flowDocument -as [System.Windows.Documents.IDocumentPaginatorSource]
$xpsDocumentWriter.Write($source.DocumentPaginator)
$xpsDocument.Close()
I was trying to use one of the XpsDocumentWriter.Write() overloads to send the FlowDocument to a printer, but it failed, $source.DocumentPaginator is null. I didn't even manage to create a .xps file and save it.
Sometimes when I get frustrated with PowerShell's language "issues", I drop back to C# which V2 makes very easy. The following prints for me:
$src = #'
public static System.Windows.Documents.DocumentPaginator
GetDocumentPaginator(System.Windows.Documents.FlowDocument flowDocument)
{
return ((System.Windows.Documents.IDocumentPaginatorSource)flowDocument).DocumentPaginator;
}
'#
Add-Type -MemberDefinition $src -Name Utils -Namespace Foo `
-ReferencedAssemblies WindowsBase,PresentationFramework,
PresentationCore
$run = New-Object System.Windows.Documents.Run('text')
$paragraph = New-Object System.Windows.Documents.Paragraph($run)
$flowDocument = New-Object System.Windows.Documents.FlowDocument($paragraph)
$thickness = New-Object System.Windows.Thickness(100)
$flowDocument.PagePadding = $thickness
$paginator = [Foo.Utils]::GetDocumentPaginator($flowDocument)
$printDialog = New-Object System.Windows.Controls.PrintDialog
$printDialog.PrintDocument($paginator, "test")
Note that you could do the same with VB code. Just use the -Language VisualBasic parameter on Add-Type.

Resources