Powershell TextBox automatically update file contents - powershell

Anyone can help me fix my code below. I am displaying a log file in the textbox, but the textbox wont update automatically when the log file is updated. I tried using a timer but it wont work since it will refresh the whole form and the input file name will be reset too, so it will return a null value. Please go to "viewComo" function.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
#2. Instantiate a Form Object
#-----------------------------------------------------------------------------------------
$Form_MAIN = New-Object system.Windows.Forms.Form;
$Form_MAIN.ClientSize = New-Object System.Drawing.Point(1024,1000);
$Form_MAIN.StartPosition = "manual";
$Form_MAIN.Location = New-Object System.Drawing.Size(600,300);
$Form_MAIN.BackColor = [System.Drawing.ColorTranslator]::FromHtml("#6699CC");
$Form_MAIN.text = "DataCenter Operation Applications";
$Form_MAIN.TopMost = $false;
$Form_MAIN.AutoSize = $false;
$Form_MAIN.AutoScale = $false;
$Form_MAIN.MaximizeBox = $false;
#3. Build the Form Components
#-----------------------------------------------------------------------------------------
$MainMenu = New-Object System.Windows.Forms.MenuStrip;
$Menu_File = New-Object System.Windows.Forms.ToolStripMenuItem("File");
$SubMenu_Open = New-Object System.Windows.Forms.ToolStripMenuItem("Open");
$SubMenu_Save = New-Object System.Windows.Forms.ToolStripMenuItem("Save");
$SubMenu_Exit = New-Object System.Windows.Forms.ToolStripMenuItem("Exit");
$Menu_DCApp = New-Object System.Windows.Forms.ToolStripMenuItem("DCApp");
$SubMenu_Reports_Checker = New-Object System.Windows.Forms.ToolStripMenuItem("Reports_Checker");
$SubMenu_Reports_Transfer = New-Object System.Windows.Forms.ToolStripMenuItem("Reports_Transfer");
$SubMenu_COB_Monitoring = New-Object System.Windows.Forms.ToolStripMenuItem("COB_Monitoring");
$SubMenu_AD_AccountLock_Checker = New-Object System.Windows.Forms.ToolStripMenuItem("AD_AccountLock_Checker");
$Menu_Help = New-Object System.Windows.Forms.ToolStripMenuItem("Help");
$Menu_About = New-Object System.Windows.Forms.ToolStripMenuItem("About");
$GB_Top = New-Object system.Windows.Forms.Groupbox;
$GB_Top.height = 330;
$GB_Top.width = 990;
$GB_Top.text = "Como Checker, Please input the Service name below";
$GB_Top.location = New-Object System.Drawing.Point(15,30);
$GB_Top.Font = New-Object System.Drawing.Font('Calibri',12);
$TB_Top_Input = New-Object system.Windows.Forms.TextBox;
$TB_Top_Input.multiline = $true;
$TB_Top_Input.width = 840;
$TB_Top_Input.height = 30;
$TB_Top_Input.location = New-Object System.Drawing.Point(10,25);
$TB_Top_Input.Font = New-Object System.Drawing.Font('Calibri',10);
$TB_Top_Button = New-Object system.Windows.Forms.Button;
$TB_Top_Button.Text = "Search COMO";
$TB_Top_Button.width = 124;
$TB_Top_Button.height = 30;
$TB_Top_Button.location = New-Object System.Drawing.Point(850,25);
$TB_Top_Button.Add_Click({checkComoFiles})
$RB_LabelQuery = New-Object System.Windows.Forms.Label;
$RB_LabelQuery.Text = "Choose the Range to query:";
$RB_LabelQuery.AutoSize = $true;
$RB_LabelQuery.width = 104;
$RB_LabelQuery.height = 10;
$RB_LabelQuery.location = New-Object System.Drawing.Point(10,55);
$RB_LabelQuery.Font = New-Object System.Drawing.Font('Callibri',9);
$RB_Option1 = New-Object system.Windows.Forms.RadioButton;
$RB_Option1.Text = "Last 1 Hour";
$RB_Option1.AutoSize = $true;
$RB_Option1.width = 104;
$RB_Option1.height = 10;
$RB_Option1.location = New-Object System.Drawing.Point(170,55);
$RB_Option1.Font = New-Object System.Drawing.Font('Callibri',9);
$RB_Option2 = New-Object system.Windows.Forms.RadioButton;
$RB_Option2.Text = "Last 3 Hours";
$RB_Option2.AutoSize = $true;
$RB_Option2.width = 104;
$RB_Option2.height = 10;
$RB_Option2.location = New-Object System.Drawing.Point(270,55);
$RB_Option2.Font = New-Object System.Drawing.Font('Callibri',9);
$RB_Option3 = New-Object system.Windows.Forms.RadioButton;
$RB_Option3.Text = "Last 24 Hours";
$RB_Option3.AutoSize = $true;
$RB_Option3.width = 104;
$RB_Option3.height = 10;
$RB_Option3.location = New-Object System.Drawing.Point(360,55);
$RB_Option3.Font = New-Object System.Drawing.Font('Callibri',9);
$RB_LabelOutput = New-Object System.Windows.Forms.Label;
$RB_LabelOutput.Text = "Result:";
$RB_LabelOutput.AutoSize = $true;
$RB_LabelOutput.width = 104;
$RB_LabelOutput.height = 10;
$RB_LabelOutput.location = New-Object System.Drawing.Point(10,85);
$RB_LabelOutput.Font = New-Object System.Drawing.Font('Calibri',12);
#$TB_Top_Output = New-Object system.Windows.Forms.TextBox;
#$TB_Top_Output.multiline = $true;
#$TB_Top_Output.width = 970;
#$TB_Top_Output.height = 200;
#$TB_Top_Output.location = New-Object System.Drawing.Point(10,110);
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Size=New-Object System.Drawing.Size(970,200)
$dataGridView.Location = New-Object System.Drawing.Point(10,110);
$dataGridView.BackgroundColor = "White";
#$form.Controls.Add($dataGridView)
$dataGridView.ColumnCount = 1
$dataGridView.ColumnHeadersVisible = $true
$dataGridView.Columns[0].Name = "Como Name"
$datagridview.Columns[0].Width = 300;
$datagridview.Font = New-Object System.Drawing.Font('Courier New',9);
$datagridview.Add_CellMouseDoubleClick({viewComoGrid})
$GB_Bottom = New-Object system.Windows.Forms.Groupbox;
$GB_Bottom.height = 600;
$GB_Bottom.width = 990;
$GB_Bottom.text = "View COMO";
$GB_Bottom.location = New-Object System.Drawing.Point(15,380);
$GB_Bottom.Font = New-Object System.Drawing.Font('Calibri',12);
$TB_Bottom_Input = New-Object system.Windows.Forms.TextBox;
$TB_Bottom_Input.multiline = $true;
$TB_Bottom_Input.width = 840;
$TB_Bottom_Input.height = 30;
$TB_Bottom_Input.location = New-Object System.Drawing.Point(10,20);
$TB_Bottom_Input.Font = New-Object System.Drawing.Font('Calibri',10);
$TB_Bottom_Button = New-Object system.Windows.Forms.Button;
$TB_Bottom_Button.Text = "View COMO";
$TB_Bottom_Button.width = 124;
$TB_Bottom_Button.height = 30;
$TB_Bottom_Button.location = New-Object System.Drawing.Point(850,20);
$TB_Bottom_Button.Font = New-Object System.Drawing.Font('Calibri',10);
$TB_Bottom_Button.Add_Click({viewComo})
$RB_LabelOutput_Bottom = New-Object System.Windows.Forms.Label;
$RB_LabelOutput_Bottom.Text = "Result:";
$RB_LabelOutput_Bottom.AutoSize = $true;
$RB_LabelOutput_Bottom.width = 104;
$RB_LabelOutput_Bottom.height = 10;
$RB_LabelOutput_Bottom.location = New-Object System.Drawing.Point(10,55);
$RB_LabelOutput_Bottom.Font = New-Object System.Drawing.Font('Calibri',12);
$TB_Top_Output_Bottom = New-Object system.Windows.Forms.TextBox;
$TB_Top_Output_Bottom.multiline = $true;
$TB_Top_Output_Bottom.size = New-Object System.Drawing.Size(970,510)
$TB_Top_Output_Bottom.ReadOnly = $True
#$TB_Top_Output_Bottom.width = 970;
#$TB_Top_Output_Bottom.height = 510;
$TB_Top_Output_Bottom.location = New-Object System.Drawing.Size(10,80);
$TB_Top_Output_Bottom.ScrollBars = 'Both'
$TB_Top_Output_Bottom.Font = New-Object System.Drawing.Font('Courier New',9);
#4. Add the Components to the Group Boxes
#-----------------------------------------------------------------------------------------
$GB_Top.controls.AddRange(#($RB_LabelQuery, $RB_Option1,$RB_Option2,$RB_Option3,$TB_Top_Input, $TB_Top_Button, $RB_LabelOutput, $dataGridView));
$GB_Bottom.controls.AddRange(#($L_Bottom_Output,$B_Enter, $TB_Bottom_Input, $TB_Bottom_Button, $RB_LabelOutput_Bottom, $TB_Top_Output_Bottom ));
#4. Add the Components to the Form
#-----------------------------------------------------------------------------------------
$Menu_File.DropDownItems.Add($SubMenu_Open);
$Menu_File.DropDownItems.Add($SubMenu_Save);
$Menu_File.DropDownItems.Add($SubMenu_Exit);
$Menu_DCApp.DropDownItems.Add($SubMenu_Reports_Checker);
$Menu_DCApp.DropDownItems.Add($SubMenu_Reports_Transfer);
$Menu_DCApp.DropDownItems.Add($SubMenu_COB_Monitoring);
$Menu_DCApp.DropDownItems.Add($SubMenu_AD_AccountLock_Checker);
$MainMenu.Items.Add($Menu_File);
$MainMenu.Items.Add($Menu_DCApp);
$MainMenu.Items.Add($Menu_Help);
$MainMenu.Items.Add($Menu_About);
$Form_MAIN.controls.AddRange(#($MainMenu,$TB_Output));
#4.1 Add the Group Boxes to the Form
#-----------------------------------------------------------------------------------------
$Form_MAIN.controls.AddRange(#($GB_Top,$GB_Bottom));
#5. Code the Event Handlers
#-----------------------------------------------------------------------------------------
$SubMenu_Open.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Open File."; })
$SubMenu_Save.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Save File."; })
$SubMenu_Exit.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Exit Program."; })
$SubMenu_Reports_Checker.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Reports_Checker"; })
$SubMenu_Reports_Transfer.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Reports_Transfer"; })
$SubMenu_COB_Monitoring.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n COB_Monitoring"; })
$SubMenu_AD_AccountLock_Checker.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n AD_AccountLock_Checker"; })
$Menu_DCApp.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Options!"; })
$Menu_Help.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n Help me!"; })
$Menu_About.Add_Click({ $TB_Top_Output_Bottom.Text = "`r`n All about ME."; })
function checkComoFiles {
$servicename= $TB_Top_Input.Text
$dataGridView.Rows.Clear()
$TB_Top_Input.Clear()
if ($RB_Option1.Checked) {
$hours = 1
$ResultQuery = Get-ChildItem -Path C:\Users\user\Downloads\ -File | Where-Object {($_.LastWriteTime - gt (Get-Date (Get-Date).AddHours(-$hours))) } |
ForEach-Object { $_ | Select-String -List -Pattern $servicename -SimpleMatch |
Select-Object -first 1 -ExpandProperty FileName}
}
if ($RB_Option2.Checked) {
$hours = 3
$ResultQuery = Get-ChildItem -Path C:\Users\user\Downloads\ -File | Where-Object {($_.LastWriteTime -gt (Get-Date (Get-Date).AddHours(-$hours))) } |
ForEach-Object { $_ | Select-String -List -Pattern $servicename -SimpleMatch |
Select-Object -first 1 -ExpandProperty FileName}
}
if ($RB_Option3.Checked) {
$hours = 24
$ResultQuery = Get-ChildItem -Path C:\Users\user\Downloads\ -File | Where-Object {($_.LastWriteTime -gt (Get-Date (Get-Date).AddHours(-$hours))) } |
ForEach-Object { $_ | Select-String -List -Pattern $servicename -SimpleMatch |
Select-Object -first 1 -ExpandProperty FileName}
}
$dataGridView.Columns[0].Name = $servicename
$rows = #($ResultQuery)
foreach ($row in $rows)
{
$dataGridView.Rows.Add($row)
}
}
Function viewComo {
$comoName = $TB_Bottom_Input.Text
$openComolive = Get-Content C:\Users\user\Downloads\$comoName
$TB_Top_Output_Bottom.Lines = $openComolive
$TB_Bottom_Input.Clear()
}
Function viewComoGrid {
$rowIndex = $datagridview.CurrentRow.Index
$columnIndex = $datagridview.CurrentCell.ColumnIndex
#Write-Host $rowIndex
#Write-Host $columnIndex
#Write-Host $datagridview.Rows[$rowIndex].Cells[0].value
#Write-Host $datagridview.Rows[$rowIndex].Cells[$columnIndex].value
$comoNameGrid = $datagridview.Rows[$rowIndex].Cells[0].value
$openComoliveGrid = Get-Content C:\Users\user\Downloads\$comoNameGrid
$TB_Top_Output_Bottom.Lines = $openComoliveGrid
$TB_Bottom_Input.Clear()
}
#6. Display Form
#-----------------------------------------------------------------------------------------
$Form_Main.ShowDialog();
Im trying to use below timer in my above code but it wont work since the whole form will be refresh
$timer = new-object Windows.Forms.Timer
$timer.Interval=10000
$timer.add_Tick({$TB_Top_Output_Bottom.Lines = Get-Content C:\Users\user\Downloads\$logname | Out-String; $TB_Top_Output_Bottom.Refresh()})
$timer.Start()

Original Version:
This isn't exactly intended to be an answer!
I think mklement0's answer is probably a safer way of getting the job done. But I remember reading about FileSystemWatcher some years ago, and having never used it before, wanted to give it a try.
Found this C# FileSystemWatcher answer, and figured out how to recreate the work in PowerShell.
Found this interesting use of SynchronizingObject for System.Timers.Timer that appears to allow Timer's Elapsed event to to run in the same thread as a control or form, and discovered that FileSystemWatcher also has a SynchronizingObject Property.
This seems to work flawlessly when editing and saving MyLogFile.TXT with NotePad.exe, but when I load MyLogFile.TXT in VSCode, the script either crashes or stops working. I think VSCode is locking the file and preventing the script from reading it, but I'm not really sure.
I would like to stress that this is an experiment, and this is outside my experience, use with with caution.
Basic code for setting up a Form for testing FileSystemWatcher:
using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
[Application]::EnableVisualStyles()
$FilePathToWatch = $PSScriptRoot
$FileNameToWatch = "MyLogFile.TXT"
$FilePathNameToWatch = Join-Path -Path $FilePathToWatch -ChildPath $FileNameToWatch
$Form_MAIN = [Form]#{
AutoSize = $false
AutoScale = $false
BackColor = '0x6699CC'
ClientSize = "1024,1000"
Location = "600,300"
MaximizeBox = $false
StartPosition = "manual"
Text = "DataCenter Operation Applications"
Topmost = $true
}
$TextBox_Output = [TextBox]#{
Anchor = 'Top, Left, Bottom, Right'
Location = '12, 12'
Multiline = $true
Name = 'TextBox_Output'
Size = "$($Form_MAIN.ClientSize.Width - 24), $($Form_MAIN.ClientSize.Height - 24)"
Text = Get-Content -Raw $FilePathNameToWatch
}
$Form_Main.Controls.Add($TextBox_Output)
Code for setting up FileSystemWatcher, ending with ShowDialog() to open the form, $watcher.Dispose() to, as mklement0 pointed out, to stop $watcher from continuing to fire after form closes.:
[IO.FileSystemWatcher]$watcher = [IO.FileSystemWatcher]#{
Path = $FilePathToWatch
NotifyFilter = [IO.NotifyFilters]::LastWrite
Filter = $FileNameToWatch
SynchronizingObject = $TextBox_Output
}
$watcher.Add_Changed({
$TextBox_Output.Text = Get-Content -Raw $FilePathNameToWatch
})
$watcher.EnableRaisingEvents = $true
$null = $Form_Main.ShowDialog()
$watcher.Dispose()
UPDATED Version:
Walk through of changes from above code, full code listed in order in following sections:
Add function GetLogFileContent for safely reading the file, or returning an empty string when the file doesn't exist.
using namespace System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms
[Application]::EnableVisualStyles()
$FilePathToWatch = $PSScriptRoot
$FileNameToWatch = "MyLogFile.TXT"
$FilePathNameToWatch = Join-Path -Path $FilePathToWatch -ChildPath $FileNameToWatch
function GetLogFileContent {
param ( [Parameter(Mandatory = $true, Position = 0)][string]$FilePathName )
if(Test-Path -PathType Leaf -LiteralPath $FilePathName) { Get-Content -Raw $FilePathName } else { '' }
}
Added code for Form's FormClosing event to shutdown $watcher so it no longer is firing events and properly disposed.
$Form_MAIN = [Form]#{
AutoSize = $false
AutoScale = $false
BackColor = '0x6699CC'
ClientSize = "1024,1000"
Location = "600,300"
MaximizeBox = $false
StartPosition = "manual"
Text = "DataCenter Operation Applications"
Topmost = $true
}
$Form_MAIN.Add_FormClosing({
$watcher.Dispose()
})
The TextBox Text property is assigned results of call to GetLogFileContent function.
$TextBox_Output = [TextBox]#{
Anchor = 'Top, Left, Bottom, Right'
Location = '12, 12'
Multiline = $true
Name = 'TextBox_Output'
Size = "$($Form_MAIN.ClientSize.Width - 24), $($Form_MAIN.ClientSize.Height - 24)"
Text = GetLogFileContent $FilePathNameToWatch
}
$Form_Main.Controls.Add($TextBox_Output)
$watcher's NotifyFilter property now set for checking for both FileName and LastWrite. Just a reminder, SynchronizingObject set to the TextBox so it can be updated on the same thread.
[IO.FileSystemWatcher]$watcher = [IO.FileSystemWatcher]#{
Path = $FilePathToWatch
NotifyFilter = [IO.NotifyFilters]::FileName -bor [IO.NotifyFilters]::LastWrite
Filter = $FileNameToWatch
SynchronizingObject = $TextBox_Output
}
Added Deleted and Renamed events to $watcher to catch deletions and renames, and using GetLogFileContent function to populate the textbox Text property.
$watcher.Add_Changed({
$TextBox_Output.Text = GetLogFileContent $FilePathNameToWatch
})
$watcher.Add_Deleted({
$TextBox_Output.Text = ''
})
$watcher.Add_Renamed({
$TextBox_Output.Text = if($_.Name -eq $FileNameToWatch) { GetLogFileContent $FilePathNameToWatch } else { '' }
})
Watcher.Dispose() removed from end of code (taken care of in FormClosing event).
$watcher.EnableRaisingEvents = $true
$null = $Form_Main.ShowDialog()

Note:
This answer addresses the question as asked.
Darin's helpful answer shows an alternative approach that doesn't unconditionally, periodically re-read the log file (based on a timer), but uses an event-based file-system watcher to only update re-read the file if and when it changes.
This is more elegant and generally preferable, except if the log file is updated very frequently, in which case you'd need a throttling mechanism (which the timer-based approach implicitly provides, though one could be implemented with the file-watcher approach too).
There is no obvious problem with your approach:
The System.Windows.Forms.Timer type fires its events on the GUI thread and therefore allows modifying the form state.
While script blocks ({ ... }) that are passed as event delegates in .add_<eventName>() calls run in a child scope of the script, thanks to PowerShell's dynamic scoping you can still read the variables from the script scope.
Since WinForms is in control of the event loop when you show a form modally with .ShowDialog(), it is sufficient to assign new content to the .Lines property of your text-box control: the control should refresh automatically (and even an explicit .Refresh() call should not make the whole form refresh).
The following is a self-contained proof of concept:
A background job is started that writes the current timestamp to a given file every second.
The WinForms code uses a timer event to read that file and update its multi-line text box control with it.
using namespace System.Windows.Forms
using namespace System.Drawing
Add-Type -AssemblyName System.Windows.Forms
# Create the form.
$form = [Form] #{
Text = "Textbox Timer-Based Refresh Demo"
Size = [Size]::new(380,200)
StartPosition = "CenterScreen"
}
# Create the textbox and add it to the form.
$form.Controls.AddRange(#(
($textBox = [TextBox] #{
Location = [Point]::new(10, 10)
Size = [Size]::new(320, 90)
MultiLine = $true
})
))
# Create a timer that fires every second, and
# reads the then-current file content.
$timer = [Timer]::new()
$timer.InterVal = 1000
$timer.add_Tick({
$textBox.Lines = Get-Content -Raw $logName
})
$timer.Start()
# The log file to read.
$logName = 't.txt'
# Create a background job that updates the log file every second.
$jb = Start-Job {
while ($true) {
Get-Date > "$using:PWD/$using:logName"
Start-Sleep 1
}
}
# Show the form modally.
$null = $form.ShowDialog()
# Clean up
$timer.Dispose()
$jb | Remove-Job -Force

Related

Powershell: Output a search from a csv to a small gui

maybe one of you experts can help a complete newbie (I don't know if what I want is even feasible).
Let's assume I have a CSV file with various data. (see csv_screenshot)csv_screenshot
I import this data via Powershell into a small GUI . How can I make it so that when I search for "Paris", I really only get the output for Paris in the GUI as a list view like this (see powershell_screenshot)
powershell_screenshot
Currently the output in the GUI looks like this (see current_result.png). How do I get it nicely formatted as a list in there. I really want to insert it like this (via Out Grid View it is no problem)
current_result.png
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Windows.Forms.Application]::EnableVisualStyles();
function search_csv {
$Input = $textbox_Search.text
$Input = "*$Input*"
$Input_Result = import-csv -path C:\Users\check.csv -Header "Location", "Client", "Mobile Device"
$output_TextBox.text = $Input_Result -like $Input
}
$search_csvtool = New-Object System.Windows.Forms.Form
$search_csvtool.Text = "CSV Search"
$search_csvtool.Size = New-Object System.Drawing.Size(674,500)
$search_csvtool.FormBorderStyle ="FixedDialog"
$search_csvtool.TopMost = $true
$search_csvtool.MaximizeBox = $false
$search_csvtool.MinimizeBox = $true
$search_csvtool.ControlBox = $true
$search_csvtool.StartPosition = "CenterScreen"
$search_csvtool.Font = "Courier New"
$label_Search = New-Object System.Windows.Forms.Label
$label_Search.Location = New-Object System.Drawing.Size(195,18)
$label_Search.Size = New-Object System.Drawing.Size(265,32)
$label_Search.TextAlign ="MiddleCenter"
$label_Search.Text = "Please enter "
$search_csvtool.Controls.Add($label_Search)
$textbox_Search = New-Object System.Windows.Forms.TextBox
$textbox_Search.Location = New-Object System.Drawing.Size(195,50)
$textbox_Search.Size = New-Object System.Drawing.Size(266,37)
$search_csvtool.Controls.Add($textbox_Search)
$button_Search = New-Object System.Windows.Forms.Button
$button_Search.Location = New-Object System.Drawing.Size(195,80)
$button_Search.Size = New-Object System.Drawing.Size(266,24)
$button_Search.TextAlign = "MiddleCenter"
$button_Search.Text = "Search"
$button_Search.Add_Click({search_csv})
$search_csvtool.Controls.Add($button_Search)
$output_TextBox = New-Object System.Windows.Forms.TextBox
$output_TextBox.Multiline = $true;
$output_TextBox.Location = New-Object System.Drawing.Size(16,130)
$output_TextBox.Size = New-Object System.Drawing.Size(627,314)
$output_TextBox.ScrollBars = "Vertical"
$output_TextBox.ReadOnly = $true;
$search_csvtool.Controls.Add($output_TextBox)
$search_csvtool.Add_Shown({$search_csvtool.Activate()})
[void] $search_csvtool.ShowDialog()
Ok, so here's what I meant in my comment:
Start the code with
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$csvData = Import-Csv -path 'C:\Users\check.csv' -Header "Location", "Client", "Mobile Device"
function search_csv {
$searchThis = $textbox_Search.Text.Trim()
# use $script: scoping here to reference the $csvData variable
$data = $script:csvData | Where-Object {$_.Location -like "*$searchThis*"}
if ($data) {
$output_TextBox.Text = ($data | Format-List | Out-String).Trim()
}
else {
$output_TextBox.Text = "Not found.."
}
}
Then create the rest of the form as you did.
Important: Destroy the form when done with a last new code line:
$search_csvtool.Dispose()
You should then have this result:
As per your comment to empty the textbox when nothing (or just whitespace) has been entered, you could change the function to:
function search_csv {
$searchThis = $textbox_Search.Text.Trim()
# use $script: scoping here to reference the $csvData variable
$data = $script:csvData | Where-Object {$_.Location -like "*$searchThis*"}
if ([string]::IsNullOrWhiteSpace($searchThis)) {
$output_TextBox.Clear()
}
elseif ($data) {
$output_TextBox.Text = ($data | Format-List | Out-String).Trim()
}
else {
$output_TextBox.Text = "Not found.."
}
}
However, as postanote already commented, it would be much better to simply disable the search button and only enable it when something other that whitespace has been entered.
To do that, you need to add an eventhandler to the textbox:
$textbox_Search = New-Object System.Windows.Forms.TextBox
$textbox_Search.Location = New-Object System.Drawing.Size(195,50)
$textbox_Search.Size = New-Object System.Drawing.Size(266,37)
$textbox_Search.Add_TextChanged({
# enable the button when there is at least one non-whitespace character present
$button_Search.Enabled = $this.Text -match '\S'
# or use
# $button_Search.Enabled = (-not [string]::IsNullOrWhiteSpace($this.Text))
})
$search_csvtool.Controls.Add($textbox_Search)
And initialize the button to be disabled at startup:
$button_Search = New-Object System.Windows.Forms.Button
$button_Search.Location = New-Object System.Drawing.Size(195,80)
$button_Search.Size = New-Object System.Drawing.Size(266,24)
$button_Search.TextAlign = "MiddleCenter"
$button_Search.Text = "Search"
# initialize to Disabled; will be enabled as soon as there is text entered in the $textbox_Search
$button_Search.Enabled = $false
$button_Search.Add_Click({search_csv})
$search_csvtool.Controls.Add($button_Search)
Inside an event handler, you can refer to the object itself using automatic variable $this
You should be able to go thru your string output line by line and change this.
That stated, you probably won't like how it looks. Normalized colons will probably look better.
This code should do the trick. I'll let you be the judge of if it looks "better"
$dataLines = ($data | Format-List | Out-String).Trim() -split '(?>\r\n|\n)'
$dataText = #(
foreach ($dataLine in $dataLines) {
$dataLine -replace '\s{1,}\:', ':'
}
) -join [Environment]::Newline
$output_TextBox.Text = $dataText

Populate ComboBox2 depending on ComboBox1 selection

Few days of searching and trying multiple options, but nothing is working actually. With the previous code posted.
I've imported all my objects from XAML (all set in variables), I don't know if that would be a problem for that. I don't think since everything else seems to work properly.
I just want my Tab2CBB2 to show values depending on the selection of Tab1CBB1. Anyone could help ? (I haven't paste the entire code, neither the paths but you can probably help me with that). Note that those are two of my multiple tries. Thanks
[void][System.Reflection.Assembly]::LoadWithPartialName('presentationframework')
[xml]$xaml = #" ...
"#
#Read XAML (Parse it)
$reader=(New-Object System.Xml.XmlNodeReader $XAML)
$Window=[Windows.Markup.XamlReader]::Load( $reader )
$listQA14 = Get-ChildItem $pathQA14 -name
$listQA15 = Get-ChildItem $pathQA15 -name
$listDBQA = Get-ChildItem $pathDBQA -name
$Tab1CBB1_SelectedIndexChanged= {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
Switch ($Tab1CBB1.Text) {
'QA14' {
$ListQA14 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'QA15' {
$ListQA15 | ForEach { $Tab1CBB2.Items.Add($_) }
}
'ARIELDBQA' {
$ListDBQA | ForEach { $Tab1CBB2.Items.Add($_) }
}
}
}
$Tab1CBB1.add_SelectedIndexChanged($Tab1CBB1_SelectedIndexChanged)
#Displays the Window
$Window.ShowDialog() | out-null
Here is another option tried :
#ComboBox1------
$ItemsCBB1 = #('ARIELDBQA','QA14','QA15')
foreach ($Item in $ItemsCBB1) {
$Tab1CBB1.Items.Add($Item)
}
#ComboBox2-------
if ($Tab1CBB1.SelectedItem -eq 'QA14') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA14) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'QA15') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listQA15) {
$Tab1CBB2.Items.Add($Serie)
}
}
elseif ($Tab1CBB1.SelectedItem -eq 'listDBQA') {
$Tab1CBB2.Items.Clear()
foreach ($Serie in $listDBQA) {
$Tab1CBB2.Items.Add($Serie)
}
}
Tried this also : $Selection = $Tab1CBB1.SelectedItem.ToString()
Variable $selection put after 'if', but not working
Note that when I indicate what the current selection would be, it is working properly. The problem seems to come from 'recording' the selection a the time of clicking... Thnaks !
Basically, all you have to do is inside the $Tab1CBB1_SelectedIndexChanged scriptblock use the various lists with script scoping.
Without that, the variables are unknown inside the script block.
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $script:ListQA14 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'QA15' { $script:ListQA15 | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
'ARIELDBQA' { $script:ListDBQA | ForEach-Object { $Tab1CBB2.Items.Add($_) } }
}
}
Another method could be to dynamically get the items to enter in the combobox, especially since these are file lists and can change while your form is being used:
$Tab1CBB1_SelectedIndexChanged = {
$Tab1CBB2.Items.Clear() # Clear the list
$Tab1CBB2.Text = $null # Clear the current entry
switch ($Tab1CBB1.Text) {
'QA14' { $path = $script:pathQA14 ; break }
'QA15' { $path = $script:pathQA15 ; break }
'ARIELDBQA' { $path = $script:pathDBQA }
}
Get-ChildItem -Path $path -Name | ForEach-Object { $Tab1CBB2.Items.Add($_) }
}
A simple example of what you seem to be after is something like this.
### Powershell populate ComboBox 2 based on ComboBox 1 Selection
Add-type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form1 = New-Object 'System.Windows.Forms.Form'
$labelDoubleClickOnAnyItem = New-Object 'System.Windows.Forms.Label'
$listbox2 = New-Object 'System.Windows.Forms.ListBox'
$listbox1 = New-Object 'System.Windows.Forms.ListBox'
$buttonOK = New-Object 'System.Windows.Forms.Button'
$form1_Load={
$items=1..9 |
ForEach-Object {"List item $_"}
$listbox1.Items.AddRange($items)
}
$listbox1_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox2.Items.Add($listbox1.SelectedItem)
$listbox1.Items.Remove($listbox1.SelectedItem)
}
$listbox2_MouseDoubleClick=[System.Windows.Forms.MouseEventHandler]{
$listbox1.Items.Add($listbox2.SelectedItem)
$listbox2.Items.Remove($listbox2.SelectedItem)
}
$form1.Controls.Add($labelDoubleClickOnAnyItem)
$form1.Controls.Add($listbox2)
$form1.Controls.Add($listbox1)
$form1.Controls.Add($buttonOK)
$form1.AcceptButton = $buttonOK
$form1.ClientSize = '378, 388'
$form1.FormBorderStyle = 'FixedDialog'
$form1.MaximizeBox = $False
$form1.MinimizeBox = $False
$form1.Name = 'form1'
$form1.StartPosition = 'CenterScreen'
$form1.Text = 'Form'
$form1.add_Load($form1_Load)
$labelDoubleClickOnAnyItem.Font = 'Microsoft Sans Serif, 9.75pt, style=Bold, Italic'
$labelDoubleClickOnAnyItem.Location = '13, 13'
$labelDoubleClickOnAnyItem.Name = 'labelDoubleClickOnAnyItem'
$labelDoubleClickOnAnyItem.Size = '353, 41'
$labelDoubleClickOnAnyItem.TabIndex = 3
$labelDoubleClickOnAnyItem.Text = 'Double click on any item to move it from one box to the other.'
$listbox2.FormattingEnabled = $True
$listbox2.Location = '200, 67'
$listbox2.Name = 'listbox2'
$listbox2.Size = '166, 277'
$listbox2.Sorted = $True
$listbox2.TabIndex = 2
$listbox2.add_MouseDoubleClick($listbox2_MouseDoubleClick)
$listbox1.FormattingEnabled = $True
$listbox1.Location = '12, 67'
$listbox1.Name = 'listbox1'
$listbox1.Size = '167, 277'
$listbox1.Sorted = $True
$listbox1.TabIndex = 1
$listbox1.add_MouseDoubleClick($listbox1_MouseDoubleClick)
$buttonOK.Anchor = 'Bottom, Right'
$buttonOK.DialogResult = 'OK'
$buttonOK.Location = '291, 353'
$buttonOK.Name = 'buttonOK'
$buttonOK.Size = '75, 23'
$buttonOK.TabIndex = 0
$buttonOK.Text = '&OK'
$form1.ShowDialog()
Or even this using just the ComboBox elements
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = New-Object System.Drawing.Point(498,459)
$Form.text = 'Form'
$Form.TopMost = $false
$Form.StartPosition = 'CenterScreen'
$ComboBox1 = New-Object system.Windows.Forms.ComboBox
$ComboBox1.text = ''
$ComboBox1.width = 216
$ComboBox1.height = 75
#('ComboxItem1','ComboxItem2','ComboxItem3') |
ForEach-Object {[void] $ComboBox1.Items.Add($_)}
$ComboBox1.location = New-Object System.Drawing.Point(31,41)
$ComboBox1.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
$ComboBox2 = New-Object system.Windows.Forms.ComboBox
$ComboBox2.text = ''
$ComboBox2.width = 213
$ComboBox2.height = 38
$ComboBox2.location = New-Object System.Drawing.Point(32,73)
$ComboBox2.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)
Function Add-ItemToComboBox2
{
$ComboBox2.Items.Add($ComboBox1.SelectedItem)
$ComboBox2.Refresh()
}
$Form.controls.AddRange(
#(
$ComboBox1,
$ComboBox2
)
)
$ComboBox1.Add_SelectedIndexChanged({ Add-ItemToComboBox2 })
[void]$Form.ShowDialog()
You have not stated what UX/UI tool you are using to design and implement your
GUI. Yet, hard coding is also just a challenge. So consider using one of these for your UX/UI effort. Your UX/UI should just work, whether you have PowerShell code behind it (or any other language). Your PowerShell code should just work, regardless of the UX/UI that may be implemented. Your UX/UI code should be separate from your ops code. You call your ops code from the UX/UI.
Free:
https://poshgui.com
https://www.youtube.com/results?search_query=poshgui
https://visualstudio.microsoft.com/vs/community
Be sure to thoroughly read the licensing agreement for VS.
https://www.youtube.com/results?search_query=vs+2019+community+winforms
Purchase
https://ironmansoftware.com/psscriptpad
https://www.youtube.com/results?search_query=psscriptpad
https://ironmansoftware.com/powershell-pro-tools
https://www.youtube.com/results?search_query=powershell-pro-tools
https://www.sapien.com/software/powershell_studio
https://www.youtube.com/results?search_query=Sapien%27s+powershell+studio++winforms
https://visualstudio.microsoft.com/vs
https://www.youtube.com/results?search_query=vs+2019+community+winforms

How to do looping for checking an existing folder with PowerShell?

I want to decide which folder that I need to choose based on my data, then if I can't find it, it will show the GUI for waiting and do looping to check it. I try this code, I can find the folder, but when I can't find it once I want to show the GUI it returns some error.
This is how I checking the folder
function FIND {
Write-Host "call the function that can call the GUI.ps1 script"
$Path = "D:\Process"
Write-Host "Starting Mapping SSID and Finding Job"
$SSID_Unit = "111dddddfafafesa"
Try{
$Path_Job = (Get-Item (Get-ChildItem "$Path\*\SSID_LST" | Select-String -Pattern "$SSID_Unit").Path).Directory.FullName
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $Path_Job.FullName
Attempts = 1
}
Write-Host "Job'$($global:Result.FileName)' Exists. Found after $($global:Result.Attempts) attempts." -ForegroundColor Green
Write-Host "Continue to Assigned Job"
Pause
} Catch {
Write-Host "Waiting for the jobss"
& D:\X\Wait_GUI.ps1 -Path $Path_Job -MaxAttempts 20
Write-Host "Job not found after $($global:Result.Attempts) attempts." -ForegroundColor Red
}
}
FIND
This is the GUI
Param (
[string]$Path = '*.*',
[string]$MaxAttempts = 5
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# set things up for the timer
$script:nAttempts = 0
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # 1 second
$timer.Add_Tick({
$global:Result = $null
$script:nAttempts++
$Path_Job = Get-Item -Path $Path
if ($Path_Job) {
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $Path_Job.FullName
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
elseif ($script:nAttempts -ge $MaxAttempts) {
$global:Result = [PSCustomObject]#{
Exists = $false
FileName = ''
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
})
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '617,418'
$Form.text = "AutoGM"
$Form.BackColor = "#8b572a"
$Form.TopMost = $false
$Form.WindowState = 'Maximized'
$Label1 = New-Object system.Windows.Forms.Label
$Label1.text = "UNDER AUTOMATION PROCESS"
$Label1.AutoSize = $true
$Label1.width = 25
$Label1.height = 10
$Label1.Anchor = 'top,right,bottom,left'
$Label1.ForeColor = "#ffffff"
$Label1.Anchor = "None"
$Label1.TextAlign = "MiddleCenter"
$Label2 = New-Object system.Windows.Forms.Label
$Label2.text = "Waiting for the job..."
$Label2.AutoSize = $true
$Label2.width = 25
$Label2.height = 10
$Label2.ForeColor = "#ffffff"
$Label2.Anchor = "None"
$Label2.TextAlign = "MiddleCenter"
$Form.controls.AddRange(#($Label1,$Label2))
[void]$Form.Show()
# Write-Host $Form.Height
# Write-Host $Form.Width
$Label1.location = New-Object System.Drawing.Point(($Form.Width*0.35), ($Form.Height*0.4))
$Label2.location = New-Object System.Drawing.Point(($form.Width*0.43), ($Form.Height*0.5))
$L_S = (($Form.Width/2) - ($Form.Height / 2)) / 15
$Label1.Font = "Microsoft Sans Serif, $L_S, style=Bold"
$Label2.Font = "Microsoft Sans Serif, $L_S, style=Bold"
$Form.controls.AddRange(#($Label1,$Label2))
# start the timer as soon as the dialog is visible
$Form.Add_Shown({ $timer.Start() })
$Form.Visible = $false
[void]$Form.ShowDialog()
# clean up when done
$Form.Dispose()
if not found, it return this
Waiting for the jobss
Job not found after 1 attempts.
and the GUI is not shown
Ok, first of all, you are using different tests for the file and/or directory in the code and in the GUI. Furthermore, you call the GUI.ps1 file with a path set to a $null value.
I would change your code to something like this:
$Path = "D:\Process\*\SSID_LST\*" # the path to look for files
$SSID_Unit = "111dddddfafafesa" # the Search pattern to look for inside the files
function Test-FileWithGui {
[CmdletBinding()]
param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $true, Position = 2)]
[string]$Pattern,
[int]$MaxAttempts = 5
)
Write-Host "Starting Mapping SSID and Finding Job"
# set up an 'empty' $global:Result object to return on failure
$global:Result = '' | Select-Object #{Name = 'Exists'; Expression = {$false}}, FileName, Directory, #{Name = 'Attempts'; Expression = {1}}
# test if the given path is valid. If not, exit the function
if (!(Test-Path -Path $Path -PathType Container)) {
Write-Warning "Path '$Path' does not exist."
return
}
# try and find the first file that contains your search pattern
$file = Select-String -Path $Path -Pattern $Pattern -SimpleMatch -ErrorAction SilentlyContinue | Select-Object -First 1
if ($file) {
$file = Get-Item -Path $file.Path
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Directory = $file.DirectoryName
Attempts = 1
}
}
else {
& "D:\GUI.ps1" -Path $Path -Pattern $Pattern -MaxAttempts $MaxAttempts
}
}
# call the function that can call the GUI.ps1 script
Test-FileWithGui -Path $Path -Pattern $SSID_Unit -MaxAttempts 20
# show the $global:Result object with all properties
$global:Result | Format-List
# check the Global result object
if ($global:Result.Exists) {
Write-Host "File '$($global:Result.FileName)' Exists. Found after $($global:Result.Attempts) attempts." -ForegroundColor Green
}
else {
Write-Host "File not found after $($global:Result.Attempts) attempts." -ForegroundColor Red
}
Next the GUI file.
As you are now searching for a file that contains some text, you need a third parameter to call this named Pattern.
Inside the GUI file we perform the exact same test as we did in the code above, using the parameter $Pattern as search string:
Param (
[string]$Path,
[string]$Pattern,
[int]$MaxAttempts = 5
)
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
# set things up for the timer
$script:nAttempts = 0
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000 # 1 second
$timer.Add_Tick({
$global:Result = $null
$script:nAttempts++
# use the same test as you did outside of the GUI
# try and find the first file that contains your search pattern
$file = Select-String -Path $Path -Pattern $Pattern -SimpleMatch -ErrorAction SilentlyContinue | Select-Object -First 1
if ($file) {
$file = Get-Item -Path $file.Path
$global:Result = [PSCustomObject]#{
Exists = $true
FileName = $file.FullName
Directory = $file.DirectoryName
Attempts = $script:nAttempts
}
$timer.Dispose()
$Form.Close()
}
elseif ($script:nAttempts -ge $MaxAttempts) {
$global:Result = [PSCustomObject]#{
Exists = $false
FileName = $null
Directory = $null
Attempts = $script:nAttempts
}
$script:nAttempts = 0
$timer.Dispose()
$Form.Close()
}
})
$Form = New-Object system.Windows.Forms.Form
$Form.ClientSize = '617,418'
$Form.Text = "AutoGM"
$Form.BackColor = "#8b572a"
$Form.TopMost = $true
$Form.WindowState = 'Maximized'
# I have removed $Label2 because it is easier to use
# just one label here and Dock it to Fill.
$Label1 = New-Object system.Windows.Forms.Label
$Label1.Text = "UNDER AUTOMATION PROCESS`r`n`r`nWaiting for the job..."
$Label1.AutoSize = $false
$Label1.Dock = 'Fill'
$Label1.TextAlign = "MiddleCenter"
$Label1.ForeColor = "#ffffff"
$L_S = (($Form.Width/2) - ($Form.Height / 2)) / 10
$Label1.Font = "Microsoft Sans Serif, $L_S, style=Bold"
$Form.controls.Add($Label1)
# start the timer as soon as the dialog is visible
$Form.Add_Shown({ $timer.Start() })
[void]$Form.ShowDialog()
# clean up when done
$Form.Dispose()
The results during testing came out like below
If the file was found within the set MaxAttempts tries:
Starting Mapping SSID and Finding Job
Exists : True
FileName : D:\Process\test\SSID_LST\blah.txt
Directory : D:\Process\test\SSID_LST
Attempts : 7
File 'D:\Process\test\SSID_LST\blah.txt' Exists. Found after 7 attempts.
When the file was NOT found:
Starting Mapping SSID and Finding Job
Exists : False
FileName :
Directory :
Attempts : 20
File not found after 20 attempts.
If even the folder $Path was not found, the output is
Starting Mapping SSID and Finding Job
WARNING: Path 'D:\Process\*\SSID_LST\*' does not exist.
Exists : False
FileName :
Directory :
Attempts : 1
File not found after 1 attempts.
Hope that helps

PS Forms - Unhandled Exception using BREAK in ForEach loop

Following on from a previous question
The actual code is a lot more complex, but the following is working example of my loop:
Add-Type -AssemblyName System.Windows.Forms
$source = '\\servera\files'
$destination = '\\server b\files'
$form = New-Object System.Windows.Forms.Form
$CopyOutput = New-Object System.Windows.Forms.Label
$CopyOutput.Location = '10,15'
$CopyOutput.Size = '350,20'
$form.Text = "$DomainName Folder/Archive Copy"
$form.Size = '380,130'
$form.CancelButton = $ExitButton
$form.Add_FormClosing({
$script:CancelLoop = $true
})
$StartButton = New-Object System.Windows.Forms.Button
$StartButton.Name = 'StartButton'
$StartButton.Location = '10,50'
$StartButton.Size = '75,23'
$StartButton.Text = 'Start Copy'
$StartButton.Enabled = $true
$PauseButton = New-Object System.Windows.Forms.Button
$PauseButton.Location = '100,50'
$PauseButton.Size = '75,23'
$PauseButton.Text = 'Pause Copy'
$PauseButton.Enabled = $true
$StopButton = New-Object System.Windows.Forms.Button
$StopButton.Location = '190,50'
$StopButton.Size = '75,23'
$StopButton.Text = 'Stop Copy'
$StopButton.Enabled = $true
$ExitButton = New-Object System.Windows.Forms.Button
$ExitButton.Name = 'ExitButton'
$ExitButton.Location = '280,50'
$ExitButton.Size = '75,23'
$ExitButton.Text = 'Exit'
$ExitButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$form.Controls.AddRange(#($StartButton,$PauseButton,$StopButton,$ExitButton,$CopyOutput))
$StopButton.Add_Click({
$script:PauseToggle = $false
$script:CancelLoop = $true
})
$PauseButton.Add_Click({
# Boolean change value to true/false
$script:PauseToggle = !$script:PauseToggle
})
$StartButton.Add_Click({
$script:CancelLoop = $false
$script:PauseToggle = $false
$StopButton.Enabled = $true
$StartButton.Enabled = $false
Get-ChildItem -LiteralPath $Source -Recurse -File | ForEach {
Copy-Item -LiteralPath $_.FullName -Destination $Destination
$CopyOutput.Text = ('Copying' + $_.FullName)
[System.Windows.Forms.Application]::DoEvents()
If($script:CancelLoop -eq $true) {
$CopyOutput.Text = 'Cancel copy'
#Exit the loop
Break;
}
If ($script:PauseToggle) {
$CopyOutput.Text = 'Paused'
Do {
[System.Windows.Forms.Application]::DoEvents()
} Until (!$script:PauseToggle)
}
}
$CancelButton.Enabled = $false
$StartCopyButton.Enabled = $true
})
$form.ShowDialog()
$form.Dispose()
All the articles I can find say that this code should work ok - can anyone advise how to avoid the unhandled exception which results when the "Stop" button is pressed?
All the articles I can find say that this code should work ok - can anyone advise how to avoid the unhandled exception which results when the "Stop" button is pressed?
From what I can see in my research, this is an issue with using BREAK in forms. I did however find a workaround from here
Using this commandlet will stop the current pipeline instead of using BREAK
Filter Stop-Pipeline {
$sp = { Select-Object -First 1 }.GetSteppablePipeline($MyInvocation.CommandOrigin)
$sp.Begin($true)
$sp.Process(0)
}

powershell: change GUI element when async FileSystemWatcher fires

I like to change a GUI element (windows-form in powershell ISE), when a new file is created. Therefore I set up a form and start a filesystemwatcher in another runspace (MWE):
# this function should be called when a new file is created
function foobar(){
$form.BackColor = "black"
}
# set up runspace for async FileSystemWatcher
$Runspace = [runspacefactory]::CreateRunspace()
$PowerShell = [System.Management.Automation.PowerShell]::Create()
$PowerShell.runspace = $Runspace
$Runspace.Open()
[void]$PowerShell.AddScript({
$logFile = 'C:\powershell\test.log'
$dirName = 'C:\powershell\'
$hotFolder = New-Object IO.FileSystemWatcher $dirName -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $hotFolder Created -SourceIdentifier FileCreated -Action {
$name = $Event.SourceEventArgs.Name
$path = $Event.SourceEventArgs.FullPath
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
Out-File -FilePath $logFile -Append -InputObject "The file '$name' was $changeType at $timeStamp"
# this call does not work
foobar
}
})
$AsyncObject = $PowerShell.BeginInvoke()
# set up form
$form = New-Object System.Windows.Forms.Form
$form.ShowDialog()
The FileSystemWatcher works (log-file is written), but the call of "foobar" is ignored / does not work.
My first try was to Register the FileSystemWatcher within the form, which does not work (similar to this: FileSystemWatcher and GUI). I found this thread FileSystemWatcher kommt nicht mit Form zurecht (german only), which suggests the use of runspaces.
The runspace solves the stuck GUI-problem, but I need a way to trigger events in the form, when the fileSystemWatcher registers a new file. How can I achive that?
in short:
1) how can the FileSystemWatcher trigger a change of a GUI-element
2) are runspaces the right approach in this case
I'm not an expert in powershell (still learning). Any help and suggestions are appreciated.
Thanks in advance.
I did some research and found a solution:
added a synchronized hashtable to share variables between runspaces
added a button of the form to the hashtable (this button will be hidden in the final version drawing.Size(0,0) )
fileSystemWatcher makes use of performclick() to click the button
button calls desired function on click
There is still a point which feel awkward:
to unregister the fileSystemWatcher I set a shared variable to 1 and trigger the fileSystemWatcher by generating a file
Is there a more elegant way to do this?
Do I miss some points where the code is unnecessarily complicated?
Any comments are appreciated.
Here is a MWE. To use it set the $dir variable to your need. (MWE does not work in not updated powershell which ships with Win7)
# set working dir
$dir = 'C:\Downloads'
Write-Host 'working dir' $dir
# create function which is called when new file is created
function showPath($path){
$label.Text = $path
}
# set up runspace for async FileSystemWatcher
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.Open()
# synchronized hashtable and hashtable elements
$sync = [Hashtable]::Synchronized(#{})
$sync.path = 1 # random start value
$sync.exit = 0 # switch: if set to 1 fileSystemWatcher will be unregistert when next event occurs
$sync.dir = $dir
$btnNewFile= New-Object System.Windows.Forms.Button
$btnNewFile.Location = New-Object System.Drawing.Size(220,10)
$btnNewFile.Size = New-Object System.Drawing.Size(150,23)
$btnNewFile.Text = "do not click - fake button"
$btnNewFile.Add_Click({
$newPath = $sync.path
$form.text = $newPath
showPath($newPath)
})
$sync.btnNewFile = $btnNewFile
$Runspace.SessionStateProxy.SetVariable("sync", $sync)
$PowerShell = [System.Management.Automation.PowerShell]::Create()
$PowerShell.runspace = $Runspace
[void]$PowerShell.AddScript({
$logFile = Join-Path $sync.dir test.log
$dirName = $sync.dir
$hotFolder = New-Object IO.FileSystemWatcher $dirName -Property #{IncludeSubdirectories = $false;NotifyFilter = [IO.NotifyFilters]'FileName, LastWrite'}
Register-ObjectEvent $hotFolder Created -SourceIdentifier FileCreated -Action {
$path = $Event.SourceEventArgs.FullPath
$name = $Event.SourceEventArgs.Name
$changeType = $Event.SourceEventArgs.ChangeType
$timeStamp = $Event.TimeGenerated
# check if exit condition is met
if($sync.exit -eq 1){
Out-File -FilePath $logFile -Append -InputObject "Exit file: '$name'"
Unregister-Event FileCreated
}
else{
Out-File -FilePath $logFile -Append -InputObject "The file '$name' was $changeType at $timeStamp"
# set path to synchroniszed variable
$sync.path = $path
# click Button to trigger function call
$sync.btnNewFile.PerformClick()
}
}
})
$AsyncObject = $PowerShell.BeginInvoke()
# GUI setup
$labelHeader = New-Object System.Windows.Forms.Label
$labelHeader.Location = New-Object System.Drawing.Size(10,50)
$labelHeader.Size = New-Object System.Drawing.Size(100,23)
$labelHeader.Text = 'path to new file:'
$label = New-Object System.Windows.Forms.Label
$label.Location = New-Object System.Drawing.Size(110,50)
$label.Size = New-Object System.Drawing.Size(200,23)
$label.Text = 'no file created'
$global:fileCounter = 0
$btnCreateFile = New-Object System.Windows.Forms.Button
$btnCreateFile.Location = New-Object System.Drawing.Size(10,10)
$btnCreateFile.Size = New-Object System.Drawing.Size(100,23)
$btnCreateFile.Text = "New File"
$btnCreateFile.Add_Click({
$global:fileCounter+=1
$fileName = "$global:fileCounter.txt"
$newFile = Join-Path $dir $fileName
New-Item $newFile -ItemType file
})
$btnExit = New-Object System.Windows.Forms.Button
$btnExit.Location = New-Object System.Drawing.Size(110,10)
$btnExit.Size = New-Object System.Drawing.Size(100,23)
$btnExit.Text = "&Exit"
$btnExit.Add_Click({
$sync.Exit = 1
$btnCreateFile.PerformClick()
$Powershell.Dispose()
$form.Close()
})
# set up form
$form = New-Object System.Windows.Forms.Form
$form.Width = 400
$form.Height = 120
$form.Controls.Add($btnCreateFile)
$form.Controls.Add($btnExit)
$form.Controls.Add($labelHeader)
$form.Controls.Add($label)
$form.Controls.Add($sync.btnNewFile)
$form.ShowDialog()
I really like your solution, but the problem, like you say, is not working for PS2.0, including the service pack 1 for Win7, where I need.
My solution for the GUI update works both in PS2(win7) and in PS3(win10), it is based in Windows Presentation Framework(WPF) instead Windows Forms, because with WPF we can use Data Binding and the INotifyPropertyChanged interface. I have based my work in Trevor Jones web How-To ,with some tricks to get work.
For your question about Unregister the system I have translated, from VB to PS, a post from Mike Ober in SpiceWorks, in which his concept to Register and Unregister the system based in global variables and possible errors inspired me.
Here is my code:
Add-Type -AssemblyName PresentationFramework,PresentationCore,WindowsBase
Function Create-WPFWindow {
Param($Hash)
# Create a window object
$Window = New-Object System.Windows.Window
$Window.Width = '600'
$Window.Height = '300'
$Window.Title = 'WPF-CONTROL'
$window.WindowStartupLocation = [System.Windows.WindowStartupLocation]::CenterScreen
$Window.ResizeMode = [System.Windows.ResizeMode]::NoResize
# Create a Label object
$Label = New-Object System.Windows.Controls.Label
$Label.Height = 40
$Label.HorizontalContentAlignment = 'Left'
$Label.VerticalContentAlignment = 'Center'
$Label.FontSize = 15
$Label.Content = 'Actividad:'
$Hash.Label = $Label
# Create a TextBlock object
$TextBlock = New-Object System.Windows.Controls.TextBlock
$TextBlock.Height = 150
$TextBlock.FontSize = 20
$TextBlock.TextWrapping = 'Wrap'
$Hash.TextBlock = $TextBlock
# Create a Button1 object
$Button1 = New-Object System.Windows.Controls.Button
$Button1.Width = 300
$Button1.Height = 35
$Button1.HorizontalContentAlignment = 'Center'
$Button1.VerticalContentAlignment = 'Center'
$Button1.FontSize = 20
$Button1.Content = 'Iniciar'
$Hash.Button1 = $Button1
# Assemble the window
$StackPanel1 = New-Object System.Windows.Controls.StackPanel
$StackPanel1.Margin = '150,20,5,5'
$StackPanel1.Orientation = 'Horizontal'
$StackPanel1.Children.Add($Button1)
$StackPanel2 = New-Object System.Windows.Controls.StackPanel
$StackPanel2.Margin = '5,5,5,5'
$StackPanel2.Orientation = 'Vertical'
$StackPanel2.Children.Add($Label)
$StackPanel2.Children.Add($TextBlock)
$StackPanel = New-Object System.Windows.Controls.StackPanel
$StackPanel.Margin = '5,5,5,5'
$StackPanel.Children.Add($StackPanel1)
$StackPanel.Children.Add($StackPanel2)
$Window.Content = $StackPanel
# Stop the service and release the resources
$Window.Add_Closing({
$Hash.On = $false
$global:p.BeginInvoke()
$global:p.Dispose()})
$Hash.Window = $Window
}
$Hash = [hashtable]::Synchronized(#{})
# Create a WPF window and add it to a Hash table
Create-WPFWindow $Hash | Out-Null
# Create a datacontext for the TextBlock, we add it to the synchronized $Hash to update the GUI from the FileSystemWatcher Event.
$DataContext = New-Object System.Collections.ObjectModel.ObservableCollection[Object]
$Text = [string]'Pulse el botón para iniciar el sistema.'
$DataContext.Add($Text)
$Hash.TextBlock.DataContext = $DataContext
$Hash.DataContext = $DataContext
$Hash.path='C:\POWERSHELL_PROJECT\Result'
# These two vars are for my needs, you can obviate them or delete
$Hash.urlLOGIN=''
$Hash.urlLOTE=''
$Hash.fileWatcher = $null
$Hash.LOG='C:\POWERSHELL_PROJECT\Result\LOG.log'
$Hash.firstEvent = $false
$Hash.On=$false
$Hash.msg=''
# Create and set a binding on the TextBlock object
$Binding = New-Object System.Windows.Data.Binding -ArgumentList '[0]'
$Binding.Mode = [System.Windows.Data.BindingMode]::OneWay
[void][System.Windows.Data.BindingOperations]::SetBinding($Hash.TextBlock,[System.Windows.Controls.TextBlock]::TextProperty, $Binding)
# Add an event for the Button1 click to Register FileSystemWatcher and Unregister it
$Hash.Button1.Add_Click({
if ($Hash.On -eq $true){
$Hash.On = $false
$Hash.Button1.Background = 'Green'
$Hash.Button1.Content = 'Iniciar'
}else{
$Hash.On = $true
$Hash.Button1.Background = 'Red'
$Hash.Button1.Content = 'Detener'
}
$p.BeginInvoke() | Out-Null
})
# Multithreading runspaces for FileSystemWatcher
$rs_dForm = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$rs_dForm.ApartmentState = 'STA'
$rs_dForm.ThreadOptions = 'ReuseThread'
$rs_dForm.Open()
$rs_dForm.SessionStateProxy.SetVariable('Hash', $Hash)
$p = [PowerShell]::Create().AddScript({
Function global:OnFileSystemWatcherError {
FileEventListener -Path $Hash.path
}
# With simple function we can refresh the Textbox and Log
Function global:Refresh-WPF-and-LOG {
$Hash.DataContext[0] = $Hash.msg
echo $Hash.msg >> $Hash.LOG
}
Function global:FileEventListener ($Path){
if ($Hash.On){
$Hash.fileWatcher = New-Object System.IO.FileSystemWatcher
$Hash.fileWatcher.Path = $Path
$Hash.fileWatcher.Filter = '*.xml'
$Hash.fileWatcher.IncludeSubdirectories = $false
$Hash.fileWatcher.InternalBufferSize = 32768
$Hash.fileWatcher.EnableRaisingEvents=$true
Register-ObjectEvent -InputObject $Hash.fileWatcher -EventName Changed -SourceIdentifier File.Changed -Action {
$Global:t = $event
if (!$Hash.firstEvent){
try{
# For example you can:
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + $event.SourceEventArgs.Name
}catch{
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + $_.Exception.Message + ' ' + $_.Exception.ItemName
}finally{
Refresh-WPF-and-LOG
}
$Hash.firstEvent=$true
}else{
$Hash.firstEvent=$false
}
}
# With this Register we control the errors from the FileSystemWatcher system, and reinit it this case
Register-ObjectEvent -InputObject $Hash.fileWatcher -EventName Error -SourceIdentifier File.Error -Action {
$Global:t = $event
$Hash.On = $false
OnFileSystemWatcherError
}
$Hash.msg = '[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + 'Servicio INICIADO.'
}else{
if ( $Hash.fileWatcher -ne $null ){
$Hash.fileWatcher.EnableRaisingEvents=$false
Unregister-Event File.Changed
Unregister-Event File.Error
$Hash.fileWatcher.Dispose()
$Hash.fileWatcher=$null
$Hash.msg='[' + $(Get-Date -Format u | foreach {$_ -replace ' ','-'}).ToString() + ']--' + 'Sistema DETENIDO.'
}
}
Refresh-WPF-and-LOG
}
FileEventListener -Path $Hash.path
})
$p.Runspace = $rs_dForm
# Show the window
$Hash.Window.ShowDialog() | Out-Null
I hope to help.