Monthly Archives: October 2015

Creating PowerShell GUIs

I’m old school. I’m a big fan of the “Real men don’t click” club!

My PowerShell bestie, Michaja van der Zouwen, is all about the GUI! We would go back and forth about to GUI or not to GUI… Good times…

We got a great introduction in creating PowerShell GUI app at the recent DuPSUG meeting by none other than June Blender! Just google June Blender… We’re being spoiled here!

Back in the days June was also part of the “Real (wo)men don’t click” club. So what changed? I can totally relate to June’s story about providing a script that could resolve a contractors problem. Now you would think the manager would be grateful eh? Nope! They weren’t interested in learning or using PowerShell even if it solved their problem. The idea was too daunting for them. Enter GUI.

GUI took away that initial fear of learning something new. “Just run this and click this button!” Doesn’t get easier than that eh? So should we all be creating GUIs? Well you should at least know how to 😉 Hence the workshop!

So I’m not against GUI, but it is a different mindset when creating a GUI app. You really need to think in events. You still need to validate parameters, but you need to anticipate what a user’s next move could be. The user’s move needs to be processed by ‘event-handlers’. With a script I have some parameters, I validate them and I’m good! With a GUI you need to think ahead of what could happen if…

We used Sapien PowerShell Studio to create a small GUI app. June gave us some excellent Gotcha and Aha tips! Sapien PowerShell Studio makes creating the GUI easy! Once the GUI interface was created we added the script logic. The event-handlers are basically script blocks!

Here’s where you need to think ahead:

What do I want happen if the textbox value is empty? Then you shouldn’t be able to click the button. Ok… But what if someone enters spaces only? We should validate that and make sure the value isn’t empty or spaces. But what if there’s a lingering space somewhere? Make sure Trim your textbox value.

Sapien PowerShell Studio makes it easy to export the complete script. Just go to Deploy -> “Export To Clipboard”. Paste in ISE and run it! Works! Have look at the script, there’s a lot going on under the hood… Imagine creating that by hand…


#————————————————————————
# Source File Information (DO NOT MODIFY)
# Source ID: 0e2b4026-3710-4ec0-8aef-f79ad0cbfae0
# Source File: C:\scripts\forms\demo.psf
#————————————————————————
<#
.NOTES
——————————————————————————–
Code generated by: SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.95
Generated on: 10/15/2015 12:24 AM
Generated by: Irwin
Organization: Centric
——————————————————————————–
.DESCRIPTION
GUI script generated by PowerShell Studio 2015
#>
#———————————————-
#region Application Functions
#———————————————-
#endregion Application Functions
#———————————————-
# Generated Form Function
#———————————————-
function Call-demo_psf {
#———————————————-
#region Import the Assemblies
#———————————————-
[void][reflection.assembly]::Load('mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
[void][reflection.assembly]::Load('System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
[void][reflection.assembly]::Load('System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089')
[void][reflection.assembly]::Load('System.ServiceProcess, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a')
#endregion Import Assemblies
#———————————————-
#region Generated Form Objects
#———————————————-
[System.Windows.Forms.Application]::EnableVisualStyles()
$formGetServices = New-Object 'System.Windows.Forms.Form'
$buttonGetService = New-Object 'System.Windows.Forms.Button'
$textboxComputerName = New-Object 'System.Windows.Forms.TextBox'
$labelComputerName = New-Object 'System.Windows.Forms.Label'
$buttonClose = New-Object 'System.Windows.Forms.Button'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$InitialFormWindowState = New-Object 'System.Windows.Forms.FormWindowState'
#endregion Generated Form Objects
#———————————————-
# User Generated Script
#———————————————-
$formGetServices_Load={
#TODO: Initialize Form Controls here
#$buttonGetService.Enabled = $false
$textboxComputerName.Text = $env:COMPUTERNAME
}
$buttonClose_Click={
#TODO: Place custom script here
$formGetServices.Close()
}
$buttonOK_Click={
#TODO: Place custom script here
}
$buttonGetService_Click={
#TODO: Place custom script here
try{
Get-Service -ComputerName $textboxComputerName.Text.Trim() | Out-GridView -Title "Get Services on $($textboxComputerName.Text.Trim())"
}
catch{
#[void][reflection.assembly]::Load("System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
[void][System.Windows.Forms.MessageBox]::Show("Can't get services on $($textboxComputerName.Text)","ComputerName")
}
}
$textboxComputerName_TextChanged={
#TODO: Place custom script here
$buttonGetService.Enabled = $false
if ( $textboxComputerName.Text.Trim() -ne "" ) {
$buttonGetService.Enabled = $true
}
}
# –End User Generated Script–
#———————————————-
#region Generated Events
#———————————————-
$Form_StateCorrection_Load=
{
#Correct the initial state of the form to prevent the .Net maximized form issue
$formGetServices.WindowState = $InitialFormWindowState
}
$Form_Cleanup_FormClosed=
{
#Remove all event handlers from the controls
try
{
$buttonGetService.remove_Click($buttonGetService_Click)
$textboxComputerName.remove_TextChanged($textboxComputerName_TextChanged)
$buttonClose.remove_Click($buttonClose_Click)
$buttonOK.remove_Click($buttonOK_Click)
$formGetServices.remove_Load($formGetServices_Load)
$formGetServices.remove_Load($Form_StateCorrection_Load)
$formGetServices.remove_FormClosed($Form_Cleanup_FormClosed)
}
catch [Exception]
{ }
}
#endregion Generated Events
#———————————————-
#region Generated Form Code
#———————————————-
$formGetServices.SuspendLayout()
#
# formGetServices
#
$formGetServices.Controls.Add($buttonGetService)
$formGetServices.Controls.Add($textboxComputerName)
$formGetServices.Controls.Add($labelComputerName)
$formGetServices.Controls.Add($buttonClose)
$formGetServices.Controls.Add($buttonOK)
$formGetServices.AcceptButton = $buttonGetService
$formGetServices.ClientSize = '536, 123'
$formGetServices.FormBorderStyle = 'FixedDialog'
$formGetServices.MaximizeBox = $False
$formGetServices.MinimizeBox = $False
$formGetServices.Name = 'formGetServices'
$formGetServices.StartPosition = 'CenterScreen'
$formGetServices.Text = 'Get Services'
$formGetServices.add_Load($formGetServices_Load)
#
# buttonGetService
#
$buttonGetService.Location = '93, 88'
$buttonGetService.Name = 'buttonGetService'
$buttonGetService.Size = '350, 23'
$buttonGetService.TabIndex = 2
$buttonGetService.Text = 'GetService'
$buttonGetService.UseVisualStyleBackColor = $True
$buttonGetService.add_Click($buttonGetService_Click)
#
# textboxComputerName
#
$textboxComputerName.Location = '172, 26'
$textboxComputerName.Name = 'textboxComputerName'
$textboxComputerName.Size = '352, 20'
$textboxComputerName.TabIndex = 1
$textboxComputerName.add_TextChanged($textboxComputerName_TextChanged)
#
# labelComputerName
#
$labelComputerName.Location = '12, 29'
$labelComputerName.Name = 'labelComputerName'
$labelComputerName.Size = '125, 23'
$labelComputerName.TabIndex = 0
$labelComputerName.Text = 'ComputerName'
#
# buttonClose
#
$buttonClose.Location = '12, 88'
$buttonClose.Name = 'buttonClose'
$buttonClose.Size = '75, 23'
$buttonClose.TabIndex = 3
$buttonClose.Text = '&Close'
$buttonClose.UseVisualStyleBackColor = $True
$buttonClose.add_Click($buttonClose_Click)
#
# buttonOK
#
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '449, 88'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 4
$buttonOK.Text = '&OK'
$buttonOK.UseVisualStyleBackColor = $True
$buttonOK.add_Click($buttonOK_Click)
$formGetServices.ResumeLayout()
#endregion Generated Form Code
#———————————————-
#Save the initial state of the form
$InitialFormWindowState = $formGetServices.WindowState
#Init the OnLoad event to correct the initial state of the form
$formGetServices.add_Load($Form_StateCorrection_Load)
#Clean up the control events
$formGetServices.add_FormClosed($Form_Cleanup_FormClosed)
#Show the Form
return $formGetServices.ShowDialog()
} #End Function
#Call the form
Call-demo_psf | Out-Null

view raw

Demo-GUI.ps1

hosted with ❤ by GitHub

All the script logic can be found under ‘User Generated Script’ comment block. The rest PowerShell Studio took care of…

As an introduction to creating PowerShell GUI scripts, mission accomplished!

Here’s the link to June’s github repository for more information.

Thanks June it was a pleasure meeting you in person! I’m more open to the idea of creating a GUI around scripts, only after putting up a fight though… Old habits die hard… Hehe…

Ttyl,

Urv

 

Revisiting Exporting Data

Sup PSHomies!

I’ve been playing with different formats lately. I’d like to share a few thoughts on the subject if I may… For demo purposes I’ll be using the following cmdlets: Export-Csv, Export-Clixml and ConvertTo-Json!

Export-Csv

I’ve talked about my love for exporting to csv in the past. Here’s thing, exporting to CSV treats everything as a string. For reporting purposes this might not be an issue. When it comes to nested objects… Yeah… Then you’re better off exporting to XML. Incidentally Jeff Hicks has a great blog on this topic, you should definitely check it out! CSV is still my goto format because of reporting in Excel, although, I’ve been using Doug Finke’s ImportExcel module more and more! Doug’s module cuts out the middle man and can export to Excel without having to export as a CSV first. It does a whole lot more! Worth looking into!

Export-Clixml

Exporting a nested object is pretty straightforward using Export-Clixml. The structure isn’t pretty though. That was the main reason I didn’t use the cmdlet. Export-Clixml is great when used in combination with Import-Clixml, it restores your nested object without a hitch! You can export your results, send the file and import elsewhere for further processing if needed. When I think of xml, I immediately conjure up ideas of html reporting. The xml tags are too cryptic for any css style, I wouldn’t even know where to begin. I recently discovered PScribo (Thanks to the PowerShell Summit in Stockholm), a module by Ian Brighton! This made html reporting a breeze! All I did was import my XML file back into PowerShell to retrieve my nested object and I did the rest in PowerShell! That was awesome!

ConvertTo-Json

The ConvertTo-Json cmdlet has been introduced in PowerShell version 3.0. Back then I was a stickler for XML so I briefly looked at it and forgot all about it… That is until Azure Resource Manager came along. If you’re doing anything with Azure Resource Manager then Json should be on your radar. If you’re not convinced just look at the ARM Templates out there. Json is a lot easier on the eyes for sure. Still not conviced? Just google Json vs XML.

Ok here’s some code you can play with to get a general idea of what the possibilities are when exporting to different formats. Have a look at the Json and Xml, which would you prefer? That was rhetorical… 😉


<#
Author: I.C.A Strachan
Version:
Version History:
Purpose: Demo exporting/importing to/from csv, xml and json format
#>
[CmdletBinding()]
#region Nested Object for Demo purposes
$Win32_ComputerSystem = @(
'DNSHostName'
'Model'
'Manufacturer'
'Domain'
'DomainRole'
'PartOfDomain'
'SystemType'
)
$Win32_OperatingSystem = @(
'Version'
'BuildNumber'
'BuildType'
'OSLanguage'
'OSType'
'OSArchitecture'
'MUILanguages'
'OperatingSystemSKU'
'Organization'
'ProductType'
'ServicePackMajorVersion'
'ServicePackMinorVersion'
'SizeStoredInPagingFiles'
'SystemDevice'
'SystemDirectory'
'WindowsDirectory'
)
$Win32_BIOS = @(
'SMBIOSBIOSVersion'
'Manufacturer'
'Name'
'SerialNumber'
'Version'
'ReleaseDate'
)
function Get-WMIData {
[cmdletbinding()]
param(
[string]
[ValidateNotNullOrEmpty()]
$WMIClass,
[string[]]
[ValidateNotNullOrEmpty()]
$ComputerName,
[string[]]
[ValidateNotNullOrEmpty()]
$WMIProperties
)
$queryProperties =''
foreach ($property in $WMIProperties){
$queryProperties += "$property,"
}
#Remove last character from $queryProperties
$queryProperties = $queryProperties -replace ".$"
Get-wmiObject -Query "SELECT $queryProperties FROM $WMIClass" -ComputerName $ComputerName |
Select-Object $WMIProperties
}
$Inventory = @{}
$Inventory.ComputerSystem = Get-WMIData -WMIClass Win32_ComputerSystem -ComputerName LocalHost -WMIProperties $Win32_ComputerSystem
$Inventory.OperatingSystem = Get-WMIData -WMIClass Win32_OperatingSystem -ComputerName LocalHost -WMIProperties $Win32_OperatingSystem
$Inventory.BIOS = Get-WMIData -WMIClass Win32_BIOS -ComputerName LocalHost -WMIProperties $Win32_BIOS
#endregion
#region Export-Csv
$exportSplat = @{
NoTypeInformation = $true
Encoding = 'UTF8'
Delimiter = ';'
}
#Export each key to seperate csv File
Foreach ($key in $Inventory.Keys) {
if ( $Inventory.$key.Count -ne 0 ){
$exportSplat.Path = "$PSSCriptRoot\$($key).csv"
$Inventory.$key |
Export-Csv @exportSplat
}
}
#Import Csv for verfication
$importCsv = @{}
#Get csv Files
$csvFiles = Get-ChildItem -Filter *.csv -Path $PSSCriptRoot -File
$importSplat =@{
Delimiter = ';'
Encoding = 'UTF8'
}
Foreach ($file in $csvFiles) {
$importCsv.$($file.BaseName) = Import-Csv -Path $file.FullName @importSplat
}
#endregion
#region Export-Clixml
$Inventory |
Export-Clixml -Path "$PSSCriptRoot\Inventory.xml" -Encoding UTF8
#Import Xml for verfication
$importXml = Import-Clixml -Path "$PSSCriptRoot\Inventory.xml"
#endregion
#region Export to Json Format using ConvertTo-Json
$Inventory |
ConvertTo-Json |
Out-File "$PSSCriptRoot\Inventory.json" -Encoding utf8
#Import Json for verification
$importJson = Get-Content -Path "$PSSCriptRoot\Inventory.json" |
ConvertFrom-Json
#endregion
#region Compare the membertypes of Csv, Xml & Json
$importCsv.ComputerSystem | Get-Member #Everything is a string
$importXml.ComputerSystem | Get-Member #Typecast is preserved
$importJson.ComputerSystem | Get-Member #Typecast is preserved
if ($psISE) {
psedit "$PSSCriptRoot\Inventory.json"
psedit "$PSSCriptRoot\Inventory.xml"
}
else {
notepad.exe "$PSSCriptRoot\Inventory.json"
notepad.exe "$PSSCriptRoot\Inventory.xml"
}
#endregion

Bottom line

Export-Csv is best when you need to report  anything in Excel workbooks and you’re not worried about type. Everyone gets Excel.

Export-Clixml isn’t pretty but excellent when it comes to keeping the data metadata in tact. You can always import preserving the metadata and process further in PowerShell.

Use Json if you want to have a structured data set à la XML. Json is a lot friendlier than XML. I was also surprised that the cmdlet interpreted values to it’s best avail. False became Boolean as you would expect. Json is growing on me…

Hope it’s worth something to you…

Ttyl,

Urv