Windows forms, tips and trix – Buttons

Ever written a GUI to your application? Ever think there are alot of unnecessary lines?
Here is a smart way to add basic buttons without too many lines.

function CreateButton{
  param(
    $name,
    $text,
    $size,
    $location,
    $onclick,
    $Form
  )
  $name = New-Object System.Windows.Forms.Button
  $name.Text = "$text"
  $name.Location = "$location"
  $name.Size = "$size"
  $name.add_Click($onclick)
  $Form.Controls.Add($name)
}

This requires a specific type of code to be run on click. We declare a variable as code within curlybrackets as shown below. In this case we use System.Windows.Forms.SaveFileDialog and save a richtextbox named $consolewindow.

$SaveButton_Click = {
  $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
  $SaveFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
  $SaveFileDialog.ShowDialog()
  $ConsoleWindow.Text | Out-File $SaveFileDialog.FileName
  $SaveFileDialog.Dispose()
}

To add a button with this information we need to create a main form and the console window with the code below.

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

# Main form settings
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = "1000,620"
$Form.DesktopLocation = "100,100"
$Form.MaximizeBox = $False
$Form.ShowIcon = $False
$Form.FormBorderStyle = "FixedSingle"
$Form.Name = "Example"
$Form.Text = "Example"

# Console window settings
$ConsoleWindow = New-Object System.Windows.Forms.RichTextBox
$ConsoleWindow.Size = "1000,250"
$ConsoleWindow.Location = "0,320"
#$ConsoleWindow.ReadOnly = $True #commented out for manual input.
$ConsoleWindow.BackColor = "Black"
$ConsoleWindow.ForeColor = "White"
$Form.Controls.Add($Consolewindow)

I’ve added a few extra rows just to make it look good for the example.
Now let’s create a button

CreateButton -name "SaveButton" -text "Save to file" -size "100,30" -location "50,50" -form $form -onclick $SaveButton_Click

Then we load the forms window

$Form.ShowDialog()

 

Normally you might not have a use for this function but when creating dynamic buttons you could have a use for it. To create a range of buttons from an array, use something like this.

$x = 0
$y = 0
for($i=0; $i -lt 20;$i++){
  CreateButton -name $Buttons[$i] -text $Buttons[$i] -size "100,30" -location "$x,$y" -form $form -onclick $Button_Click[$i]
  $x += 100
  if($x -gt "900"){$y += 30;$x = 0}
}

This example takes the first 20 from an array and puts them on the main form in a orderly fashion as to not overcrowd the main window. The example has no practical use, but you could extract nestled objects and create dynamic buttons with a bit of work.

The full script would look like this:
Note that i’m using the same function for all buttons.

function CreateButton{
  param(
    $name,
    $text,
    $size,
    $location,
    $onclick,
    $Form
  )
  $name = New-Object System.Windows.Forms.Button
  $name.Text = "$text"
  $name.Location = "$location"
  $name.Size = "$size"
  $name.add_Click($onclick)
  $Form.Controls.Add($name)
}
$SaveButton_Click = {
  $SaveFileDialog = New-Object System.Windows.Forms.SaveFileDialog
  $SaveFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*"
  $SaveFileDialog.ShowDialog()
  $ConsoleWindow.Text | Out-File $SaveFileDialog.FileName
  $SaveFileDialog.Dispose()
}
[Void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")

# Main form settings
$Form = New-Object System.Windows.Forms.Form
$Form.ClientSize = "1000,620"
$Form.DesktopLocation = "100,100"
$Form.MaximizeBox = $False
$Form.ShowIcon = $False
$Form.FormBorderStyle = "FixedSingle"
$Form.Name = "Example"
$Form.Text = "Example"

# Console window settings
$ConsoleWindow = New-Object System.Windows.Forms.RichTextBox
$ConsoleWindow.Size = "1000,250"
$ConsoleWindow.Location = "0,320"
#$ConsoleWindow.ReadOnly = $True #commented out for manual input.
$ConsoleWindow.BackColor = "Black"
$ConsoleWindow.ForeColor = "White"
$Form.Controls.Add($Consolewindow)

$Buttons = @("1".."26")
$x = 0
$y = 0
for($i=0; $i -lt 20;$i++){
  if($buttons[$i] -eq $null){break}
  CreateButton -name $Buttons[$i] -text $Buttons[$i] -size "100,30" -location "$x,$y" -form $form -onclick $SaveButton_Click
  $x += 100
  if($x -gt "900"){$y += 30;$x = 0}
}

$Form.ShowDialog()

wget for windows

ever wanted to use wget in Powershell? This short function allows you to wget to local directory.

function wget($urlpath){
  $directory = (get-location).path

  $filename = $urlpath.Split("/")[-1]
  $file = $directory+"\"+$filename

  $webclient = New-Object System.Net.WebClient
  $webclient.DownloadFile($urlpath,$file)
}

Simply enter the function into your current powershell session and try it out.
wget http://upload.wikimedia.org/wikipedia/en/f/f4/The_Scream.jpg

If you want to permanently have access to this command add the function to the following file: “%windir%\system32\WindowsPowerShell\v1.0\profile.ps1

For more information on Powershell profiles, read this article:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb613488(v=vs.85).aspx

Powershell auto updater

I wrote this short script to keep local scripts up to date with server versions.

Makes it easier to handle scripts that are added with initial version through task sequence or other onetime distributions. The requirement is for the scripts to have the variable $CurrentVersion = <int/double>

Example script, located on the central/repository:

$CurrentVersion = 1.1
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$form = New-Object 'System.Windows.Forms.Form'
$form.showdialog()

If the local/current script contains $CurrentVersion = 1.0 or less it will get updated once the launcher has been run.
Launcher code below:

# Following variables control the paths used in script

# Retrieve the directory of script.
$Script:G_runDir = Split-Path -Parent $MyInvocation.MyCommand.Path

# Name of the script to update.
$Script:G_Script  = "ScriptPath.ps1"

# Where repository version is located.
$Script:G_RepositoryPath = "\\$env:userdnsdomain\Path\To\Script"

# Build paths for scripts. Do not change!
$Script:G_LocalScript = "$G_runDir\$G_Script"
$Script:G_CentralScript = "$G_RepositoryPath\$G_Script"
===========================================================================

Function MainFunction(){

    if(Test-Path $G_CentralScript){

        $LocalInfo = Get-Info -ScriptPath $G_LocalScript
        $CentralInfo = Get-Info -ScriptPath $G_CentralScript
        
        $LocalVersion = $LocalInfo.Version
        $LocalChecksum = $LocalInfo.Checksum

        $CentralVersion = $CentralInfo.Version
        $CentralChecksum = $CentralInfo.Checksum

        Write-Host "Local Version: $LocalVersion. Central Version: $CentralVersion"
        
        If($LocalChecksum -ne $CentralChecksum){
            If($LocalVersion -ge $CentralVersion){
                Write-Host "Local script corrupt"
            }
            try{
                Write-Host "Updating script"
                Copy-Item "$G_CentralScript" -Destination "$G_runDir" -Force
                Write-host "Script pdated"
            }
            catch{
                Write-Error $_
            }
        }Elseif($LocalChecksum -eq $CentralChecksum){
            Write-Host "Script up to date"
        }
    }    
    Else{
        Write-host "Local Version: $LocalVersion. Could not find $G_CentralScript."
    }
       
    Launch-Application
} # End Function

#----------------------------------------------------------------
# Gets version of scripts.
#----------------------------------------------------------------
Function Get-Info{
    Param(
        [Parameter(Mandatory=$true)]$ScriptPath
    )
    
    $Return = @{"Checksum" = "Null";"Version" = 0}
    $MD5 = New-Object 'System.Security.Cryptography.MD5CryptoServiceProvider'
    
    If(Test-Path $ScriptPath){

        Try{
            $Checksum = [System.BitConverter]::ToString($MD5.ComputeHash([System.IO.File]::ReadAllBytes($ScriptPath)))
            $Return.Checksum = $Checksum
        }
        Catch{
            Write-Error $_
        }

        $Script = (get-content $ScriptPath) | Where-Object{$_ -like "*APP_VER*"}
        if(!$Script){
            Return Write-Host "$ScriptPath does not contain any valid version information!"
        }
        else{
            Try{
                # Uses invoke-expression to set version variable.
                $ScriptBlock = $Script | Where-Object{$_.StartsWith("Set-Variable")}
                If($ScriptBlock.GetType().Name -eq "String"){
                    Invoke-Expression $ScriptBlock
                    If(($APP_VER.GetType() -eq [Double]) -or ($APP_VER.GetType() -eq [Int])){
                        $Return.Version = $APP_VER
                    }
                }
            }Catch{
                Write-Error $_
            }
        }
    }
    Return $Return
}

#----------------------------------------------------------------
# Prompts for input and launches script.
#----------------------------------------------------------------

Function Launch-Application{
    Try{
        .{Powershell.exe -File "$Script:G_LocalScript"}
    }
    Catch{
        Write-Error $_
    }
} # End function

#=== Call Main function =========================================================
#================================================================================

MainFunction

Just copy-paste this and change the paths and you should be good. If you’re running a console application you might want to change the “-Windowstyle” in Start-Script function to “normal”
Currently it waits until any key has been pressed but if you want it to launch instantly just comment out the line

[void]$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")