The other day we laid out the design for our PS Event Viewer utility, today for part 2 we will be designing and configuring. For this project I decided to leverage PoshGUI, which is great for quickly creating standalone forms. It definitely has it’s limitations, for example what if your form grows and shrinks? You would probably want to use a TableLayoutPanel with dock properties set to ‘fill’ so that you don’t have to guess the sizes of the objects as the form grows/shrinks. In this case we are just going to have a static form, so placing elements at x/y coordinates shouldn’t give us any issues. This will be modified even further, but it’s a nice little life hack to leverage this free utility to kick things off. You can fork the code here: https://poshgui.com/editor/5ae0ed8e514c144412b926e2.
Now that we have a starting point for our UI we can add some functions. Per our design we wanted to create 4 functions:
- Get application/security/system logs (default to last 3 days)
- Save results to local location in viewable format
- Find/search (This will actually be handled by an event)
- Create a runspace to execute code separate from GUI – We will touch on this in part 3
For simplicity purposes since this post is focusing on GUI creation I will paste the functional code here:
function Get-LogResults{ Param ( [String] $ComputerName = $env:COMPUTERNAME, [String] $LogName, [int[]] $EventID, [String[]] $EntryType, [DateTime] $Time = (Get-Date).AddDays(-2), [String[]] $Source, [String[]] $Keyword ) #Build query $Params = @{} if($EventID) { $Params["InstanceID"] = $EventID } if($EntryType) { $Params["EntryType"] = $EntryType } if($Source) { $Params["Source"] = $Source } if($Keyword) { $Params["Message"] = $Keyword } Get-EventLog -ComputerName $ComputerName -LogName $LogName -After $Time @Params } function Save-Results{ if($Script:Results -ne $null){ $SavePath = Select-FolderDialog if($SavePath){ $Script:Results | Export-Csv "$SavePath\EventViewerResults.csv" -NoTypeInformation } } } function Start-BackgroundJob { }
Now that we have our core functionality, we need to alter our GUI events to call our code. Start-BackgroundJob was intentionally left blank for right now as we will touch on it in the next part, however it is needed to execute our code in a separate runspace via our click events. Event code always starts with an ‘_’ so per our code from PoshGUI with a few minor tweaks here are the events we have initialized:
#Form Events $Form.Add_FormClosing({ }) $ButtonSubmit.Add_Click({ }) $TextBoxMachine.Add_Enter({ }) $TextBoxMachine.Add_Leave({ }) $TextBoxFind.Add_TextChanged{ } $ButtonSave.Add_Click({ }) $ListViewLogResults.Add_SelectionChanged({ }) $ListViewLogResults.Add_CellPainting() $ButtonView2.Add_Click({ }) $ButtonView1.Add_Click({ })
These events should handle pretty much everything we need to do for our GUI. To start we will focus on what happens when the submit button is clicked, which reflect the core functionality we just created. The overall concept here is to load the form, wait for user input, perform an operation, and finally update the GUI with the results. When the user clicks $ButtonSubmit we need to call our Get-LogResults function and pass in parameters from the GUI. Then once that function processes we need to update the GUI. Repeat this same process for other events:
#Form Events $Form.Add_FormClosing({ #Handles a hung GUI #Stop-Process -Id $PID }) $ButtonSubmit.Add_Click({ $ButtonSubmit.Enabled = $False $ButtonSubmit.Text = "Loading..." #Build params $Params = @{} $EntryTypeBuilder = @() if($CheckBoxInformation.Checked) { $EntryTypeBuilder += "Information" } if($CheckBoxCritical.Checked) { $EntryTypeBuilder += "Error" } if($CheckBoxWarning.Checked) { $EntryTypeBuilder += "Warning" } if($CheckBoxError.Checked) { $EntryTypeBuilder += "Error" } switch($ComboBoxTime.SelectedItem.ToString()){ "Today"{ $Params["Time"] = Get-Date } "Last 2 days"{ $Params["Time"] = (Get-Date).AddDays(-1) } "Last 3 days"{ $Params["Time"] = (Get-Date).AddDays(-2) } } if($EntryTypeBuilder) { $Params["EntryType"] = $EntryTypeBuilder } else { #Defaults to information if nothing was specified $Params["EntryType"] = "Information" } if($TextBoxMachine.Text -ne "Machine"){ $Params["ComputerName"] = $TextBoxMachine.Text } $Params["LogName"] = $ComboBoxLogPicker.SelectedItem.ToString() if($TextBoxSource.Text -ne ""){ $Params["Source"] = $TextBoxSource.Text } if($TextBoxEventID.Text -ne ""){ $Params["EventID"] = $TextBoxEventID.Text } if($TextBoxKeywords.Text -ne ""){ $Params["Keyword"] = $TextBoxKeywords.Text } $Script:Results = Get-LogResults @Params $Form.SuspendLayout() $ListViewLogResults.Rows.Clear() $TextBoxLogResult.Text = "" #Update the GUI foreach($Result in $Results){ $ListViewLogResults.Rows.Add($Result.EntryType,$Result.TimeGenerated,$Result.InstanceId,$Result.Source,$Result.Message) } $Form.ResumeLayout() $ButtonSubmit.Text = "Fetch Results" $ButtonSubmit.Enabled = $True }) $ListViewLogResults.Add_CellPainting($ListViewLogResults_CellPainting) $TextBoxMachine.Add_Enter({ if($TextBoxMachine.Text -eq "Machine") { $TextBoxMachine.Clear() } }) $TextBoxMachine.Add_Leave({ if($TextBoxMachine.Text -eq "") { $TextBoxMachine.Text = "Machine" } }) $TextBoxFind.Add_TextChanged{ $ListViewLogResults.Refresh() $ListViewLogResults.DataSource.DefaultView.RowFilter = "EntryID LIKE '*$($TextBoxFind.Text)*'" } $ButtonSave.Add_Click({ Save-Results }) $ListViewLogResults.Add_SelectionChanged({ $Row = $ListViewLogResults.SelectedRows $TextBoxLogResult.Text = $Row.Cells[4].Value }) $ButtonView2.Add_Click({ $Form.SuspendLayout() $ListViewLogResults.width = 392 $ListViewLogResults.height = 561 $ListViewLogResults.location = New-Object System.Drawing.Point(187,33) $TextBoxLogResult.width = 208 $TextBoxLogResult.height = 561 $TextBoxLogResult.location = New-Object System.Drawing.Point(584,33) $Form.ResumeLayout() }) $ButtonView1.Add_Click({ $Form.SuspendLayout() $ListViewLogResults.width = 605 $ListViewLogResults.height = 269 $ListViewLogResults.location = New-Object System.Drawing.Point(187,33) $TextBoxLogResult.width = 605 $TextBoxLogResult.height = 278 $TextBoxLogResult.location = New-Object System.Drawing.Point(187,313) $Form.ResumeLayout() })
Now that we have our events and functions you might think we’re done, right? Well if you launch your script and try to pull some event logs you will see why we need to leverage the background runspace. The form cannot simultaneously stay responsive while performing a 2nd operation. Check out Part 3 where I will go over this in detail and wrap things up on this project.