Building Cleaner, Responsive WPF Forms

In my first two posts on this subject, I was just getting started with learning responsive WPF form building. I’m here today to show you a better way to build a responsive WPF using runspaces that will do the exact same thing as my previous uploads showed. Just better.

This time, I won’t be putting the form into its own runspace. As I learnt you didn’t need to from JRV over on the TechNet forums. This has some benefits that I will very helpfully, briefly and probably incorrectly list below:

  • Don’t have to use syncHash when updating the form
  • One less runspace for the form
  • Having a form in its own runspace creates additional overhead and possible errors

So to display the form I would use something like the below:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

$Window.ShowDialog() | Out-Null

Which will give us the below form:

But what if I want the button press to make the label number increase? I would use something like this on the button press:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

$Button.Add_Click({
    $counter = 1 

    do{
        Start-Sleep -Milliseconds 5
        $label.content = $counter
        [System.Windows.Forms.Application]::DoEvents()
        $counter += 1
    }while ($counter -le 5000)

})

$Window.ShowDialog() | Out-Null

This produces a form which increases the label up to 5000 when the button is pressed. You can see this below:

But what if I want to actually run something in a runspace. For example, test the connection to google.com? Then I would use the below code:

[xml]$xml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="Counter" Height="119" Width="351.5" ResizeMode="CanMinimize" WindowStartupLocation="CenterScreen">
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
        <Label Name="Label" Content="0" HorizontalAlignment="Left" Margin="16.666,9.333,0,0" VerticalAlignment="Top" FontSize="18"/>
        <Button Name="Button" Content="Start" HorizontalAlignment="Center" VerticalAlignment="Top" Width="75" Margin="123.25,63,123.25,0"/>
    </Grid>
</Window>
"@

$Reader=(New-Object System.Xml.XmlNodeReader $xml)
$Window=[Windows.Markup.XamlReader]::Load($Reader)

$Label = $Window.FindName("Label")
$Button = $Window.FindName("Button")

$Button.Add_Click({
    $syncHash = [hashtable]::Synchronized(@{})
    $Runspace = [runspacefactory]::CreateRunspace()
    $Runspace.ApartmentState = "STA"
    $Runspace.ThreadOptions = "ReuseThread"
    $Runspace.Open()
    $Runspace.SessionStateProxy.SetVariable("syncHash",$syncHash)

    $powershell = [powershell]::Create().AddScript({
        $connection = Test-Connection -ComputerName google.com -Count 5
        $syncHash.output = [math]::Round(($connection.ResponseTime | Measure-Object -Average).Average)
    })

    $powershell.Runspace = $Runspace

    $Object = $powershell.BeginInvoke()

    do {
        Start-Sleep -Milliseconds 50
        [System.Windows.Forms.Application]::DoEvents()
    }while(!$Object.IsCompleted)

    $powershell.EndInvoke($Object)
    $powershell.Dispose()

    $label.Content = $syncHash.output
})

$Window.ShowDialog() | Out-Null

All the above examples will stay responsive whilst the action is performed. There are a couple of different methods to do this as you can see above, for anything that takes some time to complete, a runspace is needed. But when you are updating the form quickly, like the counter, then no runspace is needed. 

Enjoy!

Adding an BASE64 Icon to a WPF GUI

Nice and simple one today. I’m going to show you how to add an icon to a WPF GUI in PowerShell using BASE64 data.

I won’t be putting my BASE64 data into this post since its a MASSIVELY long string of characters but it should look something like this ” iVBORw0KG…”

First, we need to create a new variable to hold the data and then use the bitmapimage object to convert the data into a usable icon. You can see this below:

[string]$script:base64=@"
iVBORw0KGgo...
"@

$script:bitmap = New-Object System.Windows.Media.Imaging.BitMapImage
$bitmap.BeginInit()
$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64)
$bitmap.EndInit()
$bitmap.Freeze()

After this we can simply assign the new icon to the form using the code below:

$window.Icon = $bitmap

Enjoy!

Responsive PowerShell WPF Form Introduction #2

Following on from my last post, I’m going to show you how to update a textbox using a button on the same form. I will be adding the following code starting on line 38:

#BUTTON LOGIC
$syncHash.Button.Add_Click({

    $syncHash.Window.Dispatcher.Invoke(
        [action]{
            $syncHash.TextBox.AppendText("This is a test")
        }
    )
})

This is fairly basic in what it does. It just adds “This is a test” to the textbox. Say if I want the button to run a task and then update the textbox with the results, but the results took a long time to come, the form would freeze. This is because whatever command you run in the same runspace as the GUI, takes controls and stops the GUI being responsive.

So, what I’m going to do is ping google 5 times, get the average from all of those and then update the textbox without the GUI becoming unresponsive. To do this, I’m going to create a new runspace and add the code I want to run. You can see this below:

#BUTTON LOGIC
$syncHash.Button.Add_Click({
    #ASSIGNING HOST VARIABLE
    $syncHash.host = $Host
    #CREATING NEW RUNSPACE
    $pingrunspace = [runspacefactory]::CreateRunspace()
    $pingrunspace.ApartmentState = "STA"
    $pingrunspace.ThreadOptions = "ReuseThread"
    $pingrunspace.Open()
    #PUTTING THE SYNCHASH VARIABLE INSIDE THE NEW RUNSPACE
    $pingrunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)

    #THIS IS THE CODE THAT WILL BE EXECUTED IN THE NEW RUNSPACE
    $code = {

        #CONNECTION TO GOOGLE AND CALCULATING AVERAGE IN NEW RUNSPACE
        $connection = Test-Connection -ComputerName google.co.uk -Count 5
        $average = [math]::Round(($connection.responsetime | Measure-Object -Average).Average)
        #UPDATING THE TEXTBOX WITH CONNECTION AVERAGE IN NEW RUNSPACE
        $syncHash.Window.Dispatcher.Invoke(
            [action]{
                $syncHash.TextBox.AppendText($average)
            }
        )

    }
        
    #ADDING AND RUNNING THE CODE IN THE NEW RUNSPACE
    $PSInstance = [powershell]::Create().AddScript($code)
    $PSinstance.Runspace = $pingrunspace
    $job = $PSinstance.BeginInvoke()
    
})

This will run the code in a separate runspace to the GUI and allow you to interact with it whilst the commands complete in the background.

Just in case you want the entire this, this is what the whole file looks like 🙂

#CREATE HASHTABLE AND RUNSPACE FOR GUI
$syncHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"         
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)      
#BUILD GUI AND ADD TO RUNSPACE CODE
$psCmd = [PowerShell]::Create().AddScript({   
    [xml]$xaml = @"
    <Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Name="Window" Height="400" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Button Name="Button" Content="Press" Height="200" Width="580" Grid.Row="0" Grid.Column="0" />
        <TextBox Name="Textbox" Height="200" Width="580" Grid.Row="1" Grid.Column="0" />
    </Grid>
</Window>
"@
  
    #INTERPRET AND LOAD THE GUI
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )

    #EXTRACT THE CONTROLS FROM THE GUI
    $syncHash.TextBox = $syncHash.window.FindName("Textbox")
    $syncHash.Button = $syncHash.Window.FindName("Button")

    #BUTTON LOGIC
    $syncHash.Button.Add_Click({

        $syncHash.host = $Host
        $pingrunspace = [runspacefactory]::CreateRunspace()
        $pingrunspace.ApartmentState = "STA"
        $pingrunspace.ThreadOptions = "ReuseThread"
        $pingrunspace.Open()
        $pingrunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)

        $code = {

            $connection = Test-Connection -ComputerName google.co.uk -Count 5
            $average = [math]::Round(($connection.responsetime | Measure-Object -Average).Average)
            $syncHash.Window.Dispatcher.Invoke(
                [action]{
                    $syncHash.TextBox.AppendText($average)
                }
            )

        }

        $PSInstance = [powershell]::Create().AddScript($code)
        $PSinstance.Runspace = $pingrunspace
        $job = $PSinstance.BeginInvoke()
    })


    #FINALISE AND CLOSE GUI RUNSPACE UPON EXITING
    $syncHash.Window.ShowDialog() | Out-Null
    $syncHash.Error = $Error
    $Runspace.Close()
    $Runspace.Dispose()
    
})
#LOAD RUNSPACE WITH GUI IN
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()

Enjoy!

Responsive PowerShell WPF Form Introduction #1

Hooooly jebus chwist! This took a LONG time for me to get my head around and an even longer time to implement and get working (still breaking it every minute!). I used this website and this website to help me learn the basics.

Today, I’m going to show you how to create a responsive WPF from using PowerShell. This utilises runspaces and a synchronised hashta… never mind the technical stuff!

This is the code that I used:

#CREATE HASHTABLE AND RUNSPACE FOR GUI
$syncHash = [hashtable]::Synchronized(@{})
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"         
$newRunspace.Open()
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)      
#BUILD GUI AND ADD TO RUNSPACE CODE
$psCmd = [PowerShell]::Create().AddScript({   
    [xml]$xaml = @"
    <Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Name="Window" Height="400" Width="600">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>

        <Button Name="Button" Content="Press" Height="200" Width="580" Grid.Row="0" Grid.Column="0" />
        <TextBox Name="Textbox" Height="200" Width="580" Grid.Row="1" Grid.Column="0" />
    </Grid>
</Window>
"@
  
    #INTERPRET AND LOAD THE GUI
    $reader=(New-Object System.Xml.XmlNodeReader $xaml)
    $syncHash.Window=[Windows.Markup.XamlReader]::Load( $reader )

    #EXTRACT THE CONTROLS FROM THE GUI
    $syncHash.TextBox = $syncHash.window.FindName("Textbox")
    $syncHash.Button = $syncHash.Window.FindName("Button")

    #FINALISE AND CLOSE GUI RUNSPACE UPON EXITING
    $syncHash.Window.ShowDialog() | Out-Null
    $syncHash.Error = $Error
    $Runspace.Close()
    $Runspace.Dispose()
    
})
#LOAD RUNSPACE WITH GUI IN
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()

Using this, you can then use the same command prompt used to launch the script to change the form. E.g. to change the text in the textbox we would use:

$syncHash.Window.Dispatcher.Invoke(
    [action]{$syncHash.TextBox.Text = "Updated text here"}
)

In another post, I’ll show you how to update the textbox using a button on the same form. Exciting stuff, right?

Leave a comment if you have any questions or issues. Enjoy!

LAPS WinForm 2

New and improved LAPS WinForm because the original one, found here, was kind of crap. It didn’t handle exceptions very well and I don’t think the group policy update worked at all after some further debugging.

I am please to present the new GUI for LAPS:

The best place to download this from would be my TechNet gallery

Enjoy!

SharePoint Group Membership WinForm

This is a little WinForm I created that would output the group membership for a domain user or FBA (Forms-Based Authentication) user on SharePoint.

This is what the form looks like, it gives the option for a domain or FBA user and also checked if the user exists before trying to get the relevant information:

The form first checks if CredSSP is configured on your machine to delegate your credentials to the SharePoint server. The form then loads, waits for your input, validates your input and finally collects the group information for your input.

And finally, this is the code for the Winform. I’ve removed some details as they need to be filled in by you. Enjoy!

#CHECKING CREDSSP SETTINGS
if ((Get-Item  WSMan:\localhost\Client\Auth\CredSSP).value -eq $false){
    #CREDSSP NOT CONFIGURED, EXITING
    Write-Host @"
    
CredSSP is not configured!

Please open an elavated PowerShell prompt and run:

Enable-WSManCredSSP -Role client -DelegateComputer sandsharepointf

"@
    Exit
}else{}

#LOADING ASSEMBLIES
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()

#ICON FOR THE FORM
[string]$icon64=@"
#base64data
"@

#CONVERTING BASE 64 ICON INTO GRAPHIC
$iconstream = [System.IO.MemoryStream][System.Convert]::FromBase64String($icon64)
$iconbmp = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($iconstream)
$iconhandle = $iconbmp.GetHicon()
$icon = [System.Drawing.Icon]::FromHandle($iconhandle)

#FORM
$SharePoint_Membership_Form                 = New-Object system.Windows.Forms.Form
$SharePoint_Membership_Form.ClientSize      = '400,278'
$SharePoint_Membership_Form.text            = "SharePoint Membership"
$SharePoint_Membership_Form.TopMost         = $false
$SharePoint_Membership_Form.StartPosition   = "centerscreen"
$SharePoint_Membership_Form.Icon            = $icon
$SharePoint_Membership_Form.FormBorderStyle = "Fixed3D"

#USERNAME LABEL
$Username_Label                  = New-Object system.Windows.Forms.Label
$Username_Label.text             = "Enter a username:"
$Username_Label.AutoSize         = $true
$Username_Label.width            = 25
$Username_Label.height           = 10
$Username_Label.location         = New-Object System.Drawing.Point(146,12)

#USERNAME TEXTBOX
$Username_Textbox                = New-Object system.Windows.Forms.TextBox
$Username_Textbox.multiline      = $false
$Username_Textbox.width          = 175
$Username_Textbox.height         = 20
$Username_Textbox.location       = New-Object System.Drawing.Point(115,33)

#VARIABLE FOR KEYDOWN
$Username_Textbox_keydown = {}

#KEYDOWN ASSIGNED
$Username_Textbox_keydown = [System.Windows.Forms.KeyEventHandler]{
    if ($_.keycode -eq 'Enter'){
        $Search_Button.PerformClick()
    }
}

#REGISTER KEYDOWN HANDLER TO USERNAME TEXTBOX
$Username_Textbox.add_keydown($Username_Textbox_keydown)

#DOMAIN RADIO BUTTON
$Domain_User_RB                  = New-Object system.Windows.Forms.RadioButton
$Domain_User_RB.text             = "Domain User"
$Domain_User_RB.AutoSize         = $true
$Domain_User_RB.width            = 104
$Domain_User_RB.height           = 20
$Domain_User_RB.location         = New-Object System.Drawing.Point(120,64)
$Domain_User_RB.Checked          = $true

#SHAREPOINT FBA USER RADIO BUTTON
$FBA_User_RB                     = New-Object system.Windows.Forms.RadioButton
$FBA_User_RB.text                = "FBA User"
$FBA_User_RB.AutoSize            = $true
$FBA_User_RB.width               = 104
$FBA_User_RB.height              = 20
$FBA_User_RB.location            = New-Object System.Drawing.Point(215,64)

#SEARCH BUTTON
$Search_Button                  = New-Object system.Windows.Forms.Button
$Search_Button.text             = "Search"
$Search_Button.width            = 60
$Search_Button.height           = 30
$Search_Button.location         = New-Object System.Drawing.Point(171,89)

#SEPERATOR LINE
$Seperator_Label                 = New-Object system.Windows.Forms.Label
$Seperator_Label.text            = ""
$Seperator_Label.AutoSize        = $false
$Seperator_Label.BorderStyle     = "Fixed3D"
$Seperator_Label.width           = 390
$Seperator_Label.height          = 2
$Seperator_Label.location        = New-Object System.Drawing.Point(5,124)

#OUTPUT TEXTBOX
$Output_Textbox                 = New-Object System.Windows.Forms.TextBox
$Output_Textbox.Multiline       = $true
$Output_Textbox.Width           = 390
$Output_Textbox.Height          = 142
$Output_Textbox.Location        = New-Object System.Drawing.Point(5,131)
$Output_Textbox.ReadOnly        = $true
$Output_Textbox.ScrollBars      = "vertical"

#ADDING CONTROLS TO FORM
$SharePoint_Membership_Form.controls.AddRange(@($Domain_User_RB,$FBA_User_RB,$Seperator_Label,$Username_Label,$Username_Textbox,$Search_Button,$Output_Textbox))

$Search_Button.add_click({

    $Output_Textbox.Text = ""

    #DATE FOR OUTPUT
    $date = Get-Date    
    $username_value = $Username_Textbox.Text
    $Username_Prefix = $null
    $location = #base location

    #SETTING SEARCH VALUES BACK TO FALSE
    $Search_On_AD_User = $false
    $Search_On_FBA_User = $false

    #CHECKING IF USERNAME TEXTBOX IS EMPTY
    if ($Username_Textbox.Text.Length -le 0){
        #IF EMPTY, VARIABLE IS FALSE
        $Output_Textbox.AppendText("$date - $Username cannot be empty! `n")
        $Username_Not_Empty = $false
    }else{
        $Username_Not_Empty = $true
        $Output_Textbox.Text = ""
    }

    #RUNS IF DOMAIN USER RADIO BUTTON IS CHECKED
    if ($Domain_User_RB.Checked -and $Username_Not_Empty){
        try{
            $Output_Textbox.AppendText("$date - Searching for $username_value `n")
            Get-ADUser -Identity $username_value

            $Output_Textbox.AppendText("$date - Found user! `n")

            $Search_On_AD_User = $true
            $Search_On_FBA_User = $false

            $Username_Found = $true

        }catch{
            $Output_Textbox.AppendText("$date - Cannot find domain user `n")
            $Username_Found = $false
        }
    }

    #RUNS IF FBA USER RADIO BUTTON IS CHECKED
    if ($FBA_User_RB.Checked -and $Username_Not_Empty){
        
        $SPAdmin = "sharepoint_admin_user"
        $credential = New-Object System.Management.Automation.PSCredential $SPAdmin,  (Get-Content "$location\sharepoint_admin_user_encrypted_password.txt" | ConvertTo-SecureString )

        $sb = {
            $username = $args[0]
            Add-PSSnapin microsoft.sharepoint.PowerShell
            $user = Get-SPUser -Limit All -Web http://SHAREPOINTSERVER | 
                Where-Object {$_.loginname -like "i:0#.f|fbamembershipprovider|$username"}

            return $user
        }

        $Output_Textbox.AppendText("$date - Trying to find $username_value... `n")

        $invokeoutputfbasearch = Invoke-Command -ScriptBlock $sb -ComputerName SHAREPOINTSERVER -Authentication Credssp -Credential $credential -ArgumentList $username_value

        if ($invokeoutputfbasearch){
            #FOUND USER
            $Username_Found = $true
            $Search_On_FBA_User = $true
            $Search_On_AD_User = $false
            $Output_Textbox.AppendText("$date - Found FBA user!`n")
        }else{
            #NOT FOUND USER
            $Username_Found = $false
            $Output_Textbox.AppendText("$date - Cannot find FBA user `n")
        }
    }

    #ONLY RUNS IF BELOW CONDITIONS ARE MET
    if ($Username_Found -and $Username_Not_Empty){
        #ASSIGNING THE RIGHT USERNAME FORMAT
        if ($Search_On_AD_User){
            $Username_Prefix = "*|DOMAIN_NAME\"
        }else{
            $Username_Prefix = "i:0#.f|fbamembershipprovider|"
        }

        $SPAdmin = "sharepoint_admin_user"
        $credential = New-Object System.Management.Automation.PSCredential $SPAdmin,  (Get-Content "$location\sharepoint_admin_user_encrypted_password.txt" | ConvertTo-SecureString )

        $sb = { 
            $groups = $null
            $prefix = $args[0]
            $username = $args[1]
            Add-PSSnapin Microsoft.SharePoint.PowerShell
            $user = get-SPUser -limit all -web http://SHAREPOINTSERVER | 
                Where-Object { $_.loginname -like "$prefix$username" }
                $SPGroups = get-spsite -limit all | 
                    Select-Object -ExpandProperty rootweb | 
                    Select-Object -ExpandProperty siteusers | 
                    Where-Object { $user.userlogin -eq $_.loginname } | 
                    Select-Object -ExpandProperty groups | 
                    Select-Object -ExpandProperty name
            foreach ($i in $SPGroups){
                $groups = $groups + "     - $i `r`n"
            }
        return $groups
        }

        $Output_Textbox.AppendText("$date - Collecting group info on $username_value... `n")

        $InvokeOutputfinal = Invoke-Command -ScriptBlock $sb -ComputerName SHAREPOINTSERVER -Authentication Credssp -Credential $credential -ArgumentList $Username_Prefix,$username_value

        $Output_Textbox.AppendText("`n")
        $Output_Textbox.AppendText("$InvokeOutputfinal")

    }else{#THIS SERVES ONLY AS A TRAP TO STOP ANYTHING RUNNING
    }
})

#DISPLAYING FORM
[void]$SharePoint_Membership_Form.ShowDialog()

Enumerating PowerShell Options

This is quite a handy trick that I use when designing or just fiddling around with what I can do with PowerShell WinForms. The last time I used this was to get all the possible colours I could use for my form background, and also check the possible options for my border on a panel.

So if I wanted to find all the possible colours available for my forms background, I would use the following:

[enum]::GetValues([System.ConsoleColor])

Black
DarkBlue
DarkGreen
DarkCyan
DarkRed
DarkMagenta
DarkYellow
Gray
DarkGray
Blue
Green
Cyan
Red
Magenta
Yellow
White

or if I wanted to find out all the possible borders for my panel as I stated above, I would use the following:

[enum]::GetValues([System.Windows.Forms.BorderStyle])

None
FixedSingle
Fixed3D

Hope this helps, I know this is usually helpful for me. Enjoy!

LAPS Winform

*UPDATE*

I have created the *final* iteration of this WPF form which can be found here

*UPDATE*

I didn’t like having to remote desktop into my domain controller and couldn’t figure out if there was a LAPS tool included in RSAT tools so I decided just to make my own and to add some extra features.

I wanted the GUI to look pretty much identity to the actual LAPS GUI. You can see the difference below:

You might be able to see that I  changed the “Set” button to say “Set and Update”. This was because I wanted the form to also attempt to update the group policy settings on the computer so that it would get a new password a lot quicker than the original GUI.

There’s not much else I can say, I will leave the entire script below for you to copy and paste. You will need to add the domain controller for your environment in the $domaincontroller variable at the top of the script. I have converted this to an EXE and run whenever I need it, never skips a beat. Let me know how you get on with it. Enjoy!

#ADDING FORM ASSEMBLY
Add-Type -AssemblyName system.windows.forms

#ENTER DOMAIN CONTROLLER BELOW
$domaincontroller = ""

#BASE 64 ICON
[string]$icon64 = "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"

#CONVERTING BASE 64 ICON TO SOMETHING USEFUL
$iconstream = [System.IO.MemoryStream][System.Convert]::FromBase64String($icon64)
$iconbmp = [System.Drawing.Bitmap][System.Drawing.Image]::FromStream($iconstream)
$iconhandle = $iconbmp.GetHicon()
$icon = [System.Drawing.Icon]::FromHandle($iconhandle)

#LAPS UI FORM
$lapsform = New-Object system.windows.forms.form    
$lapsform.Size = New-Object System.Drawing.Size(400,320)
$lapsform.Text = "                                     LAPS UI         "
$lapsform.StartPosition = "centerscreen"
$lapsform.FormBorderStyle = "fixed3d"
$lapsform.Icon = $icon

#LAPS TEXTBOX LABEL
$lapsform_computername_textbox_label = New-Object System.Windows.Forms.Label
$lapsform_computername_textbox_label.Location = New-Object System.Drawing.Point(20,20)
$lapsform_computername_textbox_label.Size = New-Object System.Drawing.Size(100,15)
$lapsform_computername_textbox_label.Text = "ComputerName"
$lapsform.Controls.Add($lapsform_computername_textbox_label)

#LAPS TEXTBOX
$lapsform_computername_textbox = New-Object System.Windows.Forms.TextBox
$lapsform_computername_textbox.Location = New-Object System.Drawing.Point(21,40)
$lapsform_computername_textbox.Size = New-Object System.Drawing.Size(250,15)
$lapsform.Controls.Add($lapsform_computername_textbox)

#VARIABLE FOR KEYDOWN
$lapsform_computername_textbox_keydown = {}

#KEYDOWN ASSIGNED
$lapsform_computername_textbox_keydown = [System.Windows.Forms.KeyEventHandler]{
    if ($_.keycode -eq 'Enter'){
        $lapsform_search_button.PerformClick()
    }
}

#REGISTER KEYDOWN HANDLER TO COMPUTER TEXTBOX
$lapsform_computername_textbox.add_keydown($lapsform_computername_textbox_keydown)

#LAPS SEARCH BUTTON
$lapsform_search_button = New-Object System.Windows.Forms.Button
$lapsform_search_button.Location = New-Object System.Drawing.Point(290,40)
$lapsform_search_button.Size = New-Object System.Drawing.Size(60,20)
$lapsform_search_button.Text = "Search"
$lapsform.Controls.Add($lapsform_search_button)

#LAPS SEARCH BUTTON LOGIC
$lapsform_search_button.add_click({
    if ($lapsform_computername_textbox.Text.Length -le 0){
        $lapsform_output_label.Text = "You must enter a computer name"
    }else{
        try{
            #getting text from textbox
            $computernametext = $lapsform_computername_textbox.Text

            #checking if computer is in AD
            $checkad = Get-ADComputer -Identity $computernametext
        
            #invoking admpwdpassword command on $domaincontroller
            $invokegetadmpwd = Invoke-Command -ComputerName $domaincontroller -ScriptBlock {get-admpwdpassword -ComputerName $args[0] } -ArgumentList $computernametext | Select-Object Password, expirationtimestamp
        
            #getting password and password expiration date
            $lapsform_password_textbox.Text = $invokegetadmpwd | Select-Object -ExpandProperty password
            $lapsform_password_expires_textbox.Text = $invokegetadmpwd | Select-Object -ExpandProperty expirationtimestamp

            $lapsform_output_label.text = ""
        }catch{

            if (!$checkad){
                $lapsform_output_label.Text = "Computer not found"
            }
            #clears password and expiry textbox
            $lapsform_password_textbox.Text = ""
            $lapsform_password_expires_textbox.Text = ""
        }
    }
})

#PASSWORD TEXTBOX LABEL
$lapsform_password_textbox_label = New-Object System.Windows.Forms.Label
$lapsform_password_textbox_label.Location = New-Object System.Drawing.Point(20, 90)
$lapsform_password_textbox_label.Size = New-Object System.Drawing.Size(100,20)
$lapsform_password_textbox_label.Text = "Password"
$lapsform.Controls.Add($lapsform_password_textbox_label)

#PASSWORD TEXTBOX
$lapsform_password_textbox = New-Object System.Windows.Forms.TextBox
$lapsform_password_textbox.Location = New-Object System.Drawing.Point(21,110)
$lapsform_password_textbox.Size = New-Object System.Drawing.Size(250,15)
$lapsform_password_textbox.ReadOnly = $true
$lapsform_password_textbox.Font = New-Object System.Drawing.Font("courier",12,[System.Drawing.FontStyle]::Regular)
$lapsform.Controls.Add($lapsform_password_textbox)

#PASSWORD EXPIRES TEXTBOX LABEL
$lapsform_password_expires_textbox_label = New-Object System.Windows.Forms.Label
$lapsform_password_expires_textbox_label.Location = New-Object System.Drawing.Point(20,145)
$lapsform_password_expires_textbox_label.Size = New-Object System.Drawing.Size(100,20)
$lapsform_password_expires_textbox_label.Text = "Password Expires"
$lapsform.Controls.Add($lapsform_password_expires_textbox_label)

#PASSWORD EXPIRES TEXTBOX
$lapsform_password_expires_textbox = New-Object System.Windows.Forms.TextBox
$lapsform_password_expires_textbox.Location = New-Object System.Drawing.Point(21,165)
$lapsform_password_expires_textbox.Size = New-Object System.Drawing.Size(250,15)
$lapsform_password_expires_textbox.ReadOnly = $true
$lapsform.Controls.Add($lapsform_password_expires_textbox)

#DATETIME PICKER LABEL
$lapsform_datetime_picker_label = New-Object System.Windows.Forms.Label
$lapsform_datetime_picker_label.Location = New-Object System.Drawing.Point(20,200)
$lapsform_datetime_picker_label.Size = New-Object System.Drawing.Size(150,20)
$lapsform_datetime_picker_label.Text = "New Expiration Time"
$lapsform.Controls.Add($lapsform_datetime_picker_label)

#DATETIME PICKER
$lapsform_datetime_picker = New-Object System.Windows.Forms.DateTimePicker
$lapsform_datetime_picker.Location = New-Object System.Drawing.Point(21,220)
$lapsform_datetime_picker.Size = New-Object System.Drawing.Size(250,15)
$lapsform_datetime_picker.Format = "custom"
$lapsform_datetime_picker.CustomFormat = "dd MMMM yyyy"
$lapsform.Controls.Add($lapsform_datetime_picker)

#DATETIME PICKER SET BUTTON
$lapsform_datetime_set_button = New-Object System.Windows.Forms.Button
$lapsform_datetime_set_button.Location = New-Object System.Drawing.Point(285,220)
$lapsform_datetime_set_button.Size = New-Object System.Drawing.Size(91,20)
$lapsform_datetime_set_button.Text = "Set and Update"
$lapsform.Controls.Add($lapsform_datetime_set_button)

$lapsform_datetime_set_button.add_click({

    if ($lapsform_computername_textbox.Text.Length -le 0){
        $lapsform_output_label.Text = "You must enter a computer name"
    }else{
        try{    
            $datetimepickervalue = $lapsform_datetime_picker.value.ToString("MM dd yyyy")
            #getting text from textbox
            $computernametext = $lapsform_computername_textbox.Text
    
            #checking if computer is in AD
            $checkad = Get-ADComputer -Identity $computernametext
            
            #invoking admpwdpassword command on $domaincontroller
            Invoke-Command -ComputerName $domaincontroller -ScriptBlock {reset-admpwdpassword -ComputerName $args[0] -wheneffective $args[1] } -ArgumentList $computernametext, $datetimepickervalue 

            #setting value of output label
            $lapsform_output_label.Text = "Password reset request was successful - GP updating - PLEASE WAIT"

            Invoke-GPUpdate -Computer $computernametext -ErrorAction SilentlyContinue

            $lapsform_output_label.Text = "Finished"
        }catch{
            #checking if computer is in AD
            if (!$checkad){
                $lapsform_output_label.Text = "Computer not found"
            }else{
                write-host "Another issue - WinRM probably isn't allowed..."
            }
    
        }

    }
})

#OUTPUT TEXTBOX
$lapsform_output_label = New-Object System.Windows.Forms.Label
$lapsform_output_label.Location = New-Object System.Drawing.Point(1,265)
$lapsform_output_label.Size = New-Object System.Drawing.Size(385,20)
$lapsform_output_label.BackColor = "white"
$lapsform_output_label.BorderStyle = "fixedsingle"
$lapsform.Controls.Add($lapsform_output_label)

#LAPS UI FORM DIALOG
[void]$lapsform.ShowDialog()

Unix Permissions Winform

In this post, I will show case a winform application that I have just finished building which will tell you the correct command when given the required permissions. For example, a read permission is identified as a 4 in Unix environments.

I have created a small table below:

 Permission Level  Permission Bit
 Read  4
 Write  2
 Execute  1

Since you need to define permissions for: the owner; owner group and others, you need to supply 3 permission bits per command. Plus one more for special permissions at the beginning but we can ignore that for now.

So if we wanted to give the below permissions:

Special – ignore

Owner – read (4), write(2) and execute(1)

Owner group – read(4) and write(2)

Other – read(4)

we would use the following command: chmod 0764 <path-to-file>

Now we can get on with the actual winform… I created this to tell me what permissions I needed to assigned. Below is a screenshot of the winform:

Front Winform

This also keeps a short history of the permissions in the history textbox which is flushed after so long to stop the textbox from overflowing. Here is a download for the project, in the zip folder is both a ps1 file and an exe file. Enjoy!

 

 

 

Creating a Dynamically Changing WinForm

This is a fairly simple thing, if you know what you’re doing or if you have done it before. But if, like me, you are at a complete lost and sick of searching only to find half complete or even completely contradictory answers, then this is the guide for you.

My goals for this GUI was to have the form resize IF a tick box was $true (ticked)

First I started off created a blank form with a single checkbox item, you can see this below:

$form = New-Object System.Windows.Forms.Form
$form.Text = "New User Form"
$form.Size = New-Object System.Drawing.Size(500,540)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "Fixed3D"
$form.MaximizeBox = $false
$form.MinimizeBox = $false

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(165,470)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(245,470)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $CancelButton
$form.Controls.Add($CancelButton)

$checkbox = New-Object System.Windows.Forms.CheckBox
$checkbox.Location = New-Object System.Drawing.Point(100,100)
$checkbox.Size = New-Object System.Drawing.Size(100,25)
$checkbox.Text = "Test Checkbox"
$form.controls.Add($checkbox)

$result = $form.ShowDialog()

That will create an empty form like the one below:

Initial Form

Now I will add the following code after all the objects but before the end $result variable which displays the form:

$checkbox.add_checkstatechanged({

 if ($checkbox.Checked -eq $True){

  $form.size = new-object system.drawing.size(800,540)

 }else{

  $form.size = new-object system.drawing.size(500,540)

}

})

 

this is the part that will make the form larger if the checkbox is checked and return it to the original size if it isn’t checked. You can do SO many other things as well. For example, having a textbox enabled or disabled, changing the forms title, changing the colour of the form and to be honest just about anything you can think of.

This is the complete script you will need:

$form = New-Object System.Windows.Forms.Form
$form.Text = "New User Form"
$form.Size = New-Object System.Drawing.Size(500,540)
$form.StartPosition = "CenterScreen"
$form.FormBorderStyle = "Fixed3D"
$form.MaximizeBox = $false
$form.MinimizeBox = $false

$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(165,470)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$form.AcceptButton = $OKButton
$form.Controls.Add($OKButton)
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Point(245,470)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.CancelButton = $CancelButton
$form.Controls.Add($CancelButton)

$checkbox = New-Object System.Windows.Forms.CheckBox
$checkbox.Location = New-Object System.Drawing.Point(100,100)
$checkbox.Size = New-Object System.Drawing.Size(100,25)
$checkbox.Text = "Test Checkbox"
$form.controls.Add($checkbox)

$checkbox.add_checkstatechanged({
 if ($checkbox.Checked -eq $true){
  $form.Size = New-Object System.Drawing.Size(800,540)
 }else{
  $form.Size = New-Object System.Drawing.Size(500,540)
 }
})

$result = $form.ShowDialog()

This script will create a form like in the screenshots below. In this screenshot the checkbox is not ticked:

Initial Form

And in this screenshot the checkbox has been ticked, making the form size noticeably wider:

Ticked Form

Hope this helps someone struggling with getting started with some of the more complex WinForm tricks. Enjoy and comment if you need any help!