LAPS 3

Okay, this is probably my final update to the whole LAPS thing. I have created two iterations in the past but neither were really groundbreaking or my own design. Not that this update is groundbreaking either though. This is a further update to the below post:

LAPS WinForm 2

I wanted to completely redo my LAPS form (again) to make it my own design, responsive and ultimately better. This is what the final form looks like. It is completely responsive and resizeable:

I will include the source code here but the best place to download this would be from my TechNet gallery.

There are a couple of things you need to change in the form to make it work:

  • Adding your domain controller and domain root to the variables at the top of the script
  • Add your BASE64 data into the BASE64 variable to use your own logo

Heres the code:

#Enter your domain and domain controller below :)
$script:domainController = "DOMAIN CONTROLLER HERE" #E.G domaincontroller.domain.lan
$script:domainRoot = "DOMAIN ROOT HERE" #E.G domain.lan

#LOADING ASSEMBLIES
Add-Type -AssemblyName PresentationFramework, System.Drawing, System.Windows.Forms, WindowsFormsIntegration

#ICON FOR FORM
[string]$base64=@'
BASE64 DATA HERE
'@

#CREATING THE IMAGE FROM BASE64 DATA
$bitmap = New-Object System.Windows.Media.Imaging.BitMapImage
$bitmap.BeginInit()
$bitmap.StreamSource = [System.IO.MemoryStream][System.Convert]::FromBase64String($base64)
$bitmap.EndInit()
$bitmap.Freeze()

#LAPS WINDOW XML
[xml]$LAPSXaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    Title="LAPS UI" Height="400" Width="400" MinHeight="400" MinWidth="400" WindowStartupLocation="CenterScreen">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2"/>
            <ColumnDefinition/>
            <ColumnDefinition Width="Auto" MinWidth="75"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" MinHeight="7"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Label Content="ComputerName:" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Column="1" FontSize="14"/>
        <TextBox Name="Computer_Textbox" VerticalContentAlignment="Center" HorizontalAlignment="Stretch" Grid.Row="1" TextWrapping="Wrap" VerticalAlignment="Stretch" Margin="3" Grid.Column="1" FontSize="14"/>
        <Button Name="Search_Button" Content="Search" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="1" VerticalAlignment="Stretch" Margin="0,3,5,3"/>
        <Label Content="Password" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="2" VerticalAlignment="Stretch" FontSize="14"/>
        <TextBox Name="Password_Textbox" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="3" TextWrapping="Wrap" Margin="3" VerticalAlignment="Stretch" IsReadOnly="True" FontSize="14"/>
        <Button Name="Copy_Button" Content="Copy" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="3" Margin="0,3,5,3" VerticalAlignment="Stretch"/>
        <Label Content="Password Expires" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="4" VerticalAlignment="Stretch" FontSize="14"/>
        <TextBox Name="Password_Ex_Textbox" Grid.Column="1" IsReadOnly="True" HorizontalAlignment="Stretch" Grid.Row="5" TextWrapping="Wrap" VerticalAlignment="Stretch" Margin="3" FontSize="14"/>
        <Label Content="New Expiration" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="6" VerticalAlignment="Stretch" FontSize="14"/>
        <DatePicker Name="Date_Picker" Grid.Column="1" HorizontalAlignment="Stretch" Grid.Row="7" VerticalAlignment="Stretch" Margin="3" FontSize="14"/>
        <Button Name="Set_Button" Content="Set" Grid.Column="2" HorizontalAlignment="Stretch" Grid.Row="7" VerticalAlignment="Stretch" Margin="0,5,5,5"/>
        <GridSplitter IsEnabled="False" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="8" Grid.Column="1" Margin="5,2,5,2" Grid.ColumnSpan="2"/>
        <TextBox Name="Output_Textbox" VerticalScrollBarVisibility="Auto" IsReadOnly="True" HorizontalAlignment="Stretch" Grid.Row="9" TextWrapping="Wrap" Margin="1,5,1,1" VerticalAlignment="Stretch" Grid.ColumnSpan="3" FontSize="12"/>
    </Grid>
</Window>
"@

#LOADING XAML
$LAPSReader=(New-Object System.Xml.XmlNodeReader $LAPSXaml)
$LAPSWindow=[Windows.Markup.XamlReader]::Load($LAPSReader)
$LAPSWindow.Icon = $bitmap

#ASSIGNING CONTROLS
$Computer_Textbox = $LAPSWindow.FindName("Computer_Textbox")
$Search_Button = $LAPSWindow.FindName("Search_Button")
$Password_Textbox = $LAPSWindow.FindName("Password_Textbox")
$Copy_Button = $LAPSWindow.FindName("Copy_Button")
$Password_Ex_Textbox = $LAPSWindow.FindName("Password_Ex_Textbox")
$Date_Picker = $LAPSWindow.FindName("Date_Picker")
$Set_Button = $LAPSWindow.FindName("Set_Button")
$Output_Textbox = $LAPSWindow.FindName("Output_Textbox")

#FUNCTION TO SET OUTPUT TEXTBOX
function set-output-textbox{
    param(
        [string]$value,
        [bool]$date
    )
    if ($date){
        $Output_Textbox.Text = ("[$(Get-Date)] - $value `r`n")
    }else{
        $Output_Textbox.Text = $value
    }
}

#FUNCTION TO UPDATE OUTPUT TEXTBOX
function update-output-textbox{
    param(
        [string]$value,
        [bool]$date
    )
    if ($date){
        $Output_Textbox.AppendText("[$(Get-Date)] - $value `r`n")
    }else{
        $Output_Textbox.AppendText("     $value `r`n")
    }
    $Output_Textbox.ScrollToEnd()
}

#FUNCTION TO UPDATE FORM
function update-form{
    [System.Windows.Forms.Application]::DoEvents()
}

#FUNCTION TO UPDATE PASSWORD TEXTBOX
function update-password-textbox($value){
    $Password_Textbox.Text = $value
}

#FUNCTION TO UPDATE PASSWORD EX TEXTBOX
function update-passwordex-texbox($value){
    $Password_Ex_Textbox.Text = $value
}

#FUNCTION TO SET CONTROLS
function set-controls{
    param(
        [bool]$switcher,
        [bool]$setswitcher
    )
    $Search_Button.IsEnabled = $switcher
    $Set_Button.IsEnabled = $setswitcher
    $Date_Picker.IsEnabled = $setswitcher
}

#DECIDE IF COPY BUTTON SHOULD BE ENABLED
$Copy_Button.IsEnabled = $false
$Password_Textbox.Add_TextChanged({
    if ($Password_Textbox.Text.Length -gt 0){
        $Copy_Button.IsEnabled = $true
    }else{
        $Copy_Button.IsEnabled = $false
    }
})

#MAKING COMPUTER NAME UPPERCASE ON FOCUS LOST
$Computer_Textbox.Add_LostFocus({
    $Computer_Textbox.Text = $Computer_Textbox.Text.ToUpper()
})

#COPY BUTTON LOGIC
$Copy_Button.Add_Click({
    Set-Clipboard -Value $Password_Textbox.Text
})

#COMPUTER TEXTBOX KEYDOWN LOGIC
$Computer_Textbox.Add_KeyDown({
    if ($args.Key -eq 'Enter'){
        $Search_Button.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs $([System.Windows.Controls.Button]::ClickEvent)))
    }
})

#DISABLING CONTROLS ON FORM LOAD
set-controls -switcher $true -setswitcher $false

#WELCOME MESSAGE ON FORM LOAD
$Output_Textbox.HorizontalContentAlignment="Center"
$Output_Textbox.VerticalContentAlignment="Center"
set-output-textbox -date $false -value "Welcome to version 3 of this form! It is now responsive and a lot cleaner in the background. Nothing you ever had to worry about though :)"

#SEARCH BUTTON LOGIC
$Search_Button.Add_Click({

    #DISABLING CONTROLS ON BUTTON PRESS
    $Output_Textbox.HorizontalContentAlignment="Left"
    $Output_Textbox.VerticalContentAlignment="Top"
    set-controls -switcher $false -setswitcher $false
    update-password-textbox -value $null
    update-passwordex-texbox -value $null
    $Date_Picker.Text = $null

    if ($Computer_Textbox.Text.Length -le 0){
        #OUTPUT IF EMPTY SEARCH AND ENABLING CONTROLS
        set-output-textbox -date $true -value "Input cannot be empty"
        set-controls -switcher $true -setswitcher $false    
    }else{
        set-output-textbox -date $true -value "Please Wait"
        
        #PUTTING INPUT INTO VARIABLE
        $script:computerName = $Computer_Textbox.Text

        #CREATING A SYNCHRONISED HASHTABLE
        $script:syncHash = [hashtable]::Synchronized(@{})

        #CREATING SEARCH RUNSPACE
        $searchRunspace = [runspacefactory]::CreateRunspace()
        $searchRunspace.ApartmentState = "STA"
        $searchRunspace.ThreadOptions = "ReuseThread"
        $searchRunspace.Open()
        $searchRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
        $searchRunspace.SessionStateProxy.SetVariable("computerName",$computerName)
        $searchRunspace.SessionStateProxy.SetVariable("domainController",$domainController)

        #POWERSHELL TO BE RAN IN RUNSPACE
        $searchPowerShell = [powershell]::Create().AddScript({
            $syncHash.searchADComputer = Get-ADComputer -Identity $computerName
            $syncHash.searchInvoke = Invoke-Command -ComputerName $domainController -ScriptBlock { Get-AdmPwdPassword -ComputerName $args[0] } -ArgumentList $computerName | Select-Object Password, ExpirationTimeStamp
        })

        #ASSIGNING RUNSPACE TO POWERSHELL
        $searchPowerShell.Runspace = $searchRunspace
        #STARTING THE RUNSPACE AND POWERSHELL
        $searchObject = $searchPowerShell.BeginInvoke()

        #REFRESHING UNTIL POWERSHELL IS COMPLETE
        do{
            Start-Sleep -Milliseconds 100
            update-form
        }while (!$searchObject.IsCompleted)

        #ENDING POWERSHELL INVOKE AND DISPOSING OF RUNSPACE
        $searchPowerShell.EndInvoke($searchObject)
        $searchPowerShell.Dispose()
    
        if ($syncHash.searchADComputer){
            #COMPUTER IS FOUND ON DOMAIN
            if ($syncHash.searchInvoke){
                #INVOKE SUCCESSFUL
                $admpwdPassword = $syncHash.searchInvoke.password
                $admpwdPasswordExpiration = $syncHash.searchInvoke.ExpirationTimeStamp
                $admpwdPasswordExpirationFormatted = $admpwdPasswordExpiration.ToString("dd/MM/yyyy hh:mm:ss")

                #UPDATING FIELDS
                update-output-textbox -date $true -value "Information retrieved"
                update-password-textbox -value $admpwdPassword
                update-passwordex-texbox -value $admpwdPasswordExpirationFormatted
                set-controls -switcher $true -setswitcher $true
            }else{
                #INVOKE FAILED
                update-output-textbox -date $true -value "Failded to retrieve password information"
                update-password-textbox -value $null
                update-passwordex-texbox -value $null
                set-controls -switcher $true -setswitcher $false
            }
        }else{
            #COMPUTER NOT FOUND ON DOMAIN
            update-output-textbox -date $true -value "Host not found on domain"
            update-password-textbox -value $null
            update-passwordex-texbox -value $null
            set-controls -switcher $true -setswitcher $false
        }
    }
})

#SET EXPIRATION BUTTON LOGIC
$Set_Button.Add_Click({
    
    #DISABLING CONTROLS ON BUTTON PRESS
    set-controls -switcher $false -setswitcher $false

    if ($Date_Picker.Text.Length -le 0){
        #OUTPUT IF EMPTY DATE AND ENABLING CONTROLS
        update-output-textbox -date $true -value "No date selected"
        set-controls -switcher $true -setswitcher $true
    }else{
        #GETTING NEW DATES FOR EXPIRATION
        $newExpirationString = $Date_Picker.SelectedDate.ToString("MM/dd/yyyy")
        $script:newExpirationDate = [datetime]::ParseExact($newExpirationString, 'MM/dd/yyyy', $null)
        
        #OUTPUTTING FRIENDLY EXPIRATION TO OUTPUT TEXTBOX
        update-output-textbox -date $true -value "Setting expiration to $newExpirationString..."

        #CREATING SEARCH RUNSPACE
        $setRunspace = [runspacefactory]::CreateRunspace()
        $setRunspace.ApartmentState = "STA"
        $setRunspace.ThreadOptions = "ReuseThread"
        $setRunspace.Open()
        $setRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
        $setRunspace.SessionStateProxy.SetVariable("computerName",$computerName)
        $setRunspace.SessionStateProxy.SetVariable("domainController",$domainController)
        $setRunspace.SessionStateProxy.SetVariable("newExpirationDate",$newExpirationDate)

        #POWERSHELL TO BE RAN IN RUNSPACE
        $setPowerShell = [powershell]::Create().AddScript({
            try{
                $syncHash.setInvoke = Invoke-Command -ComputerName $domainController -ScriptBlock {Reset-AdmPwdPassword -ComputerName $args[0] -WhenEffective $args[1] } -ArgumentList $computerName, $newExpirationDate -ErrorAction Stop
                try{
                    Invoke-GPUpdate -Computer $computerName -ErrorAction Stop
                    $syncHash.setGPUpdate = $true
                }catch{
                    #GP UPDATE FAILED
                    $syncHash.setGPUpdate = $null
                }
            }catch{
                #CHANGING EXPIRATION FAILED
                $syncHash.setInvoke = $null
            }
        })

        #ASSIGNING RUNSPACE TO POWERSHELL
        $setPowerShell.Runspace = $setRunspace
        #STARTING THE RUNSPACE AND POWERSHELL
        $setObject = $setPowerShell.BeginInvoke()

        #REFRESHING UNTIL POWERSHELL IS COMPLETE
        do{
            Start-Sleep -Milliseconds 100
            update-form
        }while (!$setObject.IsCompleted)

        #ENDING POWERSHELL INVOKE AND DISPOSING OF RUNSPACE
        $setPowerShell.EndInvoke($setObject)
        $setPowerShell.Dispose()

        #CHECKING PASSWORD EXPIRATION SUCCESS
        if ($syncHash.setInvoke){
            update-output-textbox -date $true -value "Successfully reset password expiration date"
            #CHECKING GP UPDATE SUCCESS
            if ($syncHash.setGPUpdate){
                update-output-textbox -date $true -value "Succesfully ran GP update"
            }else{
                update-output-textbox -date $true -value "Failed to run GP update, this is probably due to permissions"
            }
        }else{
            update-output-textbox -date $true -value "Failed to reset password expiration date"
        }

        #RESETTING CONTROLS
        set-controls -switcher $true -setswitcher $true
    }
})

#CHECK FOR AD MODULE AND TEST IF ON LOCAL DOMAIN/NETWORK
if ( Test-Connection $domainRoot -Count 1 -Quiet){
    #DOMAIN IS ACCESSIBLE
    if (Get-Module -List ActiveDirectory ){
        #AD MODULE INSTALLED
        #FORM WILL BE DISPLAYED WITHOUT ANY MODIFICATIONS
    }else{
        #AD MODULE NOT INSTALLED
        set-output-textbox -date $false -value "Install the AD module and restart"
        set-controls -switcher $false -setswitcher $false
        $Computer_Textbox.IsEnabled = $false
    }
}else{
    #DOMAIN ISN'T ACCESSIBLE
    set-output-textbox -date $false -value "$domainRoot is not accessible"
    set-controls -switcher $false -setswitcher $false
    $Computer_Textbox.IsEnabled = $false
}   

#REMOVING PROCESS ON FORM CLOSE
$LAPSWindow.Add_Closing({
    try{
        $syncHash.Clear() | Out-Null
    }catch{}
    
    Stop-Process -Name "LAPS" -ErrorAction SilentlyContinue
})

#DISPLAY FORM WHILST TESTING
$app = [Windows.Application]::new()
$app.run($LAPSWindow)

Enjoy!

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!

Handling Textbox Keydown Events

Welcome to another instalment of “how much can I confused myself today…”

Here, I will you how to recognise a keydown event on a textbox and also how to “identify” which key was pressed. This was useful to me because I wanted a button to be pressed when the user pressed the enter key whilst typing in a textbox. Similar to when you type a question into Google and press enter instead of pressing the search button.

First, I found what control I want the event to handle and added a raiseevent onto the button I wanted pressing. You can see this below:

$syncHash.Textbox.Add_KeyDown({
    if ($args[1].key -eq 'Enter'){
        $syncHash.Button.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))
    }
})

So in this scenario, when the user wants to search they can just press enter in the textbox and the button will be pressed. You can also do this for the entire form. Meaning that if you have multiple textboxes and want an enter in any of them to press a button, you can just put the handler onto the entire form. You can see this below:

$syncHash.Window.Add_KeyDown({
    if ($args[1].key -eq 'Enter'){
        $syncHash.Button.RaiseEvent((New-Object -TypeName System.Windows.RoutedEventArgs -ArgumentList $([System.Windows.Controls.Button]::ClickEvent)))

    }
})

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 #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!

Change ownCloud User Home in MYSQL

So I recently created a new ownCloud 10 server to get away from ownCloud 9. This meant creating a new CentOS 7 VM bladybladyblah…

One thing that caught me out, among many to do with ownCloud, was that the original user created during the setup process couldn’t save or view files after I had reconfigured the home directory to be more secure.

After looking in the MYSQL database, I saw that the original user’s home directory had not been updated to match the new path. To check this I used the following commands and looked for the home column:

USE owncloud;
SELECT * FROM oc_accounts;

After those commands, I updated the users home setting by using the following command:

UPDATE oc_accounts SET home="/new/dir/username" WHERE user_id="user";

Nice simple fix for an issue that was driving me up the wall.

Hope you 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!

Linux Directory Checking Script

Morning! Oh wait, it’s the afternoon…

Today, I finally got around to making a script that will run automatically on my network storage server (Raspberry Pi with a dinky USB hard drive) and check if the USB HDD is accessible.

This issue started a couple of weeks ago where I was getting weird IO errors on the USB disk about every 2 weeks. Instead of buying a new drive, creating a RAID array or anything else equally as intelligent and appropriate, I decided to just reboot my Raspberry Pi every time this happened. Now, I don’t want to do this manually every time so I finally created a script and added it to my cron jobs.

You can see the script I used below:

#!/bin/bash

if [ ! -d "path/to/check" ]; then
    #Directory is not found and HDD is not okay, do whatever is below
    uptime=$(uptime)
    currenttime=$(date)
    echo "Host rebooted at $currenttime. Uptime was$uptime" >> /path/to/output.txt
    sudo reboot
fi

My crontab job is running as root because the sudo reboot part was giving me a couple of issues. This is the entry in the root crontab:

@hourly /path/to/sh/file

Enjoy!

Using DinoPass in PowerShell

This is a nice little trick I learnt whilst automating domain user creation with PowerShell, I found generating passwords in PowerShell was always ugly. Just see the example below from a previous post I’d made:

[string]$initialpassword = ([char[]](Get-Random -input $(47..57 + 65..90 +97..122) -count 8)) + (Get-Random -minimum 0 -maximum 10)

$passwordwithspacesremoved = $initialpassword.Replace(' ','')

$convertedpassword = ConvertTo-SecureString -AsPlainText $passwordwithspacesremoved -Force

This would generate a password like “cDUtxlvM5” which is just about as ugly as the code used to create it.

So I decided to use DinoPass instead since it created better looking passwords without the faff of generating them in PowerShell. This is a the code I used:

Invoke-WebRequest -Uri https://www.dinopass.com/password/strong | Select-Object -ExpandProperty content

Which would give me a much nicer, but still secure, password like “poorJump62”. Then to use it when automating domain user creation, I would use the below and put the whole thing into a variable that I would set the password to:

$super_secure_password = Invoke-WebRequest -Uri https://www.dinopass.com/password/strong | Select-Object -ExpandProperty content | ConvertTo-SecureString -AsPlainText -Force

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()