Powershell system windows forms button

Время на прочтение
7 мин

Количество просмотров 68K

Некоторое время назад в компании была развернута терминальная ферма.
Первым делом в неё были выселены пользователи некой желтой программы.
После чего отдел поддержки желтой программы спросил меня, можно ли отсылать сообщения пользователям фермы всем сразу. XaocCPS посоветовал мне играться в сторону WPF. Нужный скрипт был написан, но его работой я неудовлетворился:
1. Надо ставить внешний компонент PowerShellPack.
2. Компонент ставиться на сервера фермы (х64) отказался.
3. Распространять такое решение из за пункта 1 всем желающим не очень удобно.

Xaegr подсказал что я могу избавиться от прослойки WPF.
Писать можно, можно даже писать красиво. Скрипт выполняется везде где есть .Net Framework — XP, Win7 и скорее всего пойдет даже на х64 серверах фермы.
Как писать — под катом.

UPD по просьбам скрипт выложен на SkyDrive, ссылка в конце

И так — ТЗ, полученное от отдела поддержки было «отправка сообщений всем пользователям терминальной фермы».
Мною ТЗ было расширено:
1. Выбор серверов.
2. Выбор пользователей.
3. Ввод текста сообщения.
4. Вставка подписи — идентификатор отправившего сообщения пользователя — виден в заголовке окна, но на него внимание обращают мало, выбрал вариант из нескольких предустановленных вариантов.
5. Защита от нечаянной отправки.

Предварительно было найдено, что в Win7 и 2008* есть команда msg, с помощью которой данные сообщения и будут отправляться, но прием сообщений надо разрешить, что и сделал простеньким regedit файлом:

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server]
"AllowRemoteRPC"=dword:00000001

К сожалению, если пользователь из списка не подключен к серверу — скрипт много и сильно ругается. Я пока не придумал как получить вывод msg, и его обрабатывать.
Скрипт по максимуму комментирован.
~15kb

# Подключаем то, с помощью чего будем рисовать нашу форму
Add-Type -assembly System.Windows.Forms

	# Все просто, массивы из списка серверов, списка пользоватей, и варианты подписи - чтобы использовать могла не только поддержка.
	$Server        = @("rdp1-1, rdp1-2, rdp1-3, rdp1-4", "rdp2-1, rdp2-2, rdp2-3, rdp2-4")
	$User          = @("*", "1c-admin1, 1c-admin2")
	$Message       = "Умолчательное сообщение"
	# По хорошему, надо бы сделать так, чтобы если умолчательное сообщение не менялось и шло нажатие на кнопку отправить, то выводилось бы предупреждение.
	# Так же по аналогии с тем что есть, тут тоже можно повесить список со стандартными сообщениями

	# Подписываемся
	$Sign          = @("С уважением, администраторы 1С", "С уважением, системные администраторы")

	# Повелось, что все функции описываются до их использования.
	# Функция отправки сообщения. Принимаем параметры, проверяем, если чекбокс включен (а он включен по умолчанию), устанавливаем параметры отправки только на консоль локалхоста, иначе проходим по списку серверов, внутри каждого сервера инициируем цикл по списку пользователей с предварительно отрезанными пробелами до и после текста(помните формат: "сервер1, сервер2, сервер3" для красивой читаемости текста), вызываем сам msg для отправки сообщения и после чего вызываем диалогов окно с подтверждением отправки и закрытием программы.
Function SendMessage {
        param ($Server, $User, $Message, $Sign)
       # Write-Host $Server, $User, $Message, $Sign
        If ($TestRunCheckBox.Checked -eq 1 ) { Write-Host $TestRunCheckBox.Checked; $Server="localhost"; $User = "Console" }
        ForEach ($Item in $Server) {
            ForEach ($UserX in $User) {
                    $UserTrim = $UserX.Trim()
                    $ServerTrim = $Item.Trim()
                    $MsgTrim = $Message.Trim()
                    $SignTrim = $SignX.Trim()
                    # Отладочный вывод, оставлен на всякий случай.
                    # Write-Host "User: $UserTrim; Server: $ServerTrim; Message: $MsgTrim; Signature: $SignTrim"
                    c:\windows\system32\msg.exe $UserTrim /Server:$ServerTrim $MsgTrim $SignTrim
                }
        }
     Confirm
    }

# Вывод окна с подтверждением
Function Confirm {
        $ConfirmWin = New-Object System.Windows.Forms.Form
        $ConfirmWin.StartPosition  = "CenterScreen"
        $ConfirmWin.Text = "Подтверждение отправки"
        $ConfirmWin.Width = 200
        $ConfirmWin.Height = 120
        $ConfirmWin.ControlBox = 0

        $ConfirmWinOKButton = New-Object System.Windows.Forms.Button
        $ConfirmWinOKButton.add_click({ $MainSendWindow.Close(); $ConfirmWin.Close() })
        $ConfirmWinOKButton.Text = "Закрыть"
        $ConfirmWinOKButton.AutoSize = 1
        $ConfirmWinOKButton.Location        = New-Object System.Drawing.Point(50,50)

        $ConfirmLabel = New-Object System.Windows.Forms.Label
        $ConfirmLabel.Text = "Сообщение было отправлено"
        $ConfirmLabel.AutoSize = 1
        $ConfirmLabel.Location  = New-Object System.Drawing.Point(10,10)

        $ConfirmWin.Controls.Add($ConfirmLabel)
        $ConfirmWin.Controls.Add($ConfirmWinOKButton)
        $ConfirmWin.ShowDialog() | Out-Null
    }

# Главное окно, по хорошему тоже стоило бы оформить в виде функции
     $MainSendWindow                = New-Object System.Windows.Forms.Form
     $ToolTip = New-Object System.Windows.Forms.ToolTip

     $ToolTip.BackColor = [System.Drawing.Color]::LightGoldenrodYellow
     $ToolTip.IsBalloon = $true
     # $ToolTip.InitialDelay = 500
     # $ToolTip.ReshowDelay = 500

     # Инициализация контролов формы
     # Кнопки и чекбокс
     $SendButton                   = New-Object System.Windows.Forms.Button
     $CloseButton                   = New-Object System.Windows.Forms.Button
     $TestRunCheckBox           = New-Object System.Windows.Forms.CheckBox

     # Текстовые поля и списки
     $ServerTextBox                = New-Object System.Windows.Forms.ComboBox
     $UserTextBox                   = New-Object System.Windows.Forms.ComboBox
     $MessageTextBox             = New-Object System.Windows.Forms.TextBox
     $SignTextBox                   = New-Object System.Windows.Forms.ComboBox

     # Подписи
     $ServerTextBoxLabel           = New-Object System.Windows.Forms.Label
     $UserTextBoxLabel              = New-Object System.Windows.Forms.Label
     $MessageTextBoxLabel        = New-Object System.Windows.Forms.Label
     $SignTextBoxLabel              = New-Object System.Windows.Forms.Label

# Описываем свойства (комментариями я еще добавлю несколько нагугленных
# интересных свойств для общего развития и чтобы далеко не бегать ;))

# Главная форма
$MainSendWindow.StartPosition  = "CenterScreen"
$MainSendWindow.Text           = "Отправка сообщения пользователям"
$MainSendWindow.Width          = 470
$MainSendWindow.Height         = 220
# несколько плюшек и обещанных красивостей
#$Win.ControlBox           = 0 # отключить кнопки свернуть, минимизацию и закрытие.
# $Win.ShowIcon             = 0
# $Win.ShowInTaskbar        = 0
# $Win.HelpButton           = 1
# авторазмер может отрабатывать если вся форма - к примеру одна кнопка "Сделать хорошо"
# $Win.Autosize             = 1
# $Win.AutoSizeMode         = "GrowAndShrink"
# стиль обрамления и шрифт.
# $Win.FormBorderStyle      = [System.Windows.Forms.FormBorderStyle]::Fixed3D
# $Win.Font                 = New-Object System.Drawing.Font("Verdana",32)

# Подписи к текстовым полям
$ServerTextBoxLabel.Location   = New-Object System.Drawing.Point(10,12)
$ServerTextBoxLabel.Text       = "Список серверов"
$ServerTextBoxLabel.Autosize     = 1

$UserTextBoxLabel.Location     = New-Object System.Drawing.Point(10,42)
$UserTextBoxLabel.Text         = "Список пользователей"
$UserTextBoxLabel.Autosize     = 1

$MessageTextBoxLabel.Location  = New-Object System.Drawing.Point(10,73)
$MessageTextBoxLabel.Text      = "Сообщение"
$MessageTextBoxLabel.Autosize  = 1
# Плюшка в виде красивой подсказки, делается другим методом вызова, поэтому идет к каждому обьекту в блоке, чтобы не теряться.
$ToolTip.SetToolTip($MessageTextBoxLabel, "Надо подписаться, а то в заголовке окна с сообщениями не видно")

$SignTextBoxLabel.Location     = New-Object System.Drawing.Point(10,103)
$SignTextBoxLabel.Text         = "Подпись"
$SignTextBoxLabel.Autosize     = 1
$ToolTip.SetToolTip($SignTextBoxLabel, "Надо подписаться, а то в заголовке окна с сообщениями не видно")

# Описание текстбокса
# Позиция
$ServerTextBox.Location        = New-Object System.Drawing.Point(140,10)
# Источник данных
$ServerTextBox.DataSource      = $Server
# Размер
$ServerTextBox.Width           = 300
# Обработка события - при смене текста в поле, присваиваем переменной новое полученное значение.
$ServerTextBox.add_TextChanged({ $Server = $ServerTextBox.Text })
# индекс порядка перехода по Tab
$ServerTextBox.TabIndex        = 1
$ToolTip.SetToolTip($ServerTextBox, "Укажите список серверов")

$UserTextBox.Location          = New-Object System.Drawing.Point(140,40)
$UserTextBox.DataSource        = $User
# Не забываем про массив
$UserTextBox.Text              = $User[1]
$UserTextBox.add_TextChanged({ $User = $UserTextBox.Text })
$UserTextBox.Width             = 300
$UserTextBox.TabIndex          = 2
$ToolTip.SetToolTip($UserTextBox, "Кому отправлять будем? (* для *всех* пользователей, по умолчанию)")

# Поле сообщения
$MessageTextBox.Location       = New-Object System.Drawing.Point(140,70)
$MessageTextBox.Text           = $Message
# По клику в поле ввода - автоматически выделяем весь текст, чтобы не надо было
# нажимать удаление
$MessageTextBox.add_click({ $MessageTextBox.SelectAll() })
$MessageTextBox.add_TextChanged( { $Message = $MessageTextBox.Text })
$MessageTextBox.Width          = 300
$MessageTextBox.TabIndex       = 3
$ToolTip.SetToolTip($MessageTextBox, "И шо мы таки хотим сказать?")

# Поле подписи - отправляемая переменная уже другая
$SignTextBox.Location          = New-Object System.Drawing.Point(140,103)
# Источник текста для подписи
$SignTextBox.DataSource        = $Sign
# А мы помним, что там массив?:)
$SignTextBox.Text              = $Sign[1]
$SignTextBox.add_TextChanged({ $SignX = $SignTextBox.Text })
$SignTextBox.Width             = 300
$SignTextBox.TabIndex          = 4
$ToolTip.SetToolTip($SignTextBox, "Страна должна знать своих героев")

# Нопка отправки.
$SendButton.Location           = New-Object System.Drawing.Point(10,150)
$SendButton.Text               = "Отправить сообщение"
# Выполняем разделения строк на массивы с разделителем запятая, вызываем функцию отправки сообщения
$SendButton.add_click({ $User  = $UserTextBox.Text.Split(","); $Server = $ServerTextBox.Text.Split(","); $SignX = $SignTextBox.Text; SendMessage $Server $User $Message $SignX} )
$SendButton.Autosize           = 1
$SendButton.TabIndex           = 5
$ToolTip.SetToolTip($SendButton, "Тыцни пимпочку")

# Прописываем блокировочный чекбокс
$TestRunCheckBox.Location      = New-Object System.Drawing.Point(200,150)
$TestRunCheckBox.Text          = "Тест"
$TestRunCheckBox.Checked       = 1
$TestRunCheckBox.AutoSize      = 1
$TestRunCheckBox.TabIndex      = 6
$ToolTip.SetToolTip($TestRunCheckBox, "Сними меня, а то работать не будет")

# Кнопочка выхода, по событию вызывает метод закрытия
$CloseButton.Location          = New-Object System.Drawing.Point(315,150)
$CloseButton.Text              = "Выйти из программы"
$CloseButton.add_click({ $MainSendWindow.Close() })
$CloseButton.Autosize          = 1
$CloseButton.TabIndex          = 7
$ToolTip.SetToolTip($CloseButton, "Пойдем ка отсюда")


# Добавляем контролы в форму и вызываем её запуск
$MainSendWindow.Controls.Add($SendButton)
$MainSendWindow.Controls.Add($TestRunCheckBox)
$MainSendWindow.Controls.Add($CloseButton)

$MainSendWindow.Controls.Add($ServerTextBox)
$MainSendWindow.Controls.Add($UserTextBox)
$MainSendWindow.Controls.Add($MessageTextBox)
$MainSendWindow.Controls.Add($SignTextBox)

$MainSendWindow.Controls.Add($ServerTextBoxLabel)
$MainSendWindow.Controls.Add($UserTextBoxLabel)
$MainSendWindow.Controls.Add($MessageTextBoxLabel)
$MainSendWindow.Controls.Add($SignTextBoxLabel)

$MainSendWindow.ShowDialog() | Out-Null

В результате получилась вот такая вот красивость
image

Скрипт могу выложить, при желании читателей, куда-то.

Не уверен, что хватит кармы публировать в профильный, поэтому выложил сюда

Исходник скрипта на GitHub’е

When it comes to working with the assembly System.Windows.Form there really is a lot of Classes available to you which allows you to add elements to your GUI Form to make it more user friendly and functional; afterall the entire purpose of creating a GUI Form is to remove the users need to type in all those commands you have coded into your Script.

Given the number of Classes is quite large I will not be covering every option in this tutorial, and instead will focus on a handful, which I hope will illustrate to you the basic principals of using these classes to create a functional GUI Form.

We’ll start with a very simple Form, and slowly build on it as I did in Part 1 to help you come to grips with the way to create your GUI Form, and make you realize that while there is a certain amount of finger exercise to come, that it is not as difficult to do as one might imagine.

So let’s get started!

Open Windows PowerShell ISE and enter the following code into the Script pane:

Code:

# Load required assemblies
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Create Form to contain elements
$Form = New-Object System.Windows.Forms.Form

# Set Form Titlebar text
$Form.Text = "Console to list Directories of chosen path"

# Set size of form
# Size(<length>,<height>) in pixels
$Form.Size = New-Object System.Drawing.Size(600,400)

# Initialize form and show it
# [void] used to suppress other messages generated by Form actions
[void] $Form.ShowDialog()

PS68.png

After typing or copy & pasting the above code into the script pane click File > Run

PS67.png

  • As you can see we now have a Blank Form which we’ll be able to build on.
  • At this build we’ve set the Forms title bar text using $Form.Text
  • Unlike in Part 1, this time we used a new assembly class to control the size of our Form (System.Drawing.Size).
  • The Size() takes integer values to determine the size of the object being drawn in the format: Size(<length>,<height>) e.g. Size(600,400)
  • Note: We’ve initialized our Form slightly differently that in Part 1
    • [void] $Form.ShowDialog()
    • [void] suppresses other messages generated by the Form. If we do not use [void], then, for example, if the user closes the Form the word «Cancel» appears in the console. We do not need that information, nor do they.

Add some elements to make the GUI Form functional

A User Input Textbox

We can add textbox to our GUI Form which takes input from the User. by adding the following code block to our Script:

Code:

# Create an Input textbox
$inputBox = New-Object System.Windows.Forms.TextBox

# Size(<length>,<height) in pixels
# .Location will position the textbox in a position relative to the
# base Form based on the Forms size(600,400)
# In this case location is 20 pixels in from Form boundary, and 50 pixels down from Form boundary
$inputBox.Location = New-Object System.Drawing.Size(20,50)

# Size(<length>,<height) in pixels
# .Size determines the actual size of the textbox
$inputBox.Size = New-Object System.Drawing.Size(150,20)

# Initialize the textbox inside the Form
$Form.Controls.Add($inputBox)

I’ve added comments to the code block to help explain each snippet, but here is a break down of what it does:

  • First we create a variable $inputBox to assign our new object to. You can name your variable whatever you wish, but it helps when later analyzing your scripts to use a descriptive name.
  • Next we assign a new object using the cmdlet New-Object and passing it our assembly Class System.Windows.Forms.TextBox
  • Now using our $inputBox variable we append the .Location method in order to assign a new Drawing object by making using of the assembly class System.Drawing.Size(<length>,<height>)
  • The System.Drawing.Size class takes two integer arguments: <length> and <height> which represent the number of pixels to draw the new textbox in from a specific location which is relative to the boundary walls of the GUI Form
  • In the code above we are instructing the textbox be drawn 20 pixels in from the left boundary wall of GUI Form and 50 pixels down from the top boundary wall of our GUI Form
  • Next we set the size of the textbox using the .Size method and assigning the System.Drawing.Size(<length>,<height>) class which takes integer values to represent the length and height (in pixels) of the object being drawn. In our case that will be 150 pixels wide by 20 pixels high)
  • Finally, we need to initialize the textbox so it can be seen on the GUI Form. We do this by the snippet: $Form.Controls.Add($inputBox) which adds the Control element we pass to it (in this case our $inputBox variable which is storing our object.)

So now we need to add this to our current Script in the Windows PowerShell ISE Script Pane:

PS69.png

If we click File > Save, then File > Run we get:

PS70.png

It would be great to add a label to help the User understand what to do with that input field. To do that we simply add, the following code block before we initialize the $inputBox

Code:

# Create Instruction Label for inputBox
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Type Path here: e.g C:\Users"
$Label.Location = New-Object System.Drawing.Size(20,30)
$Label.BackColor = "Transparent"
$Label.AutoSize = $true

# Initialize Label
$Form.Controls.Add($Label)

PS71.png

When we run our updated script we get:

PS72.png

The label helps user understand the purpose of the input field

  • The message for the user was placed onto the Label using $Label.Text
  • We positioned the Label using $Label.Location = New Object System.Drawing.Size(20,30) i.e. 20 pixels in from left boundary wall of the GUI Form and 30 pixels down from top boundary wall of the GUI Form
  • Just as in Part 1 of this tutorial we made the Label itself transparent, using $Label.BackColor = «Transparent» so as not to distract the eye from the input field.
  • We initialized the $Label so it can be seen on the GUI Form using $Form.Controls.Add($Label) to pass our $Label variable storing the object via Controls.Add(<variable) to our $Form.

Supply feedback using an Output Textbox

Now we want to add an Output Textbox which will provide information to the User.

As the information may contain more than a single line of information we will make our new Textbox a multi-line field. This will be done by using the following code block:

Code:

# Create an Output textbox, 10 pixels in from Form Boundary and 150 pixels down
# As we want a multiline output set textbox size to 565 px x 200 px
# .Multiline declares the textbox is multi-line
$outputBox = New-Object System.Windows.Forms.TextBox
$outputBox.Location = New-Object System.Drawing.Size(10,150)
$outputBox.Size = New-Object System.Drawing.Size(565,200)
$outputBox.MultiLine = $True

# Initialize the textbox inside the Form
$Form.Controls.Add($OutputBox)

  • So we start by creating a variable $outputBox which we can name whatever we like as before. We will assign this variable a new Textbox object by using the cmdlet New-Object and passing the assembly class System.Windows.Forms.Textbox to it.
  • Next we position the textbox as we did before with our input textbox.
  • Next, also as before with the input textbox, we set its size. In this example, it will be 565 pixels wide by 200 pixels high.
  • Now we declare that our textbox will be Multi-lined. To do this we use the boolean .MultiLine and as it is a boolean we assign $true to confirm its to be a multi-lined textbox who’s information is stored inside the variable $outputBox
  • Finally, we need to initialize the textbox so it can be seen on the GUI Form

Now we add this to our current Script:

PS73.png

Then we save the changes, and click File > Run to get:

PS74.png

So now out GUI Form has a Textbox for the User to input data, and an Output Textbox that is multi-lined to report back data to the User. But how do we get the User input results to the Output Textbox?

Add an Action Button to GUI Form

To get the User input activated we can use a Button element which the User can click to start processing what they typed into the Input field, so a result can be displayed into the Output field.

To add this button we will use the following code block:

Code:

# Add a Button which can be used to generate an action from our textboxes
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(400,30)

# Button size(length>,<height>) in pixels
$Button.Size = New-Object System.Drawing.Size(110,80)

# Label the button
$Button.Text = "Click to view Directories"

# Declare the action to occur when button clicked
$Button.Add_Click( { GetDirectories } )

# Initialize the button inside the Form
$Form.Controls.Add($Button)

  • So as before we start by creating a variable to assign our new button object to, and as before we can name that variable whatever we like. In this example I used $Button for the variable and then used the cmdlet New-Object and then the assembly Class System.Windows.Forms.Button
  • Then we position the button on the GUI Form using the .Location method appended to our variable. So the button will be 400 pixels from the left boundary wall of our GUI Form and 30 pixels down from the top boundary wall of our GUI Form.
  • Now we set the size of the button by appending the .Size method to our variable then assigning the new object obtained by using the assembly Class System.Drawing.Size(<length>,height>)
  • We add some text on our Button using $Button.Text = <String> which in this example is an instruction to tell User to click the button to view the Directories in the Path they just typed into their Input field.
  • Next we need to code what action to take when the button is clicked. To do this we append the .Add_Click to our $Button variable so it knows the action to detect is the button being clicked, then we provide an Function to go seek, which contains the instructions on what action to take when button is clicked.
    • Note: This function does not exist yet.
  • Finally, we need to initialize our Button so it can be seen on the GUI Form

Now we add this code block to our Script, then save the changes:

PS75.png

After saving the changes we click File > Run to get:

PS76.png

Our GUI Form now has an Input & Output textbox, and a Button

Add a Function to process input, and show output by using Button Click action

The Form is almost ready now, but without a Function nothing will happen if the User typed a path into the Input Field, then clicked the button.

Functions are a code block normally placed at the top of a Script, or at least before the elements they are designed to provide actions to, in this case, our input, output and button.

Take a look at the code block for a Function that will bring our GUI Form to life:

Code:

# Create a Function to make use of textboxes
function GetDirectories {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Variable to store results of actioning the Input
  $Result = $Input | Get-ChildItem -Directory  | Out-String

  # Assign Result to OutputBox
  $outputBox.Text = $Result
}
  • Our Function is named GetDirectories { }
  • The parameters we will assign this function are the variables we created for our GUI Form, and some cmdlets we will use to generate an action.
  • So we start by declaring a function: function GetDirectories {
  • Now we create a new variable $Input that will have the User’s input assigned to it. To get the User’s input we call the .Text method by appending it to the $inputBox variable which is where the User is typing their data.
  • Now we use the cmdlet Set-Location <variable> which in this example is our new $Input variable that stores the path the User typed into the $inputBox. This instruction changes the current directory to the directory the User chose to type into their textbox.
  • Now we create another new variable $Result which will ultimately store the data gathered by processing the $Input we piped to the Get-ChildItem cmdlet then filter result using parameter -Directory which only supplies directories and not all other files, then piped to the cmdlet Out-String
  • To get the information stored in our $Result variable to the $outputBox we append the method .Text to our $outputBox and then assign the $Result to that.
  • Then we close our function with }

Let’s add this code block to our Script:

PS77.png

After saving the change and then clicking File > Run, our GUI Form now has life.

Example: User types C:\Windows\System32 into the Input field & clicks button

PS78.png

Have you spotted the problem yet?

No, its not that ugly formatting on the Sub-directory names (although we could tidy that up too.) Click inside the Output textbox then use your down arrow. As you get to last entry viewable more content will begin to appear. Our Textbox for the output is not big enough.

Now we could increase the size, but how can we possibly guess what is the correct size for a user; after all, we do not know if they’ve added their own custom sub-folders or not. What about using formatting to create two columns or more. Well that could work, but our textbox is not wide enough to avoid truncation of the result (you know, those ‘s).

What about adding a scrollbar? That does two things for us:

  1. It makes it very clear to the user they may need to scroll down to view all content, and
  2. It makes it a lot easier to actually scroll as now they can use their mouse.

Add a scrollbar to textbox

We know its our $outputBox than needs the ability to scroll, so that means we’ll need to edit the block of code relating to this textbox.

We know when we want to add to an object (in this case $outputBox) we need to append what we create to that object.

So this is how easy it is to fix our problem:

Code:

$outputBox.Scrollbars = "Vertical"


Add that to our Script:

PS79.png

Save the edit, then click File > Run and we get this if we enter C:\Windows and click the button:

PS80.png

So at this point you have created your own filtered version of File Explorer. Filtered, because you are only displaying sub-folders of a path you enter.

If you wanted to filter it to show files and not sub-folders, then you’d change this code snippet: $Result = $Input | Get-ChildItem -Directory | Out-String

  • To view files only, and not sub-folders:

Code:

$Result = $Input | Get-ChildItem -File | Out-String

  • To view files and sub-folders:

Code:

$Result = $Input | Get-ChildItem | Out-String

Drag & Drop Feature

Something a lot of users love in Windows is the ability to drag and drop files.

For example, people drag an executable to their Command Prompt to quickly run it.

You can offer this feature in your GUI Form in PowerShell too.

To illustrate this I’m going to redesign our GUI Form, and create a left and right pane. The left pane will contain our File Explorer, and the right pane will contain a Listbox. You’ll be able to drag en element from the left pane to the Listbox in the right pane which will record the elements full path into a variable which will also be piped to a text file that will be saved to the Desktop.

So let’s get to the coding!

Code:

# Load required assemblies
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Create a Function to make use of textboxes
function GetDirectories {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Variable to store results of actioning the Input
  $Result = $Input | Get-ChildItem | Out-String

  # Assign Result to OutputBox
  $outputBox.Text = $Result
}

# Create Form to contain elements
$Form = New-Object System.Windows.Forms.Form

# Set Form Titlebar text
$Form.Text = "Console to list Files & Directories of chosen path and Drag & Drop to Listbox"

# Set size of form
# Size(<length>,<height>) in pixels
$Form.Size = New-Object System.Drawing.Size(800,600)

# Create an Input textbox
$inputBox = New-Object System.Windows.Forms.TextBox

# Size(<length>,<height) in pixels
# .Location will position the textbox in a position relative to the
# base Form based on the Forms size(800,600)
# In this case location is 20 pixels in from Form boundary, and 50 pixels down from Form boundary
$inputBox.Location = New-Object System.Drawing.Size(20,50)

# Size(<length>,<height) in pixels
# .Size determines the actual size of the textbox
$inputBox.Size = New-Object System.Drawing.Size(300,20)

# Create Instruction Label for inputBox
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Type Path here: e.g C:\Users"
$Label.Location = New-Object System.Drawing.Size(20,30)
$Label.BackColor = "Transparent"
$Label.AutoSize = $true

# Initialize Label
$Form.Controls.Add($Label)

# Initialize the textbox inside the Form
$Form.Controls.Add($inputBox)

# Create an Output textbox, 10 pixels in from Form Boundary and 150 pixels down
# As we want a multiline output set textbox size to 440 px x 400 px
# .Multiline declares the textbox is multi-line
# Declare vertical scrollbars
$outputBox = New-Object System.Windows.Forms.TextBox
$outputBox.Location = New-Object System.Drawing.Size(10,150)
$outputBox.Size = New-Object System.Drawing.Size(440,400)
$outputBox.MultiLine = $True
$outputBox.ScrollBars = "Vertical"

# Initialize the textbox inside the Form
$Form.Controls.Add($OutputBox)

# Add a Button which can be used to generate an action from our textboxes
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(20,90)

# Button size(length>,<height>) in pixels
$Button.Size = New-Object System.Drawing.Size(200,20)

# Label the button
$Button.Text = "Click to view Files `&& Directories"

# Declare the action to occur when button clicked
$Button.Add_Click( { GetDirectories } )

# Initialize the button inside the Form
$Form.Controls.Add($Button)

# Initialize form and show it
# [void] used to suppress other messages generated by Form actions
[void] $Form.ShowDialog()


Our edited Form now lists Files and Directories of the Path entered by user.

Note: I’ve left room on the right side for the Listbox yet to be created, which is what we’ll drag and drop to.

If above code and enter C:\Windows\System32 then click the button we get:

PS81.png

I will not explain the above again, as apart from some minor manipulation of the original Script to relocate, resize and re-label items, everything ought to be self-explanatory.

Add the listbox

Adding a listbot is no different to adding a textbox. Take a look at the code:

Code:

# Create a Listbox to drag items to
$listBox = New-Object Windows.Forms.ListBox
$listBox.Location = '480,100'
$listBox.Size = New-Object System.Drawing.Size(295,450)
$listBox.Anchor = (
  [System.Windows.Forms.AnchorStyles]::Bottom -bor
  [System.Windows.Forms.AnchorStyles]::Left -bor
  [System.Windows.Forms.AnchorStyles]::Right -bor
  [System.Windows.Forms.AnchorStyles]::Top
)
$listBox.IntegralHeight = $False
$listBox.AllowDrop = $True

# Initialize Listbox
$form.Controls.Add($listBox)

  • Most of this will be familiar to you.
  • We’ve used the assembly Class Windows.Forms.ListBox
  • The only subtle difference here is the use of the parameter -bor which stands for Bitwise OR (inclusive) in conjunction with specific borders which are piped via the assembly Class [System.Windows.Forms.AnchorStyles].

    There are five choices for this class. Top, Bottom, Left, Right & None.

    The control (e.g. Listbox) is anchored to the Bottom, left, right and top.

    If I instead chose None then the control would not be anchored to any border.

    While this was not truly needed in this code, I’ve shown it as an alternate way to position a control on your GUI Form.

  • $listBox.IntegralHeight = $False

    $True indicates that the text box resizes itself to display only complete items (default). $False indicates that the text box does not resize itself even if the item is too tall to display completely.

    The IntegralHeight property relates to the height of the text box, just as the AutoSize property relates to the width of the text box.

    If IntegralHeight is $True, the text box automatically resizes when necessary to show full rows. If False, the text box remains a fixed size; if items are taller than the available space in the text box, the entire item is not shown.

  • $listBox.AllowDrop = $True — This boolean allows us to drop a dragged item into the ListBox

So let’s add our Listbox code block to the Script:

PS82.png

Now save the changes, then click File > Run and we get:

PS83.png

Label the listbox

We can add a descriptive label to our Listbox to help the User understand its purpose, by using the code block:

Code:

# Create Instruction Label for Listbox
$Label2 = New-Object System.Windows.Forms.Label
$Label2.Text = "Drag files or directories from Output field and drop here"
$Label2.Location = New-Object System.Drawing.Size(480,80)
$Label2.BackColor = "Transparent"
$Label2.AutoSize = $true

# Initialize Label2
$Form.Controls.Add($Label2)


Add this code to our Script:

PS84.png

Save the changes, then click File > Run to get:

PS85.png


Add a Button & Checkbox for our Listbox


Now we want to add a Button that when clicked will produce a text file saved to the Desktop of the contents of the ListBox.

We will also offer a checkBox that if checked will clear the ListBox once the button has been activated.

Take a look at the following code blocks to achieve this:

Code:

# Add a button for the Listbox
$Button2 = New-Object System.Windows.Forms.Button
$Button2.Location = '480,30'
$Button2.Size = New-Object System.Drawing.Size(180,20)
$Button2.Text = "Save as Listbox.txt on Desktop"

# Initialize Button2
$Form.Controls.Add($Button2)

# Add a checkbox to allow User to clear listbox if desired
# Will clear if checked and button is clicked
$Checkbox = New-Object Windows.Forms.Checkbox
$Checkbox.Location = '680,30'
$Checkbox.AutoSize = $True
$Checkbox.Text = "Clear Listbox"

# Initialize checkbox
$Form.Controls.Add($Checkbox)


Now add this code to our Script:

PS86.png

Save the changes, then click File > Run to get:

PS87.png

The easy bit is over — Let’s add a little colour to our Form to help highlight buttons, and differentiate from the OutputBox and ListBox fields (both of which are a TextBox). While we are at it let’s give the user some filter options to select all files, or a particular file type to view from the folder path they select.

To do all this we will make use of the BackColor and ForeColor methods with the listBox and outputBox and button variables. For example:

Code:

$listBox.BackColor = "#9a94be"
$listBox.ForeColor = "#000000"


This sets the BackColor of our listBox to #9a94be (a lilac shade) and the ForeColor to #000000 which is ‘black‘ — So the field is lilac and the font that appears in the listBox will be black.

We can set the font face, size and style too. For example:

Code:

$listBox.Font = "Microsoft Sans Serif,10"


This sets the font face for our listBox to Microsoft Sans Serif. The size is 10 pt.

I did not do it here, but if you wanted to, after the size you could add ,style=bold:

Code:

$listBox.Font = "Microsoft Sans Serif,10,style=bold"


This would make the 10 pt Microsoft Sans Serif font appear in bold within the listBox.

We can add buttons for file types to our Form using code blocks like:

Code:

$button2 = New-Object system.windows.Forms.Button
$button2.BackColor = "#000000"
$button2.Text = "EXE"
$button2.ForeColor = "#ffffff"
$button2.Width = 60
$button2.Height = 30
$button2.location = new-object system.drawing.point(90,100)
$button2.Font = "Microsoft Sans Serif,12"

This creates a black button, with EXE on the button in a white font color, located 90 pixels in from the left and 100 pixels down from the top of form. The button itself is 60 pixels wide x 30 pixels high.

Each of these file type buttons needs its own function to allow it to instruct the computer what to do when clicked. For example:

Code:

# All Executable files in selected path explored
Function ExeFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.exe
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.exe | Out-String
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
}

We need to call our Function for the buttons. This is easy; for example:

Code:

$button2.Add_Click({ ExeFileExplorer })

When $button2 is clicked it calls the Function ExeFileExplorer which controls the actions to take place when button is clicked. In this case it will filter the results to display any executable file in the filepath supplied by the User into out $outputBox

So after these modifications and other similar modifications for other control items in the form we end up with a form looking like the following:

PS88.png

Event Handlers

So we have a few bells now, but still no whistles. We need to add some event handlers in order to make it possible to drag items from $outputBox (the left pane) to our $listBox (the right pane).

There are three ways to approach this, but for this we’ll use a relatively pain free method. I’ll go into more advanced details in a later tutorial.

For our basic form simple is best to achieve the goal of Drag & Drop capabilities.

I’ll make use of three events — MouseDown, DragEnter, DragDrop — and some Functions to inject some life into our Form. We also need to declare a new boolean to be true, to be able to make this work.

Boolean

To be able to drag and drop anything into your ListBox, we first need to declare true a boolean to allow DragDrop to even work. To do this we simply need to add one more line to our $listBox properties:

Code:

$listBox.AllowDrop = $true

This makes use of the boolean AllowDrop to declare that any item dragged can be dropped into the ListBox.

MouseEventHandler MouseDown coupled with DragDropEffects

In order to be able to drag the text contents of our $outputBox we make use of the [System.Windows.Forms.MouseEventHandler] coupled with [System.Windows.Forms.DragDropEffects]. Again this is a nice tidy code block to add to our code:

Code:

<#
   ------------------------------------------------------------
   OutputBox Event Handler
   Allows 1 or more items to be selected and dragged to listBox
   ------------------------------------------------------------ #>
$outputBox_MouseDown = [System.Windows.Forms.MouseEventHandler] {
  $outputBox.DoDragDrop($outputBox.Text,[System.Windows.Forms.DragDropEffects]::Copy)
}

  • We declare our MouseDown event by appending _MouseDown to the $outputBox variable.
  • This is event is assigned as a MouseEventHandler by adding = [System.Windows.Forms.MouseEventHandler] { }
  • The action to take place on MouseDown [ i.e. left mouse button pressed and held as you drag ] is placed inside the { } braces.
  • We append the method: DoDragDrop() to our $outputBox then pass two arguments through this method.

(1) SoutputBox.Text instructs that the text inside the TextBox $outputBox is the focus of the action to be taken.

(2) The second argument is the action to take. In this case we parse the Text contents of the TextBox $outputBox through the [System.Windows.Forms.DragDropEffects] class and instruct that it is to be copied :: Copy. Ultimately it will be copied to our ListBox, but first we need to provide Event Handlers for our ListBox and some functions to deal with the copied text data.

Drag & Drop requires two event handlers: The first deals with the Effect — DragEventHandler DragEnter for the ListBox

We made it possible to drop a dragged item into our ListBox using the boolean AllowDrop = $true.

If we left that as is, and provided no Event Handler to deal with our MouseDown then you could drag items from anywhere on your computer to the ListBox, however, that is not the plan with this project. We already have a TextBox that stored the data we want to drag to our ListBox.

Thus we need to create some Event Handlers for the ListBox to reflect this.

First, we want to handle what happens after we press and hold our left mouse button down and drag the cursor from the TextBox to our ListBox [i.e. what happens when we enter the ListBox with our mouse cursor ].

Code:

<#
   ----------------------------------------------------------------------------------
   ListBox Event Handlers
   Sets the only location a dragged item can enter.  In this case that is the ListBox
   ----------------------------------------------------------------------------------  #>
$listBox_DragEnter = [System.Windows.Forms.DragEventHandler] {
  $_.Effect = $_.AllowedEffect
}
  • As explained in the comments within this code block, when we append _DragEnter to out ListBox $listBox and assign [System.Windows.Forms.DragEventHandler] class to it, then declare that this.Effect = this.AllowedEffect (remember in PowerShell this is represented by $_) we are instructing the computer to only allow a dragged item from our TextBox $outputBox to be dropped into the ListBox $listBox.

When you run this program you’ll see as you drag from the TextBox the mouse cursor will remain a Circle with a diagonal line, until it Enters inside the ListBox then the cursor will change and the user will know they can release their mouse button to drop the dragged content into the ListBox.

  • The AllowedEffect in this case is to Copy the Text from our $outputBox into our $listBox which as you will recall you set with the previous MouseEventHandler MouseDown coupled with DoDragDrop and [System.Windows.Forms.DragEventHandler]::Copy.

Drag & Drop requires two event handlers: The second deals with the Drop itself — DragEventHandler DragEnter for the ListBox

Code:

<#
   ------------------------------------------------------------------------
   > Set how to drop selected item(s) into ListBox
   > Makes use of [System.IO.File] to read each line
     of a file, then add each line to the ListBox.

     Required to prevent multiple items from a multiline TextBox
     appearing as one long line inside ListBox, rather than a list of items
     ---------------------------------------------------------------------- #>
$listBox_DragDrop = [System.Windows.Forms.DragEventHandler] {
 
  # Read Temorary File back into ListBox
  [string[]] $lines =  [System.IO.File]::ReadAllLines("$env:USERPROFILE\Desktop\FilePath.txt")
  [string] $line
 
  foreach ($line in $lines) {
    $listBox.Text = $listBox.Items.Add($line)
  }
  # Clear OutputBox
  $outputBox.Clear()

  # Delete Temporary File
  Remove-Item "C:\Users\Regedit32\Desktop\FilePath.txt"
}
  • We declare _DragDrop and assign it as a [System.Windows.Form.DragEventHandler]
  • Now we pass our instructions between the { } braces. In this case we’ll be making use of Functions we have called on. I’ll show these next.
  • [String[]] $lines creates a String array called $lines
  • = [System.IO.File] assigns an assembly class System Input/Output File
  • ::ReadAllLines instructs that the file being passed as the ReadAllLines() method will have all lines of text in it read
  • («$env:USERPROFILE\Desktop\FilePath.txt») is the argument being passed through the ReadAllLines() method which in this case is pointing a temporary file created on the Users Desktop named FilePath.txt
  • [string] $line declares a new String variable $line
  • foreach ($line in $lines) { $listBox.Text = $listBox.Items.Add($line) } is a conditional loop which continues until there are no more new lines of text in our file $FilePath.txt. For each new line in FilePath.txt assign that line of text to our [String] $line. Now add this $line to our ListBox.

Without this snippet of code when we copy the data from $outputBox to $listBox, all lines would appear as a single long line in our ListBox. Using this Array conditional loop approach allows us to take one line at a time and add it to the ListBox.

  • $outputBox.Clear() instructs in this case to clear the TextBox $outputBox of its contents once the conditional loop has completed.
  • Remove-Item «C:\Users\Regedit32\Desktop\FilePath.txt» will delete the temporary file on User’s Desktop named FilePath.txt when the actions of the conditional loop have completed and thus populated our dragged text from the TextBox to our ListBox.

Note: You’ll notice if you run this Script the TextBox $outputBox contains a list of names of a particular file extension the User chose, for example regedit.exe

However, after we drag this list to our ListBox that Text data miraculously changes to a full file path to the file, e.g. C:\Windows\regedit.exe

This occurs because of another conditional loop coded earlier that is contained in each of the supporting Functions for the file type the User chooses to explore.

I will not re-explain this here as (1) it is very similar to the conditional loop already explained above, and (2) the Function its included in while supporting the clicking of the EXE button in our Form, is simply using standard snippets of code explained in previous Tutorials. If you need help understanding the support functions in this Script ask and I’ll go through it step by step.


Remove and/or Initialize these control event handlers

As one last piece of house maintenance, we need to add our event handlers to the Form, and also remove them as the Form is closed ( Disposed() ).

Code:

$form_FormClosed = {
  try {
    $Form.remove_FormClosed($Form_Cleanup_FormClosed)
    $outputBox.remove_MouseDown($outputBox_MouseDown)
    $listBox.remove_DragEnter($listBox_DragEnter)
    $listBox.remove_DragDrop($listBox_DragDrop)
  }
  catch [Exception] {}
}
#Initialize Events
$Form.Add_Click($button6_Click)
$button1.Add_Click($button1_Click)
$button2.Add_Click($button2_Click)
$button3.Add_Click($button3_Click)
$button4.Add_Click($button4_Click)
$button5.Add_Click($button5_Click)
$button6.Add_Click($button6_Click)
$outputBox.Add_MouseDown($outputBox_MouseDown)
$listBox.Add_DragEnter($listBox_DragEnter)
$listBox.Add_DragDrop($listBox_DragDrop)

$Form.Add_FormClosed($Form_FormClosed)

Again, I’ve explained these techniques in previous tutorials, so I will not repeat myself, but as you can see in the code block above, you do need to add each event handler and remove them too for Form Opening and Closure.

If you need help understanding this code block, just ask and I’ll give a detailed break down of what its doing.

The Full Script Code is below:

Code:

<#
__          ___           _                  __  ___  ______                                            
\ \        / (_)         | |                /_ |/ _ \|  ____|                                            
\ \  /\  / / _ _ __   __| | _____      _____| | | | | |__ ___  _ __ _   _ _ __ ___  ___   ___ ___  _ __ ___
  \ \/  \/ / | | '_ \ / _` |/ _ \ \ /\ / / __| | | | |  __/ _ \| '__| | | | '_ ` _ \/ __| / __/ _ \| '_ ` _ \
   \  /\  /  | | | | | (_| | (_) \ V  V /\__ \ | |_| | | | (_) | |  | |_| | | | | | \__ \| (_| (_) | | | | | |
    \/  \/   |_|_| |_|\__,_|\___/ \_/\_/ |___/_|\___/|_|  \___/|_|   \__,_|_| |_| |_|___(_)___\___/|_| |_| |_|


PowerShell Script Repository: https://www.windows10forums.com/articles/categories/powershell-scripts.8/
Author: Regedit32
#>

Add-Type -AssemblyName System.Windows.Forms

# All file types explored
Function AllFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.*
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.* | Out-String
 
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
}

# All Executable files in selected path explored
Function ExeFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.exe
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.exe | Out-String
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
}

# All DLL files in seleced path explored
Function DllFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.dll
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.dll | Out-String
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
}


# All SYS files of selected path explorered
Function SysFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.sys
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.sys | Out-String
  $str = Get-ChildItem -Filter *.sys
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
 
  Return $str
}

# All TXT files of selected path explored
Function TxtFileExplorer {

  # Variable to store what user types into Input textbox
  $Input = $inputBox.Text

  # Set path to user's input
  Set-Location $Input

  # Create Temporary File
  foreach ($item in $outputBox) {
    # Set filepaths
    $str = Get-ChildItem -Filter *.txt
  }
  $outputBox.Text = $str.FullName
  # Save Temporary file to Desktop
  $outputBox.Text.Split() | Out-File $env:USERPROFILE\Desktop\FilePath.txt -Append
  $outputBox.Clear()

  # Variable to store results of actioning the Input
  $Result = Get-ChildItem -Name $Input -Filter *.txt | Out-String
 
  # Assign Result to OutputBox
  $outputBox.Text = $Result
}

$Form = New-Object system.Windows.Forms.Form
$Form.Text = "Drag & Drop Files to listBox"
$Form.TopMost = $true
$Form.Width = 800
$Form.Height = 600

$listBox = New-Object system.windows.Forms.ListBox
$listBox.BackColor = "#9a94be"
$listBox.ForeColor = "#000000"
$listBox.Size = New-Object System.Drawing.Size(360,404)
$listBox.location = new-object system.drawing.point(400,137)
$listBox.Font = "Microsoft Sans Serif,10"
$listBox.AllowDrop = $true

$label2 = New-Object system.windows.Forms.Label
$label2.Text = "Drag `&& Drop Files or Directories Here"
$label2.AutoSize = $true
$label2.Width = 360
$label2.Height = 20
$label2.location = new-object system.drawing.point(400,100)
$label2.Font = "Microsoft Sans Serif,14"

$checkBox = New-Object system.windows.Forms.CheckBox
$checkBox.Text = "Clear "
$checkBox.AutoSize = $true
$checkBox.Width = 95
$checkBox.Height = 30
$checkBox.location = new-object system.drawing.point(710,60)
$checkBox.Font = "Microsoft Sans Serif,12,style=Bold"

# Create an Output textbox, 10 pixels in from Form Boundary and 150 pixels down
# As we want a multiline output set textbox size to 360 px x 400 px
# .Multiline declares the textbox is multi-line
# Declare vertical scrollbars
$outputBox = New-Object System.Windows.Forms.TextBox
$outputBox.Location = New-Object System.Drawing.Size(20,140)
$outputBox.Size = New-Object System.Drawing.Size(360,400)
$outputBox.MultiLine = $true
$outputBox.ScrollBars = "Vertical"
$outputBox.Font = "Microsoft Sans Serif,10"

# Buttons
$button1 = New-Object system.windows.Forms.Button
$button1.BackColor = "#000000"
$button1.Text = "*.*"
$button1.ForeColor = "#ffffff"
$button1.Width = 60
$button1.Height = 30
$button1.location = new-object system.drawing.point(20,100)
$button1.Font = "Microsoft Sans Serif,12"

$button2 = New-Object system.windows.Forms.Button
$button2.BackColor = "#000000"
$button2.Text = "EXE"
$button2.ForeColor = "#ffffff"
$button2.Width = 60
$button2.Height = 30
$button2.location = new-object system.drawing.point(90,100)
$button2.Font = "Microsoft Sans Serif,12"

$button3 = New-Object system.windows.Forms.Button
$button3.BackColor = "#000000"
$button3.Text = "DLL"
$button3.ForeColor = "#ffffff"
$button3.Width = 60
$button3.Height = 30
$button3.location = new-object system.drawing.point(160,100)
$button3.Font = "Microsoft Sans Serif,12"

$button4 = New-Object system.windows.Forms.Button
$button4.BackColor = "#000000"
$button4.Text = "SYS"
$button4.ForeColor = "#ffffff"
$button4.Width = 60
$button4.Height = 30
$button4.location = new-object system.drawing.point(230,100)
$button4.Font = "Microsoft Sans Serif,12"

$button5 = New-Object system.windows.Forms.Button
$button5.BackColor = "#000000"
$button5.Text = "TXT"
$button5.ForeColor = "#ffffff"
$button5.Width = 60
$button5.Height = 30
$button5.location = new-object system.drawing.point(300,100)
$button5.Font = "Microsoft Sans Serif,12"

$button6 = New-Object system.windows.Forms.Button
$button6.BackColor = "#4d194b"
$button6.Text = "Click to save as listBox.txt to Desktop"
$button6.ForeColor = "#ffffff"
$button6.Width = 300
$button6.Height = 30
$button6.location = new-object system.drawing.point(400,60)
$button6.Font = "Microsoft Sans Serif,12"

# Declare the action to occur when buttons clicked
$button1.Add_Click({ AllFileExplorer })
$button2.Add_Click({ ExeFileExplorer })
$button3.Add_Click({ DllFileExplorer })
$button4.Add_Click({ SysFileExplorer })
$button5.Add_Click({ TxtFileExplorer })
$button6.Add_Click($button6_Click)

# Input Box
$inputBox = New-Object system.windows.Forms.TextBox
$inputBox.Width = 360
$inputBox.Height = 20
$inputBox.location = new-object system.drawing.point(20,40)
$inputBox.Font = "Microsoft Sans Serif,10"

# Labels
$label1 = New-Object system.windows.Forms.Label
$label1.Text = "  Type Directory View (e.g. C:\Windows)  "
$label1.BackColor = "#b41c1f"
$label1.AutoSize = $true
$label1.ForeColor = "#ffffff"
$label1.Width = 360
$label1.Height = 30
$label1.location = new-object system.drawing.point(20,10)
$label1.Font = "Microsoft Sans Serif,14"

$label3 = New-Object system.windows.Forms.Label
$label3.Text = "Select File Extension to view"
$label3.AutoSize = $true
$label3.Width = 360
$label3.Height = 30
$label3.location = new-object system.drawing.point(20,70)
$label3.Font = "Microsoft Sans Serif,14"

# Add controls to form
$Form.SuspendLayout()
$Form.Controls.Add($button1)
$Form.Controls.Add($button2)
$Form.Controls.Add($button3)
$Form.Controls.Add($button4)
$Form.Controls.Add($button5)
$Form.Controls.Add($button6)
$Form.Controls.Add($checkBox)
$Form.Controls.Add($label1)
$Form.Controls.Add($label2)
$Form.Controls.Add($label3)
$Form.Controls.Add($inputBox)
$Form.Controls.Add($outputBox)
$Form.Controls.Add($listBox)
$Form.ResumeLayout()
# Event handlers
$button6_Click = {
  foreach ($item in $listBox) {
    # Save listBox to Desktop
    $listBox.Items | Out-File $env:USERPROFILE\Desktop\ListBox.txt -Append
  }
  if ($checkBox = 'Checked') {
    $listBox.Items.Clear()
  }
}

<#
   ------------------------------------------------------------
   OutputBox Event Handler
   Allows 1 or more items to be selected and dragged to listBox
   ------------------------------------------------------------ #>
$outputBox_MouseDown = [System.Windows.Forms.MouseEventHandler] {
  $outputBox.DoDragDrop($outputBox.Text,[System.Windows.Forms.DragDropEffects]::Copy)
}

<#
   ----------------------------------------------------------------------------------
   ListBox Event Handlers
   Sets the only location a dragged item can enter.  In this case that is the ListBox
   ----------------------------------------------------------------------------------  #>
$listBox_DragEnter = [System.Windows.Forms.DragEventHandler] {
  $_.Effect = $_.AllowedEffect
}

<#
   ------------------------------------------------------------------------
   > Set how to drop selected item(s) into ListBox
   > Makes use of [System.IO.File] to read each line
     of a file, then add each line to the ListBox.

     Required to prevent multiple items from a multiline TextBox
     appearing as one long line inside ListBox, rather than a list of items
     ---------------------------------------------------------------------- #>
$listBox_DragDrop = [System.Windows.Forms.DragEventHandler] {
 
  # Read Temorary File back into ListBox
  [string[]] $lines =  [System.IO.File]::ReadAllLines("$env:USERPROFILE\Desktop\FilePath.txt")
  [string] $line
 
  foreach ($line in $lines) {
    $listBox.Text = $listBox.Items.Add($line)
  }
  # Clear OutputBox
  $outputBox.Clear()

  # Delete Temporary File
  Remove-Item "$env:USERPROFILE\Desktop\FilePath.txt"
}

$form_FormClosed = {
  try {
    $Form.remove_FormClosed($Form_Cleanup_FormClosed)
    $outputBox.remove_MouseDown($outputBox_MouseDown)
    $listBox.remove_DragEnter($listBox_DragEnter)
    $listBox.remove_DragDrop($listBox_DragDrop)
  }
  catch [Exception] {}
}
#Initialize Events
$Form.Add_Click($button6_Click)
$button1.Add_Click($button1_Click)
$button2.Add_Click($button2_Click)
$button3.Add_Click($button3_Click)
$button4.Add_Click($button4_Click)
$button5.Add_Click($button5_Click)
$button6.Add_Click($button6_Click)
$outputBox.Add_MouseDown($outputBox_MouseDown)
$listBox.Add_DragEnter($listBox_DragEnter)
$listBox.Add_DragDrop($listBox_DragDrop)

$Form.Add_FormClosed($Form_FormClosed)

# Initialize Form
[void]$Form.ShowDialog()
$Form.Dispose()

Well that is it for now. In Part 3 of this tutorial, I’ll be showing you are much more User Friendly GUI for the Drag&DropGUI we’ve just been looking at.

I’ll be also going into more examples of Drag & Drop to illustrate the various ways to acheive this in PowerShell, including the use of custom templates.

Got a Question? Ask away? Got a request for things to add or a new tutorial topic, let us know in the Discussion section. Want to contribute your own tutorials on PowerShell? Please do! :)

Regards,

Regedit32

I have created many PowerShell scripts for the last couple of years. All with a single purpose to automate my IT work as much as possible. But this week I needed to create a script that could be run by the users themselves. And users and command line isn’t the best combination, so let’s take a look at using the PowerShell GUI.

We all love PowerShell for the simplicity and efficientness when it comes to scripting, but for normal users working with a CLI isn’t something they are used to. In this article, I will explain how you can create a simple GUI for your script and help you through the basic obstacles.

The example project

In this article I will use one of my own projects as an example, so let me first tell a bit about the problem I needed to solve.

I work for a construction company and our construction sites are connected over a 4G IPVPN network with the datacenter. Because of double natting, we can’t deploy printers with a print server.

Our users can install a printer themselves, but because they are not listed on the print server they can easily search for the printer. They will need to create a TCP/IP port, find the IP Address of the printer, select the driver and give a printer name.

I created a PowerShell script that I could run remotely to do this in the background, but that would require them to call me, so I could run the script in the background. Now I could just install all the printers, but that would only be confusing and resulting in a long list of printers.

So I figured, the PowerShell script only needs the printer model, Ip Address and a name. We can look up the Ip Address so if the user can select a model and fill in a name we are done. Saving me 2 calls a week.

The basics of the PowerShell GUI

Before we start creating a form is it important to know that the PowerShell script is run sequentially. So you define your form and show it. But any code after you displayed the form won’t be executed until you close the form.

This is something that took me 30 minutes to figure out… I simply thought I could show the form and handle the input below it in PowerShell.

Creating the form

We start with a blank form. First, we add the .Net Windows. Forms. Also, we need to define a size for the form (width, height), title and background color. Just copy-paste the code below in the PowerShell ISE and click on run

# Init PowerShell Gui
Add-Type -AssemblyName System.Windows.Forms

# Create a new form
$LocalPrinterForm                    = New-Object system.Windows.Forms.Form

# Define the size, title and background color
$LocalPrinterForm.ClientSize         = '500,300'
$LocalPrinterForm.text               = "LazyAdmin - PowerShell GUI Example"
$LocalPrinterForm.BackColor          = "#ffffff"

# Display the form
[void]$LocalPrinterForm.ShowDialog()

You will see a simple form just like this one below:

PowerShell form

Adding elements to your form

On our form, we can add elements. These can be used to display information and gather user input. The place of the input is based on points/pixels from the left side and the top side. So the location 20,50 is 20 pixels from the left side and 50 points from the top side.

We have the following elements that we can use on our forms:

  • TextBox (to get user input)
  • Label
  • Button
  • PictureBox
  • CheckBox
  • ComboBox (Dropdown list)
  • ListView
  • ListBox
  • RadioButton
  • Panel
  • Groupbox (To group elements together)
  • ProgressBar
  • DataGridView

So let’s create some elements on our form. Add the code below to your script. Make sure that ShowDialog is at the end of your script.

# Create a Title for our form. We will use a label for it.
$Titel                           = New-Object system.Windows.Forms.Label

# The content of the label
$Titel.text                      = "Adding new printer"

# Make sure the label is sized the height and length of the content
$Titel.AutoSize                  = $true

# Define the minial width and height (not nessary with autosize true)
$Titel.width                     = 25
$Titel.height                    = 10

# Position the element
$Titel.location                  = New-Object System.Drawing.Point(20,20)

# Define the font type and size
$Titel.Font                      = 'Microsoft Sans Serif,13'

# Other elemtents
$Description                     = New-Object system.Windows.Forms.Label
$Description.text                = "Add a new construction site printer to your computer. Make sure you are connected to the network of the construction site."
$Description.AutoSize            = $false
$Description.width               = 450
$Description.height              = 50
$Description.location            = New-Object System.Drawing.Point(20,50)
$Description.Font                = 'Microsoft Sans Serif,10'

$PrinterStatus                   = New-Object system.Windows.Forms.Label
$PrinterStatus.text              = "Status:"
$PrinterStatus.AutoSize          = $true
$PrinterStatus.location          = New-Object System.Drawing.Point(20,115)
$PrinterStatus.Font              = 'Microsoft Sans Serif,10,style=Bold'

$PrinterFound                    = New-Object system.Windows.Forms.Label
$PrinterFound.text               = "Searching for printer..."
$PrinterFound.AutoSize           = $true
$PrinterFound.location           = New-Object System.Drawing.Point(75,115)
$PrinterFound.Font               = 'Microsoft Sans Serif,10'

# ADD OTHER ELEMENTS ABOVE THIS LINE

# Add the elements to the form
$LocalPrinterForm.controls.AddRange(@($Titel,$Description,$PrinterStatus,$PrinterFound))

# THIS SHOULD BE AT THE END OF YOUR SCRIPT FOR NOW
# Display the form
[void]$LocalPrinterForm.ShowDialog()

What you see here is that every element is created. These are all simple text or labels elements. You can define the width and height of each element, but if your content is longer then the element it will only be partially displayed. So by setting the Autosize to true, you are assured that the user can read the whole label.

Every element is given a location, the first digit is the number of pixels from the left side, the second the number of pixels from the top.

The result will look like this:

Adding elements to PowerShell GUI Form

Using a dropdown list

Our user needs to select the printer manufacturer, we only use two brands, so I radio button could work too. We use generic print drivers so I don’t need to know the specific model of the printer.

$PrinterType                     = New-Object system.Windows.Forms.ComboBox
$PrinterType.text                = ""
$PrinterType.width               = 170
$printerType.autosize            = $true

# Add the items in the dropdown list
@('Canon','Hp') | ForEach-Object {[void] $PrinterType.Items.Add($_)}

# Select the default value
$PrinterType.SelectedIndex       = 0
$PrinterType.location            = New-Object System.Drawing.Point(20,210)
$PrinterType.Font                = 'Microsoft Sans Serif,10'

The code above is pretty clear I think, we create the combo box and add the items with a single line foreach loop to the list. Again we also define the position and I have set a minimum width for the element.

You can set a default value for your dropdown list by selecting the index. Make sure you add the variable of the element to the $LocalPrinterForm.controls.AddRange, otherwise it won’t be displayed.

Adding buttons

We are also going to add some buttons to our form. A button can have a standard action, like (
OK, Cancel, Abort, Retry, Ignore, Yes, or No) or you can assign a custom function to it.

The buttons we are going to add are Cancel and Add Printer. Cancel will just close the form and does nothing else, while AddPrinter will run our logic to add the printer. Add the buttons with the code below, again make sure you add the variables of the buttons to the $LocalPrinterForm.controls.AddRange

$AddPrinterBtn                   = New-Object system.Windows.Forms.Button
$AddPrinterBtn.BackColor         = "#a4ba67"
$AddPrinterBtn.text              = "Add Printer"
$AddPrinterBtn.width             = 90
$AddPrinterBtn.height            = 30
$AddPrinterBtn.location          = New-Object System.Drawing.Point(370,250)
$AddPrinterBtn.Font              = 'Microsoft Sans Serif,10'
$AddPrinterBtn.ForeColor         = "#ffffff"

$cancelBtn                       = New-Object system.Windows.Forms.Button
$cancelBtn.BackColor             = "#ffffff"
$cancelBtn.text                  = "Cancel"
$cancelBtn.width                 = 90
$cancelBtn.height                = 30
$cancelBtn.location              = New-Object System.Drawing.Point(260,250)
$cancelBtn.Font                  = 'Microsoft Sans Serif,10'
$cancelBtn.ForeColor             = "#000"
$cancelBtn.DialogResult          = [System.Windows.Forms.DialogResult]::Cancel
$LocalPrinterForm.CancelButton   = $cancelBtn
$LocalPrinterForm.Controls.Add($cancelBtn)

Now a lot of examples on other websites discard the results of the dialog by using [void]$form.ShowDialog(). But the return value of the dialog should be used to tell how the user has closed the form.

So we are going to modify the ShowDialog cmd.

$result = $LocalPrinterForm.ShowDialog()

This way we can check if the user has pressed Cancel or any other default button in the form

if ($result –eq [System.Windows.Forms.DialogResult]::Cancel)
{
    write-output 'User pressed cancel'
}

Adding a custom function to a button

When the user clicks Add Printer a custom script should be executed. We can create a function with our logic and assign the function to the button with the following cmd

$AddPrinterBtn.Add_Click({ AddPrinter })

And our function

function AddPrinter { 
	# ADDING PRINTER LOGIC GOES HERE
}

Result

So after we added all elements, and I added some extra-label, our final PowerShell GUI form looks like this:

PowerShell GUI Example

Now we only have to finish our script so a printer is added.

Adding the logic

So with our form in place, we can start by adding the logic. During the execution of your script, you might want to change text, show or remove fields or perform a specific action. Lets first start with outlining our PowerShell script.

#------------[Initialisations]------------

# Init PowerShell Gui
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing


#---------------[Form]-------------------

# Our form goes here

[System.Windows.Forms.Application]::EnableVisualStyles()

$LocalPrinterForm                    = New-Object system.Windows.Forms.Form
$LocalPrinterForm.ClientSize         = '480,300'
$LocalPrinterForm.text               = "LazyAdmin - PowerShell GUI Example"
$LocalPrinterForm.BackColor          = "#ffffff"
$LocalPrinterForm.TopMost            = $false
$Icon                                = New-Object system.drawing.icon ("./form.ico")
$LocalPrinterForm.Icon               = $Icon

# THE REST OF THE FORM

$LocalPrinterForm.controls.AddRange(@( "<ALL FORM ELEMENTS>" ))

#------------[Functions]------------

function AddPrinter { 
    # Function that is triggered with the add printer button
}

#------------[Script]------------

$AddPrinterBtn.Add_Click({ AddPrinter })

# REST OF YOUR SCRIPT


#------------[Show form]------------

# Show the form
$result = $LocalPrinterForm.ShowDialog()

# Catch any output of the form
if ($result –eq [System.Windows.Forms.DialogResult]::Cancel)
{
    write-output 'User pressed cancel'
}

In your functions or script section, you might want to give some feedback to the user or use the data from the input fields.

Reading and changing PowerShell GUI Elements

Reading data from the textbox is pretty straight forward. In our example, we want to use the name the users have entered and check if the printer name already exists. If the name exists we want to show an error.

if (Get-Printer -Name $printerName.text) {
	$PrinterStatus.ForeColor = "#D0021B"
	$PrinterStatus.Text = 'Printer name already exists.'
}

In the example above we check the given printer name, if it exists we change the font color of the label “PrinterStatus” to read and change the text of the label to show a message to the users.

There is no need to refresh the form or input, you can just change the text as you go.

Showing and hiding fields

Before the form is loaded I check if there is a printer available on the expected network address. If not then there is no need for the user to continue. So when I initialize the form elements I have hidden some elements.

If we have a connection, then I show the elements or change the label of the buttons for example. Hiding and show elements in PowerShell GUI can be done with the following code:

$PrinterNameLabel.Visible = $false # or $true of course 😉

Online PowerShell GUI Editor PoshGUI

Creating a bigger or complex form from the command line only can be a bit challenging. You will have to position all the elements in the correct place, create all the necessary code. Luckily there is an online PowerShell GUI editor PoshGUI.

Online PowerShell Editor PoshGUI

This is a great tool to create your initial form layout. You can download the PS1 file and continue working on your form in your favorite editor.

Conclusion

So I hope this article helpt you to get started creating your first PowerShell form. While PowerShell isn’t really meant for creating forms, it can be a great way to make a script more usable to your user or colleagues.

Subscribe to the newsletter to receive the latest article about PowerShell or Office 365 in your mailbox.

The complete code looks as follows:

#---------------------------------------------------------[Initialisations]--------------------------------------------------------
# Init PowerShell Gui
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing


#---------------------------------------------------------[Form]--------------------------------------------------------

[System.Windows.Forms.Application]::EnableVisualStyles()

$LocalPrinterForm                    = New-Object system.Windows.Forms.Form
$LocalPrinterForm.ClientSize         = '480,300'
$LocalPrinterForm.text               = "Printers"
$LocalPrinterForm.BackColor          = "#ffffff"
$LocalPrinterForm.TopMost            = $false
$Icon                                = New-Object system.drawing.icon ("//thunnissen.local/netlogon/printer.ico")
$LocalPrinterForm.Icon               = $Icon

$Titel                           = New-Object system.Windows.Forms.Label
$Titel.text                      = "Add new printer"
$Titel.AutoSize                  = $true
$Titel.width                     = 25
$Titel.height                    = 10
$Titel.location                  = New-Object System.Drawing.Point(20,20)
$Titel.Font                      = 'Microsoft Sans Serif,13'

$Description                     = New-Object system.Windows.Forms.Label
$Description.text                = "To add a printer, make sure you are connected to the same network as the printer.."
$Description.AutoSize            = $false
$Description.width               = 450
$Description.height              = 50
$Description.location            = New-Object System.Drawing.Point(20,50)
$Description.Font                = 'Microsoft Sans Serif,10'

$PrinterStatus                   = New-Object system.Windows.Forms.Label
$PrinterStatus.text              = "Status:"
$PrinterStatus.AutoSize          = $true
$PrinterStatus.width             = 25
$PrinterStatus.height            = 10
$PrinterStatus.location          = New-Object System.Drawing.Point(20,115)
$PrinterStatus.Font              = 'Microsoft Sans Serif,10,style=Bold'

$PrinterFound                    = New-Object system.Windows.Forms.Label
$PrinterFound.text               = "Searching for printer..."
$PrinterFound.AutoSize           = $true
$PrinterFound.width              = 25
$PrinterFound.height             = 10
$PrinterFound.location           = New-Object System.Drawing.Point(100,115)
$PrinterFound.Font               = 'Microsoft Sans Serif,10'

$PrinterDetails                  = New-Object system.Windows.Forms.Label
$PrinterDetails.text             = "Printer details"
$PrinterDetails.AutoSize         = $true
$PrinterDetails.width            = 25
$PrinterDetails.height           = 10
$PrinterDetails.location         = New-Object System.Drawing.Point(20,150)
$PrinterDetails.Font             = 'Microsoft Sans Serif,12'
$PrinterDetails.Visible          = $false

$PrinterNameLabel                = New-Object system.Windows.Forms.Label
$PrinterNameLabel.text           = "Name:"
$PrinterNameLabel.AutoSize       = $true
$PrinterNameLabel.width          = 25
$PrinterNameLabel.height         = 20
$PrinterNameLabel.location       = New-Object System.Drawing.Point(20,180)
$PrinterNameLabel.Font           = 'Microsoft Sans Serif,10,style=Bold'
$PrinterNameLabel.Visible        = $false

$PrinterName                     = New-Object system.Windows.Forms.TextBox
$PrinterName.multiline           = $false
$PrinterName.width               = 314
$PrinterName.height              = 20
$PrinterName.location            = New-Object System.Drawing.Point(100,180)
$PrinterName.Font                = 'Microsoft Sans Serif,10'
$PrinterName.Visible             = $false

$PrinterTypeLabel                = New-Object system.Windows.Forms.Label
$PrinterTypeLabel.text           = "Brand:"
$PrinterTypeLabel.AutoSize       = $true
$PrinterTypeLabel.width          = 25
$PrinterTypeLabel.height         = 20
$PrinterTypeLabel.location       = New-Object System.Drawing.Point(20,210)
$PrinterTypeLabel.Font           = 'Microsoft Sans Serif,10,style=Bold'
$PrinterTypeLabel.Visible        = $false

$PrinterType                     = New-Object system.Windows.Forms.ComboBox
$PrinterType.text                = ""
$PrinterType.width               = 170
$PrinterType.height              = 20
@('Canon','Hp') | ForEach-Object {[void] $PrinterType.Items.Add($_)}
$PrinterType.SelectedIndex       = 0
$PrinterType.location            = New-Object System.Drawing.Point(100,210)
$PrinterType.Font                = 'Microsoft Sans Serif,10'
$PrinterType.Visible             = $false

$AddPrinterBtn                   = New-Object system.Windows.Forms.Button
$AddPrinterBtn.BackColor         = "#ff7b00"
$AddPrinterBtn.text              = "Add"
$AddPrinterBtn.width             = 90
$AddPrinterBtn.height            = 30
$AddPrinterBtn.location          = New-Object System.Drawing.Point(370,250)
$AddPrinterBtn.Font              = 'Microsoft Sans Serif,10'
$AddPrinterBtn.ForeColor         = "#ffffff"
$AddPrinterBtn.Visible           = $false

$cancelBtn                       = New-Object system.Windows.Forms.Button
$cancelBtn.BackColor             = "#ffffff"
$cancelBtn.text                  = "Cancel"
$cancelBtn.width                 = 90
$cancelBtn.height                = 30
$cancelBtn.location              = New-Object System.Drawing.Point(260,250)
$cancelBtn.Font                  = 'Microsoft Sans Serif,10'
$cancelBtn.ForeColor             = "#000"
$cancelBtn.DialogResult          = [System.Windows.Forms.DialogResult]::Cancel
$LocalPrinterForm.CancelButton   = $cancelBtn
$LocalPrinterForm.Controls.Add($cancelBtn)

$LocalPrinterForm.controls.AddRange(@($Titel,$Description,$PrinterStatus,$PrinterFound,$PrinterName,$PrinterNameLabel,$PrinterType,$AddPrinterBtn,$cancelBtn,$PrinterTypeLabel,$PrinterDetails))

#-----------------------------------------------------------[Functions]------------------------------------------------------------

function AddPrinter { 
	$PrinterFound.ForeColor = "#000000"
	$PrinterFound.Text = 'Adding printer...'
	# Check printer port
	$portName = "TCPPort:"+$printerIp
	$portExists = Get-Printerport -Name $portname -ErrorAction SilentlyContinue

	# Create port if it not exists
	if (-not $portExists) {
		$PrinterFound.Text = 'Creating printer port...'
		Add-PrinterPort -name $portName -PrinterHostAddress $printerIp
	}

	# Select the correct driver
	if ($PrinterType.SelectedItem -eq 'Canon') {
		$printerDriverName = "Canon Generic Plus PCL6"
	}else{
		$printerDriverName = "HP LaserJet M227-M231 PCL-6"
	}

	# Check if printer driver exists
	$printDriverExists = Get-PrinterDriver -name $printerDriverName -ErrorAction SilentlyContinue

	# Install printer or printer driver and printer
	if ($printDriverExists) {
		$PrinterFound.Text = 'Installing printer...'
		Add-Printer -Name $printerName.text -PortName $portName -DriverName $printerDriverName 
	}else{
		$PrinterFound.Text = 'Installing printer driver...'
		Add-PrinterDriver -name $printerDriverName

		$PrinterFound.Text = 'Installing printer...'
		Add-Printer -Name $printerName.text -PortName $portName -DriverName $printerDriverName
	}

	if (Get-Printer -Name $printerName.text) {
		$PrinterFound.ForeColor = "#7ed321"
		$PrinterFound.Text = 'The printer is installed'
	}
	else {
		$PrinterFound.ForeColor = "#D0021B"
		$PrinterFound.Text = 'Installation failed'
	}
	$PrinterNameLabel.Visible = $false
	$PrinterName.Visible = $false
	$PrinterType.Visible = $false
	$AddPrinterBtn.Visible = $false
	$PrinterDetails.Visible = $false
	$PrinterTypeLabel.Visible = $false
	$cancelBtn.text = "Close"
}

#---------------------------------------------------------[Script]--------------------------------------------------------
# Get printers IP Address
$clientIP = (
    Get-NetIPConfiguration |
    Where-Object {
        $_.IPv4DefaultGateway -ne $null -and
        $_.NetAdapter.Status -ne "Disconnected"
    }
).IPv4Address.IPAddress

$networkAddress = $clientIP.Split('.')
$networkAddress = $networkAddress[0]+"."+$networkAddress[1]+"."+$networkAddress[2]

# Check if printer is online
$printerIp =  $networkAddress + ".31" 
$testConnection = Test-Connection $printerIp -count 1 -Quiet

If ($testConnection) {
	$PrinterFound.text = "Printer found"
	$PrinterFound.ForeColor = "#7ed321"
	$PrinterNameLabel.Visible = $true
	$PrinterName.Visible = $true
	$PrinterType.Visible = $true
	$AddPrinterBtn.Visible = $true
	$PrinterDetails.Visible = $true
	$PrinterTypeLabel.Visible = $true
}else{
	$PrinterFound.text = "No printers found"
	$PrinterFound.ForeColor = "#D0021B"
	$cancelBtn.text = "Sluiten"
}

$AddPrinterBtn.Add_Click({ AddPrinter })

[void]$LocalPrinterForm.ShowDialog()

You can also check the following articles:

  • Adding printers and printer ports with PowerShell
  • Migrating home folders to OneDrive with PowerShell
  • Getting started with Microsoft Flow

In this example. we will walk through taking a standard office task—in this case, checking to see if a system responds to a ping—and build a simple GUI around the function using System.Windows.Forms class available to us
in PowerShell from .Net.

Starting simple

To begin, we will create a simple ping testing tool:

$ComputerName = read-host "Enter Computer Name to test:"</code>

if (Test-Connection $ComputerName -quiet -Count 2){

Write-Host -ForegroundColor Green "Computer $ComputerName has network connection"</code>

}

Else{

Write-Host -ForegroundColor Red "Computer $ComputerName does not have network connection"}

And to test it:

Cool, it works!Cool, it works!

Now that we know our base code is working, we will create the outline of the GUI we want.

First and foremost, in order to have access to the Systems.Windows.Forms .NET elements we need to draw our GUI, we have to load the assemblies. This is done with the following two lines, added to the top of our script.

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

Void is a .NET parameter that tells the method here to do the operation but be quiet about it. If we did not include this param, we would see noisey output in the console as the Assemblies are loaded.

Next, we will define the basic form onto which our application will be built, calling it $form, and then setting properties for its title (via the .Text property), size and position on screen.

#begin to draw forms

$Form = New-Object System.Windows.Forms.Form

$Form.Text = "Computer Pinging Tool"

$Form.Size = New-Object System.Drawing.Size(300,150)

$Form.StartPosition = "CenterScreen"

One of the things we are doing here with the System.Windows.Forms object is to modify its KeyPreview property to $True and then add listeners for certain key presses to have our form respond to them.  If we enable KeyPreview, your form itself will intecept
a key press and can do things with it before the control that is selected gets the press. So instead of the user having to click the X button or click enter, we can tell the form to do something when the user hits Escape or Enter instead.

With that in mind, lets add a hook into both the Enter and Escape keys to make them function.

$Form.KeyPreview = $True

$Form.Add_KeyDown({if ($_.KeyCode -eq "Enter")

{$x=$ListBox.SelectedItem;$Form.Close()}})

$Form.Add_KeyDown({if ($_.KeyCode -eq "Escape")

{$Form.Close()}})

And now, lets comment block out the Ping section of the script (we’ll also use #region and #endregion to allow us to collapse away that block for the time being) and add the following lines to the bottom to display our form.

#Show form

$Form.Topmost = $True

$Form.Add_Shown({$Form.Activate()})

[void] $Form.ShowDialog()

If you’ve been following along from home, you should have something similar to this.

pingtool_02

And now lets give it a try!

Mmmm, tasty progress.

Ah, the sweet taste of progress. We now have a form and some code which works. Lets add a box where a user can specify the computer name to test, and then a button to start the test.

Drawing the forms 

We’ll be using the Systems.Windows.Forms.Label (Abbreviated as simply Forms.whatever from now on), Forms. TextBox and Forms.Button controls to add the rest of our app for now. While I’m here, if you’d like a listing of all of the available other .net controls
you can make use of, check out this link:
http://msdn.microsoft.com/en-us/library/system.windows.forms.aspx

First, lets add a label (a field of uneditable text), in which we will describe what this tool will do. To start, instantiate a new System.Windows.Forms object of type .label, and then we’ll set the location, size and text properties. Finally, we’ll add
the control to our form using the .Add() method, as you’ll see below. Add the following text above the commented out Actual Code region.

$label = New-Object System.Windows.Forms.Label

$label.Location = New-Object System.Drawing.Size(5,5)

$label.Size = New-Object System.Drawing.Size(240,30)

$label.Text = "Type any computer name to test if it is on the network and can respond to ping"

$Form.Controls.Add($label)

Some notes about this. the Location property here is given in (x,y) with distances being pixels away from the upper left hand corner of the form. Using this method of building a GUI from scratch, it is not uncommon to spend some time fiddling with the sizing
by tweaking values and executing, back and forth.

Note the #regions used to make editing our code a bit cleanerNote
the #regions used to make editing our code a bit cleaner

Lets save our script and see what happens!

We are making a GUI interface using Visual Basi--er, PowerShell!

Alright, next, to throw a textbox on there and add a button to begin the ping test, add the following lines in the ‘#region begin to draw forms’.

$textbox = New-Object System.Windows.Forms.TextBox

$textbox.Location = New-Object System.Drawing.Size(5,40)

$textbox.Size = New-Object System.Drawing.Size(120,20)

#$textbox.Text = "Select source PC:"

$Form.Controls.Add($textbox)

$OKButton = New-Object System.Windows.Forms.Button

$OKButton.Location = New-Object System.Drawing.Size(140,38)

$OKButton.Size = New-Object System.Drawing.Size(75,23)

$OKButton.Text = "OK"

$OKButton.Add_Click($ping_computer_click)

$Form.Controls.Add($OKButton)

$result_label = New-Object System.Windows.Forms.label

$result_label.Location = New-Object System.Drawing.Size(5,65)

$result_label.Size = New-Object System.Drawing.Size(240,30)

$result_label.Text = "Results will be listed here"

$Form.Controls.Add($result_label)

One thing I want to draw attention to is the $OKButton.Add_Click; specifying this property will associate the contents of the $ping_computer_click variable (currently empty) as a function to execute when the button is clicked. 

Seeing the UI come together is such a satisfying feelingSeeing the
UI come together is such a satisfying feeling

Tying it all together

Now to move our earlier code for the pinging function into the space of the $ping_computer_click variable, and uncomment it, and while we’re at it, lets change the value of $ComputerName to $textbox.Text. You should have something similar to the following:
ep2_pingtool_03

Alright, and now if we test it (let’s use ‘localhost’ to guarantee the test completes) we should see…

ep2_pingtool_04The test completes and we get console output
of what happened. Nice!

Now let’s add a status bar to the tool, so the user will have feedback when the button is pressed.

$statusBar1 = New-Object System.Windows.Forms.StatusBar

$statusBar1.Name =
"statusBar1"

$statusBar1.Text =
"Ready..."

$form.Controls.Add($statusBar1)

Then, under the $ping_computer_click section, add this line to the top and the other to bottom:

Top $statusBar1.Text = “Testing…”
Bottom $statusBar1.Text = “Testing Complete”

We’ll also update the logic from the earlier pinging test to have it update the values of the Results label with a message for whether or not the process completed, and also spruce things up with a little color.

$ping_computer_click =

{

#region Actual Code

$statusBar1.Text =
"Testing..."

$ComputerName = $textbox.Text

if
(Test-Connection $ComputerName -quiet -Count 2){

Write-Host -ForegroundColor Green
"Computer $ComputerName has network connection"

$result_label.ForeColor=
"Green"

$result_label.Text =
"System Successfully Pinged"

}

Else{

Write-Host -ForegroundColor Red
"Computer $ComputerName does not have network connection"

$result_label.ForeColor=
"Red"

$result_label.Text =
"System is NOT Pingable"

}

$statusBar1.Text =
"Testing Complete"

#endregion

}

The end result

ep2_pingtool_05

Some final notes:
• If you don’t want the user to be able to resize your beautiful tool, then simply set $form.MaximumSize and $form.MinimumSize equal to $Form.Size.
• In order to have the enter button map to clicking the Okay button, we need to move and alter the following lines to the bottom of the #drawforms region:

$Form.Add_KeyDown({if
($_.KeyCode -eq "Enter"){& $ping_computer_click}})

$Form.Add_KeyDown({if
($_.KeyCode -eq "Escape")

{$Form.Close()}})

The Ampersand (&) has a special meaning in PowerShell. It means Invoke, or Execute. If we didn’t use this, PS would attempt to display the content of the variable instead.

I hope you’ve enjoyed this demonstration/walkthrough. 

The full code

For your convenience, the full code is provided here.

01.#region Boring beginning
stuff

02.[void]
[System.Reflection.Assembly]::LoadWithPartialName(
"System.Windows.Forms")

03.[void]
[System.Reflection.Assembly]::LoadWithPartialName(
"System.Drawing")

04.#endregion

05.  

06.#region
begin to draw forms

07.$Form
= New-Object System.Windows.Forms.Form

08.$Form.Text
=
"Computer Pinging Tool"

09.$Form.Size
= New-Object System.Drawing.Size(300,170)

10.$Form.StartPosition
=
"CenterScreen"

11.$Form.KeyPreview
= $True

12.$Form.MaximumSize
= $Form.Size

13.$Form.MinimumSize
= $Form.Size

14.  

15.$label
= New-Object System.Windows.Forms.label

16.$label.Location
= New-Object System.Drawing.Size(5,5)

17.$label.Size
= New-Object System.Drawing.Size(240,30)

18.$label.Text
=
"Type any computer name to test if it is on the network and can respond to ping"

19.$Form.Controls.Add($label)

20.$textbox
= New-Object System.Windows.Forms.TextBox

21.$textbox.Location
= New-Object System.Drawing.Size(5,40)

22.$textbox.Size
= New-Object System.Drawing.Size(120,20)

23.#$textbox.Text
= "Select source PC:"

24.$Form.Controls.Add($textbox)

25.  

26.$ping_computer_click
=

27.{

28.#region
Actual Code

29.  

30.$statusBar1.Text
=
"Testing..."

31.$ComputerName
= $textbox.Text

32.  

33.if
(Test-Connection $ComputerName -quiet -Count 2){

34.Write-Host
-ForegroundColor Green
"Computer $ComputerName has network connection"

35.$result_label.ForeColor=
"Green"

36.$result_label.Text
=
"System Successfully Pinged"

37.}

38.Else{

39.Write-Host
-ForegroundColor Red
"Computer $ComputerName does not have network connection"

40.$result_label.ForeColor=
"Red"

41.$result_label.Text
=
"System is NOT Pingable"

42.}

43.  

44.$statusBar1.Text
=
"Testing Complete"

45.#endregion

46.}

47.  

48.$OKButton
= New-Object System.Windows.Forms.Button

49.$OKButton.Location
= New-Object System.Drawing.Size(140,38)

50.$OKButton.Size
= New-Object System.Drawing.Size(75,23)

51.$OKButton.Text
=
"OK"

52.$OKButton.Add_Click($ping_computer_click)

53.$Form.Controls.Add($OKButton)

54.  

55.$result_label
= New-Object System.Windows.Forms.label

56.$result_label.Location
= New-Object System.Drawing.Size(5,65)

57.$result_label.Size
= New-Object System.Drawing.Size(240,30)

58.$result_label.Text
=
"Results will be listed here"

59.$Form.Controls.Add($result_label)

60.  

61.$statusBar1
= New-Object System.Windows.Forms.StatusBar

62.$statusBar1.Name
=
"statusBar1"

63.$statusBar1.Text
=
"Ready..."

64.$form.Controls.Add($statusBar1)

65.  

66.$Form.Add_KeyDown({if
($_.KeyCode -eq "Enter"){& $ping_computer_click}})

67.$Form.Add_KeyDown({if
($_.KeyCode -eq "Escape")

68.{$Form.Close()}})

69.#endregion
begin to draw forms

70.  

71.#Show
form

72.$Form.Topmost
= $True

73.$Form.Add_Shown({$Form.Activate()})

74.[void]
$Form.ShowDialog()

In a previous article, you learned how to create a prompt inside of PowerShell by recreating the action prompt for the Remove-Item cmdlet. PowerShell can also create a graphical or GUI input prompt using .NET Framework form-building features available since PowerShell v3. In this tutorial, learn how to create a simple yes/no GUI input prompt to delete a file.

The Code

This time around, I’m going to display the entire code sample, then I’ll walk through each part and explain what is happening. Here I decided to split the PowerShell code out into two functions: the code for Remove-MyItem is its own function and calls the New-YesNoPrompt function that actually creates the GUI input prompt.

Creating the Form Window

Let’s start with the New-YesNoPrompt function. The function takes in two parameters: PromptTitle and PromptMessage. The title is displayed at the top of the window in the title bar, and the message is displayed in the box to the user, ideally what action is about to be approved or denied. First, outside of my two functions at the top of my .ps1 file, I need to import two .NET Framework classes using Add-Type in order to create my form:

  • System.Windows.Forms
  • System.Drawing

Back inside the function, I need to create the $form object to start adding controls to:

# Create the form title, size, and starting position
$form = New-Object System.Windows.Forms.Form
$form.Text = $PromptTitle
$form.Size = New-Object System.Drawing.Size(300, 200)
$form.StartPosition = 'CenterScreen'

The PowerShell code contains the basic information on displaying the GUI input form using different properties, such as:

Text: the title goes here and is displayed in the title bar of the window

Size: creates a new object using the System.Drawing.Size namespace; this is defined in an (x, y) format, or (width, height). The box created here is 300 pixels wide by 200 pixels tall.

StartPosition: this determines where the window is displayed; currently set to CenterScreen to display in the middle, but other options include:

  • CenterParent: the form is centered within its parent form
  • Manual: position is determined by the Location property, which is the of the upper-left corner of the window
  • WindowsDefaultBounds: the form is positioned at the Windows default location and has the bounds determined by Windows default
  • WindowDefaultLocation: the form is positioned at the Windows default location and has the dimensions specified in the form’s size

With the basic windows form created, let’s move onto create action buttons for the user to respond to.

Creating the Buttons

For our form, I need options for the user to select from. These options will be presented as Yes and No buttons to indicate if I want to delete the file or not:

# Create the Yes button and its properties
$yesButton = New-Object System.Windows.Forms.Button
$yesButton.Location = New-Object System.Drawing.Point(60, 120)
$yesButton.Size = New-Object System.Drawing.Size(75, 23)
$yesButton.Text = 'Yes'
$yesButton.DialogResult = [System.Windows.Forms.DialogResult]::Yes
$form.AcceptButton = $yesButton
$form.Controls.Add($yesButton)
# Create the No button and its properties
$noButton = New-Object System.Windows.Forms.Button
$noButton.Location = New-Object System.Drawing.Point(165, 120)
$noButton.Size = New-Object System.Drawing.Size(75, 23)
$noButton.Text = 'No'
$noButton.DialogResult = [System.Windows.Forms.DialogResult]::No
$form.CancelButton = $noButton
$form.Controls.Add($noButton)

First, I create an object based on the Button class of the Forms namespace. I then set the Location of the button within the form using (x,y) coordinates, or the distance from the upper left of the form window in pixels. This is the same idea as setting the Location of the form itself on the monitor. This will probably take some experimentation to get it just right.

Next, I set the Size of the button just like setting the size of the windows form using (width, height) in pixels. After the size is setting the Text of what will appear on the button, here the words Yes and No to indicate if I want to delete the file or not.

Notice the difference in each button’s Location property. The Y values are both the same at 120, meaning they will be horizontally aligned, but the X values are not. The Yes button is 60 pixels from the left while the No button is 165 pixels from the left, meaning they will be next to each other. You’ll see this later in our final screenshot.

The DialogResult property gets or sets the returned value when the button is clicked. The DialogResult property is set to an identifier that indicates the return value of the dialog box, and for each of our buttons this is Yes or No. Other options for DialogResults include: Abort, Cancel, Ignore, None, OK, and Retry.

Along with each button, I set the AcceptButton to $yesButton and CancelButton to $noButton for the form itself. The AcceptButton is activated when the user presses the Enter key on the form, and the CancelButton is activated when the user presses the Escape key. So for my form, if the user presses Enter to the prompt, this is the same as clicking the Yes button, and pressing Escape is the same as pressing the No button. Finally, I add the button to the form itself under the Controls property.

Finalizing the Form

At last, I need to display the value of the $PromptMessage parameter onto the form. This is going to be the question I ask the user to respond to, and this takes shape using a Label. Like our other objects so far, it has a Location, Size, and Text property to create how it looks.

Also, like the buttons, I add it to the Controls property of the form. The last property I set is TopMost, which just puts the form on top of all others when it is displayed. With the form created, I return the form object back to whatever called my form function.

Remove-MyItem Function

By separating the logic to create the graphical prompt out of the Remove-MyItem function, the code here is greatly reduced. First I create a $params object with the window title and message that I want to display in the graphical prompt window. I pass this object to my prompt function using splatting (I always forget splatting exists, so I wanted to use it here) along with using the ShowDialog() method on my form. This will display the form to the user, and the result of which button is selected is saved to $response.

Just like the examples from last week, I use a switch statement to perform different actions based on the option the user has chosen. The condition is to use the System.Windows.Forms.DialogResult type we specified for each button in the other function. For this example, I just outputted text to show whether the action would remove the file or leave it alone.

Final Results

Here is the final result of creating my GUI input prompt to remove a file when calling my PowerShell function Remove-MyFile and selecting the Yes button in the prompt:

powershell gui input

Final Thoughts

The method above is not intended to replace other options for confirming changes or testing commands using ShouldProcess, which adds -WhatIf and -Confirm functionality to a function. I received some valid criticism from the previous post on creating console prompts. The article here is to simple show how it is possible to create a graphical prompt, not to replace this other important functionality. Please check out my repository for this on GitHub for the code used in this post plus any additional examples I come up with in the future.

Questions or comments? If so, drop me a note below or find me on Twitter or LinkedIn to discuss further.

  • Powerpoint download for windows 10 free
  • Powershell отключить обновление windows 10
  • Powerpoint 365 скачать бесплатно для windows 10
  • Powershell module activedirectory windows 10
  • Powershell messagebox system windows forms