PowerShell GUI – Pelipalvelimien pingaustyökalu & skripti .exe-tiedostoksi

Harjoituksessa käytetty laitteisto/ohjelmisto:

  • Tietokone (Asus Z170-A, i7-6700K , 16GB DDR4 3200MHz, Evga 1070 sc)
  • Oheishärpäkkeet
  • Windows 10 Pro 64-Bit ( versio 1607)
  • PowerShell versio 5.1.14393.1358

Johdanto:

Skriptin alkukommentit kiteyttävät tarkoituksen melko hyvin. Tavoitteenani olisi siis luoda PowerShell skripti, joka luo graafisen käyttöliittymän ja sille toiminnallisuudet Old School Runescape pelipalvelimien ja käyttäjän välisen viiveen selvittämiseksi. Virallista tapaa tarkistaa palvelimien ping eli pelipalvelimen ja käyttäjän välinen latenssi (aika, joka ICMP-paketilla kestää kulkea edestakaisin lähteen ja kohteen välillä) ei vielä ole, ja siten koin tälläisen työkalun tarpeelliseksi sen sijaan, että komentokehotteen kautta kirjoittaisin osoitteet aina uudelleen.

<#
##################################################
A Tool for pinging Oldschool Runescape game servers.
User types the server's number in an inputbox and the tool displays the ping results in an Output box.
Also allows users to choose how many packets are being sent and received.
* Potential soon to come updates: *
- A button for pinging all of the game worlds at once.
@ Markus Pyhäranta. 16.7.2017.
##################################################
#>

Alla oleva batch skripti pingaa kaikki osrs-palvelimet, mutta ajattelin, että olisi ihan hauska harjoitus luoda PowerShellillä graafinen käyttöliittymä ohjelmalle, jolla voisi helposti pingata yksittäisen palvelimen ilman, että tarvitsee kirjoittaa pitkää komentoa komentoriville.

@ECHO off
SET worlds=1,2,3,4,5,6,8,9,10,11,12,13,14,16,17,18,19,20,21,22,25,26,27,28,29,30,33,34,35,36,37,38,41,42,43,44,45,46,49,50,51,52,53,54,57,58,59,60,61,62,65,66,67,68,69,70,73,74,75,76,77,78,81,82,83,84,85,86,93,94
FOR %%i IN (%worlds%) DO (
Echo | SET /p=World %%i
FOR /F "tokens=5" %%a IN ('Ping oldschool%%i.runescape.com -n 1 ^| FIND "time="') DO Echo %%a
)
PAUSE

Tarvoitteena olisi myös jossain kohtaa lisätä vastaava toiminnallisuus tähän käyttöliittymään, jotta saisi kaikki palvelimet pingattua kerralla. Parikin eri tavalla toimivaa funktiota olen jo kehitellyt, mutta tuloksien tulostamista graafisen käyttöliittymän tekstiboxiin miellyttävässä formaatissa en ole vielä saanut toimimaan kunnolla.
Skripti löytyy:
URL: https://github.com/PyhaMarkus/OSRSPingTool


Graafisen käyttöliittymän komponentit:

Koska PowerShell perustuu .NET-frameworkkiin , sillä voidaan helposti kirjoittaa graafista käyttöliittymää Microsoftin .NET kirjastoja hyödyntämällä.
Aluksi ladataan tarpeelliset .NET-kirjastot. [void] hiljentää outputin, eli ei tule PowerShellin konsoliin ilmoituksia.

########## GUI Starts Here ##########
#Loading of .NET assemblies for GUI
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
Main Form

Tämä ikkuna on se pääkehys koko ohjelmalle, jonka sisälle tulee vielä muita komponentteja. Ikkunalle voidaan määritellä mm. koko, logo ja otsikko. Ikkuna on määritelty muuttujalla $Form, jonka sisään rakennan muita komponentteja, kuten nappeja ja tekstilaatikoita.

#Main form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Oldschool Runescape Ping"
$Form.Size = New-Object System.Drawing.Size(535,420)
$Form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon('C:\Users\Markus\Desktop\OSRSPing\favicon.ico') #Logo for form
#$Form.FormBorderStyle = [System.Windows.Forms.FormBorderStyle]::FixedToolWindow # <- Fixed windows size

Viimeinen rivi on sitä varten, jos haluaa estää ohjelman koon muuttamisen tai pienentämisen käyttäjän toimesta. Tällä hetkellä kommentoituna pois.
Loput komponentit tullaan rakentamaan pääikkunan sisään seuraavasti:
mainbox

Labels

Label control toimii tässä tapauksessa samoin, kuin paragraph <p> -tagit html-koodissa. Lisään sillä siis tekstiä, kuten ostikoita tai muita huomautuksia käyttäjälle. Sillekin voidaan määrittää monia ulkonäöllisiä ominaisuuksia, kuten fontti, fonttikoko, väri, sijainti jne.

#Heading
$Label = New-Object system.windows.Forms.Label
$Label.Text = "Tool for testing OSRS server connections"
$Label.AutoSize = $true
$Label.location = new-object system.drawing.point(5,10)
$Label.Font = "Microsoft Sans Serif,14,style=Underline"
$Label.ForeColor = "#51b5f7"
$Form.controls.Add($Label)
#Paragraph "Insert"
$Label2 = New-Object system.windows.Forms.Label
$Label2.Text = "Insert world number below:"
$Label2.AutoSize = $true
$Label2.location = new-object system.drawing.point(10,40)
$Label2.Font = "Microsoft Sans Serif,11"
$Form.controls.Add($Label2)
#Paragraph "Note"
$Label3 = New-Object system.windows.Forms.Label
$Label3.Text = "Note: Also setup how many ping results you would like."
$Label3.AutoSize = $true
$Label3.location = new-object system.drawing.point(10,355)
$Label3.Font = "Microsoft Sans Serif,9"
$Form.controls.Add($Label3)
#Paragraph "Count"
$Label4 = New-Object system.windows.Forms.Label
$Label4.Text = "Count:"
$Label4.AutoSize = $true
$Label4.location = new-object system.drawing.point(375,353)
$Label4.Font = "Microsoft Sans Serif,11"
$Label4.ForeColor = "#51b5f7"
$Form.controls.Add($Label4)

Kunkin labelin viimeisellä rivillä se lisätään osaksi ohjelman pääikkunaa, $Formia. Esimerkiksi: $Form.controls.Add($Label). Ilman tuota labelia ei näkyisi ohjelmassa ollenkaan. Sama päätee kaikkiin nappeihin, tekstilaatikoihin ja muihin GUI:n komponentteihin.
Alla esimerkki kahdesta labelista:
labels

InputBox

Tähän tekstilaatikkoon käyttäjä syöttää pelipalvelimen numeron, jonka haluaa työkalun pingaavan. OSRS maat ovat muotoa esimerkiksi 336, joka taas muuntautuu URL:ina oldschool36.runescape.com. Kaikki maat ovat “3” -alkuisia, joten käyttäjän tarvitsee syöttää ainoastaan kaksi viimeistä numeroa. Tästä syystä rajoitin myös syöttöjen määrän kahteen yksikköön. Syöttöä en ole rajoittanut ainoastaan numeroihin. Sillä ei kuitenkaan ole väliä, koska jos syöttää siihen kirjaimia numeroiden sijaan, tulee seuraava virheilmoitus:

Ping request could not find host oldschoolxx.runescape.com. Please check the name and try again.

Inputboxin koodi:

#User InputBox for inserting game world
$InputBox = New-Object System.Windows.Forms.TextBox
$InputBox.Location = New-Object System.Drawing.Size(10,90)
$InputBox.Size = New-Object System.Drawing.Size(50,20)
$InputBox.Font = "Microsoft Sans Serif,12"
$InputBox.MaxLength = "2"
$InputBox.textAlign = "center"
$Form.Controls.Add($InputBox)

Tulos:
input

OutputBox

Aluksi toteutin ohjelman siten, että pingtulokset näytettiin PowerShell-konsoli-ikkunassa, joka aukesi nappia painamalla. Tämä ei tietenkään ollut optimaalinen ratkaisu, joten selvittelin, kuinka tulokset voitaisiin näyttää saman käyttöliittymän sisällä ilman uusien ikkunoiden aukaisua. Ratkaisu oliki lopulta hyvin yksinkertainen.
$OutputBox.ReadOnly -muuttujalla sain estettyä käyttäjän inputin, joten jäljelle jää vain tekstilaatikko, johon tulostaminen on sallittua, mutta kirjoittaminen estetty. Kooltaan OutputBox on sopiva, että siihen saa mahdutettua vastaukset kahdeksalta pingin ICMP-paketilta. Tarvittaessa laatikkoa voi rullata alaspäin.

#OutputBox for displaying ping results to user
$OutputBox = New-Object System.Windows.Forms.TextBox
$OutputBox.Location = New-Object System.Drawing.Size(10,125)
$OutputBox.Size = New-Object System.Drawing.Size(500,220)
$OutputBox.BackColor = "white"
$OutputBox.Font = "consolas, 8"
$OutputBox.MultiLine = $True
$OutputBox.ScrollBars = "Vertical"
$OutputBox.Enabled = $True
$OutputBox.ReadOnly = $True #Prevents user input
$Form.Controls.Add($OutputBox)

Lopputulos ja esimerkki pingtuloksista:
output

DropDownBox

Aluksi ajattelin, että työkalu pingaisi palvelimet oletustavalla, jossa paketteja lähetetään ja vastaanotetaan neljä. Päätin kuitenkin lisätä tällaisen vetolaatikon, josta käyttäjä voi valita montako tulosta palvelimelta haluaa. Samalla en kuitenkaan tahtonut, että käyttäjä voisi tulvia paketteja täysin absurdeilla määrille, joten rajoitin pakettien määrän kolmeen vaihtoehtoon: 1, 4, 8. Määriä voin tarvittaessa lisätä, mutta nuo vaikuttivat sopivilta. Oletuksena ohjelma lähettää ja vastaanottaa vain yhden ICMP-paketin:
1
Käyttäjä voi oikean alakulman vetolaatikosta valita haluamansa määrän. Vastaukset eivät päivity reaaliajassa, vaan käyttäjä joutuu odottamaan viimeisenkin vastauksen saapumista. Tämän vuoksi suurempia pakettimääriä ei kannattaisikaan lähettää, sillä odotus kasvaa, eikä käyttäjä tiedä, mistä viive johtuu. Tuohon voisi kyllä lisätä tulostuksen joka kertoisi, että prosessi on meneillään taustalla ja kohta palataan asiaan vastauksien kera. Tämän voisi kokeilla lisätä myöhemmin. Reaaliaikaiset vastaukset olisivat myös kätevät.
Koodi Dropdown listalle:

#Dropdown list for selecting packet count
$DropDownBox = New-Object System.Windows.Forms.ComboBox
$DropDownBox.Location = New-Object System.Drawing.Size(430,350) 
$DropDownBox.Size = New-Object System.Drawing.Size(60,15) 
$DropDownBox.DropDownHeight = 200 
$DropDownBox.Font = "Microsoft Sans Serif,11"
$DropDownBox.DropDownStyle =
     [System.Windows.Forms.ComboBoxStyle]::DropDownList; #Prevents user input
$Form.Controls.Add($DropDownBox)

Toiseksi viimeisellä rivillä estin käyttäjää kirjoittamasta omaa määräänsä, vaan hänen on pakko valita noista kolmesta ennalta määritellystä pakettimäärästä, jotka listasta löytyvät.
Listan rakenne:

#Items for DropDownBox #Items for DropDownBox
$CountList=@("1","4","8")
foreach ($CountNumber in $CountList) {                     
                  [void]$DropDownBox.Items.Add($CountNumber) # [void]to ignore the return value from the add method. "Items.Add" always returns the index of the new item in the list.
                  $DropDownBox.SelectedIndex = 0 #Default value for DropDownBox. The first item in list. Set to 1 by default.
                         } #end foreach

Yllä olen määritellyt, mitä listassa lukee. $CountList -muuttujan array (lista, jono, rivi, taulukko tms.) muodostuu listan sisällöstä.
Foreach silmukkarakenteen toteamus sanoo, että jokaista $CountNumber objektia kohden $CountList -arrayssa toteutetaan jokin tietty tehtävä tai toteamus:

$DropDownBox.Items.Add($CountNumber) Tarkoittaa, että jokainen $CountNumber objekti lisätään $DropDownBoxiin, eli siihen vetolaatikkooni.

Items.Add -metodi palauttaa itsestään listan objektien indexit suoraan konsoliin, kun skripti ajetaan, mikä koitui aluksi ärsyttäväksi. Tähän malliin:

index

Tämän vuoksi keksin myöhemmin laittaa sen eteen [void], jotta sitä ei turhaan tulostettaisi konsoliin. Sen tulostus voidaan estää muillakin tavoilla ks. https://blogs.msdn.microsoft.com/powershell/2006/07/31/suppressing-return-values-in-powershell-functions/

$DropDownBox.SelectedIndex = 0 määrittelee listan oletusindeksiksi nollan, mikä tarkoittaa itseasiassa listan objektia “1”, sillä indeksi alkaa nollasta ylöspäin.

Varmasti joku ohjelmoija selittäisi nämä asiat ja termit paremmin, mutta parempaan en nyt suomeksi kykene varsinkaan ilman aiempaa ohjelmointitaustaa.
Tulos:
boc

Button

Oleellinen osa ohjelmaa on se painike, jota käyttäjä painaa, kun haluaa kohteen pingauksen käynnistyvän. Nappula käynnistää painettaessa funktion PingInfo.

#Ping Button. Calls for function PingInfo.
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(400,75)
$Button.Size = New-Object System.Drawing.Size(110,40)
$Button.Text = "Ping"
$Button.Font = "Microsoft Sans Serif, 10, style=Bold"
$Button.Add_Click({PingInfo}) #Action triggered by pressing the button.
$Form.Controls.Add($Button)

Tulos:
button

Main Formin aktivointi

Aktivoi ohjelman pääikkuna, jonka sisään muut komponentit on rakennettu:

$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog() #activates the form
########## GUI Ends Here ##########
<##########== Script ends here ==##########>

Graafinen käyttöliittymä loppuu tähän.


PingInfo -funktio:

Tämä funktio kutsutaan nappia painettaessa. Se tekee seuraavat asiat:

  1. Lukee InputBoxista käyttäjän syöttämän palvelimen numeron ja lähettää sen $TargetWorld -muuttujaksi.
  2. Tarkistaa, montako ICMP-sanomaa käyttäjä halusi lähetettävän pelipalvelimelle. Arvo lähetetään $Count -muuttujaksi ja oletuksena sen arvo on 1, ellei käyttäjä ole sitä muuttanut.
  3. Suoritetaan komento: “ping oldschool$TargetWorld.runescape”, jossa $TargetWorld oli käyttäjän InputBoxiin syöttämä kaksinumeroinen luku. -n $Count kertoo komennossa montako pakettia lähetetään. Lisäksi out-string -cmdlet tarvitaan, sillä muuten tuloksien tulostus näyttää tältä:tulostus
  4. Lopuksi muuttujan $PingResult tulokset lähetetään OutputBoxiin ja funktio loppuu.
########## Functions ##########
function PingInfo {
#Get user input
     $TargetWorld = $InputBox.text;
#Get selected ping count
     $Count = $DropDownBox.SelectedItem.ToString()
#Ping the server
     $PingResult = ping oldschool$TargetWorld.runescape.com -n $Count| fl | out-string;
#Output the results
     $OutputBox.text = $PingResult                     
                       } #End PingInfo
########## End Functions ##########

Tämä ajetaan siis aina nappia painamalla.

Lopputulos:

Lopputuloksena täysin toimiva kokonaisuus. Käyttäjä syöttää pelipalvelimen numeron tekstikenttään ja painaa nappia. Ohjelma lähettää ICMP-paketteja käyttäjän haluaman määrän ja palauttaa niihin vastaukset suureen tekstikenttään.
lopputulos
Tarkoituksena olisi vielä myöhemmin lisätä tuohon toinen nappi, joka pingaisi automaattisesti kaikki palvelimet ja palauttaisi käyttäjälle tulokset.
Koko skriptin lähdekoodi: https://github.com/PyhaMarkus/OSRSPingTool


PowerShell skripti suoritettavaksi .exe-tiedostoksi:

PowerShell ei oletuksena itsenäisesti tue .ps1 -skriptien muuntamista suoritettaviksi .exe -tiedostoiksi. Harvemmin tähän on kyllä tarvettakaan, mutta mielestäni oli mielenkiintoista kokeilla, kuinka helposti tämä onnistuisi.
Löysin PS2EXE-GUI -nimisen skriptin tähän hommaan. Alkuperäinen tekijä Ingo Karstein ei enää päivitä tekelettään, joten Markus Scholtes jatkoi työtä pitemmälle. Skripti tukee PowerShell vitosta, jota aiempi ei tehnyt. Siihen on lisätty muitakin uusia ominaisuuksia sitemmin.
Lataus: https://gallery.technet.microsoft.com/scriptcenter/PS2EXE-GUI-Convert-e7cb69d5
Skriptin ajamiseksi tietokoneella täytyy olla sallittu ulkoisten skriptien ajaminen:

PS C:\Users\Markus> Set-ExecutionPolicy Unrestricted

exelist
Nyt, kun skripti on ladattu ja ExecutionPolicy on asetettu, skripti voidaan ajaa.

C:\Users\Markus\Desktop\TestiKansio\ps2exe.ps1 -inputFile C:\Users\Markus\Desktop\OSRSPing\OSRSPingTool.ps1 -outputFile C:\Users\Markus\Desktop\OSRSPing\OSRSPingTool.exe -noConsole -iconFile C:\Users\Markus\Desktop\OSRSPing\favicon.ico

-inputFile -parametri määrittelee alkuperäisen PowerShell -skriptin, jota ollaan muuntamassa.
-ouputFile määrittelee kohteen, eli minkäniminen tiedosto tuosta .ps1-skriptistä halutaan.
-noConsole -parametri estää PowerShell konsoli-ikkunan avautumisen, kun ohjelma ajetaan.
-iconFile -parametrillä voidaan määritellä kuvake ohjelmalle.
Skripti sisältää muitakin ominaisuuksia, mutta itse tarvitsen nyt vain noita neljää parametriä.
Koska ExecutionPolicy on minulla Unrestricted, PowerShell varmistaa silti vielä, että haluanko todella ajaa skriptin. Painoin [R] Run once vaihtoehtoa.
tehty
Kaikki toimi toivotusti ja kansiossani oli nyt suoritettava OSRSPingTool.exe, joka toimii aivan samalla tavalla, kuten aiemmin PowerShell skriptinä.
properties
Käynnistettynä:
tulos1
Sitten vielä lopuksi testauksena OSRS maan numero 336 pingaus neljästi:
tulos2
Toimii vallan mainiosti.


Lähteet:

PoshGUI. Online PowerShell GUI Designer.
# Hyvä tapa ottaa alkuun selvää, miten erilaiset GUI-komponentit toimivat.
https://poshgui.com/
Sysadminemporium 2012. PowerShell GUI – Front end for your scripts – Episode 1.
https://sysadminemporium.wordpress.com/2012/11/26/powershell-gui-front-end-for-your-scripts-episode-1/
Sysadminemporium 2012. PowerShell GUI – Front end for your scripts – Episode 2.
https://sysadminemporium.wordpress.com/2012/11/27/powershell-gui-for-your-scripts-episode-2/
MSDN Microsoft. Creating a read-only textbox (Windows Forms).
https://msdn.microsoft.com/en-us/library/aa983585(v=vs.71).aspx
Sapien 2011. Spotlight on textbox control.
https://www.sapien.com/blog/2011/06/13/primalforms-2011-spotlight-on-the-textbox-control/
Microsoft Technet. Windows PowerShell Tip of the Week.
https://technet.microsoft.com/en-us/library/ff730941.aspx
PowerShellDevTools 2010. How to deal with ComboBox control.
https://powershelldevtools.wordpress.com/2010/07/20/how-to-deal-with-a-combobox-control/
Admin-Tools. Combobox.
http://admin-tools.com/note_pages/notes.php?subject=020-Software&category=030-Sapien-Powershell-Studio
FoxDeploy 2013. Continued: Creating a GUI Natively For Your PowerShell Tools Using .NET Methods.
https://foxdeploy.com/2013/10/30/continued-creating-a-gui-natively-for-your-powershell-tools-using-net-methods/
Time’s Land of IT 2010. Custom icons in Poweshell winforms.
https://powertoe.wordpress.com/2010/03/01/custom-icons-in-powershell-winforms/
Technet Microsoft 2015. How to build a form in PowerShell?
https://gallery.technet.microsoft.com/scriptcenter/How-to-build-a-form-in-7e343ba3
Reddit.com/r/PowerShell 2016. Combobox fill printing sequential numbers to the console.
Linkki
Stackoverflow 2010. Prevent ArrayList.Add() from returning the index.
https://stackoverflow.com/questions/2149159/prevent-arraylist-add-from-returning-the-index
SS64 ForEach (loop statement)
https://ss64.com/ps/foreach.html
MSDN Microsoft 2017. Out-String.
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.utility/out-string
MSDN Microsoft 2015. Suppressing return values in PowerShell functions.
https://blogs.msdn.microsoft.com/powershell/2006/07/31/suppressing-return-values-in-powershell-functions/
MSDN Microsoft 2005. How to set a default value for a Combo Box?
https://social.msdn.microsoft.com/Forums/windows/en-US/1bbfc2b0-f099-4572-be70-e70a27ec2eed/how-to-set-a-default-value-for-a-combo-box?forum=winforms
Technet Microsoft 2017. PS2EXE-GUI: “Convert PowerShell Scripts to EXE Files with GUI.
https://gallery.technet.microsoft.com/scriptcenter/PS2EXE-GUI-Convert-e7cb69d5


TÄTÄ DOKUMENTTIA SAA KOPIOIDA JA MUOKATA GNU GENERAL PUBLIC LICENSE (VERSIO 3 TAI UUDEMPI) MUKAISESTI. HTTP://WWW.GNU.ORG/LICENSES/GPL.HTML
MARKUS PYHÄRANTA

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top