I used to use a tiny utility called ScriptMan, it would sit in the system tray, and with two clicks it would copy the contents of my selected RTF to the clipboard. I haven't found an equivalent that works on Windows 10 and doesn't monitor the clipboard constantly, so I've tried to write my own in PowerShell. It's worked - almost...
After spending a bit of (way too much of!!) my afternoon/evening on StackOverflow and several other places, I've come up with the following script:
function RTF_to_Clipboard {
$ScriptName = $this.Tag
Write-Host "ScriptName = $ScriptName"
$LabelOutput.Text = "Button $ScriptName clicked"
$ScriptFilePath = "$ScriptsFolder$ScriptName"
Write-Host "$ScriptFilePath = $ScriptFilePath"
#https://stackoverflow.com/questions/65543792/how-to-write-contents-of-an-rtf-file-to- windows-clipboard-with-one-liner
Add-Type -AssemblyName System.Windows.Forms
$rtf = Get-Content -Path $ScriptFilePath
$IsRTF = ($rtf | Select-String '\{\\rtf1' -Quiet)
Write-Host "File $ScriptFilePath" (&{If($IsRTF) {"is"} Else {"IS NOT"}}) "an RTF file."
#[Windows.Forms.Clipboard]::SetText($rtf, [System.Windows.Forms.TextDataFormat]::Rtf)
#[Windows.Forms.Clipboard]::SetDataObject($rtf, $true)
[Windows.Forms.Clipboard]::SetText($rtf, [System.Windows.Forms.TextDataFormat]::Text)
Write-Host $rtf
}
This function is launched by a GUI:
# https://theitbros.com/powershell-gui-for-scripts/
$ScriptsFolder = 'C:\Users\me\OneDrive\Documents\IT\scripts\'
$Scripts = #(
'script 1 - multiline text.rtf',
'script 2 - single line text.rtf',
'script 3 - multiline RTF.rtf'
)
Add-Type -assembly System.Windows.Forms
#Now create the screen form (window) to contain elements:
$main_form = New-Object System.Windows.Forms.Form
#Set the title and size of the window:
$main_form.Text ='Copy Scripts to Clipboard'
$main_form.Width = 600
$main_form.Height = 400
#If the elements on the form are out of window bounds, use the AutoSize property to make the form automatically stretch.
$main_form.AutoSize = $true
#Create button and label for each item in the $Scripts array
$loopcount = 0
foreach($Command in $Scripts)
#for ($loopcount=0; $loopcount -lt $Scripts.count; $loopcount++)
{
$YPos = 25 * $loopcount + 5
$loopcount++
# $Command = $Scripts[$loopcount]
#Now put the button on the form:
Echo "Create button $loopcount for function $Command"
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(0,$YPos)
$Button.Size = New-Object System.Drawing.Size(60,23)
$Button.Text = "Load"
$Button.Tag = $Command
$Button.Add_Click({
$LabelOutput.Text = "$Command clicked"
Write-Host "$Command clicked"
RTF_to_Clipboard
# RTF_to_Clipboard -ScriptName $Command
})
$main_form.Controls.Add($Button)
#Create a label element on the form:
$Label = New-Object System.Windows.Forms.Label
$Label.Text = $Command
$Label.Location = New-Object System.Drawing.Point(70,$YPos)
$Label.AutoSize = $true
$main_form.Controls.Add($Label)
}
$LabelOutput = New-Object System.Windows.Forms.Label
$LabelOutput.Text = "No command selected yet"
$LabelOutput.Location = New-Object System.Drawing.Point(0,150)
$LabelOutput.AutoSize = $true
$main_form.Controls.Add($LabelOutput)
#Now you can display the form on the screen.
$main_form.ShowDialog()
The form buttons appear to be working, but not copying the text/RTF to the clipboard.
As you can guess from the script names, I have 3 test scripts:
Multi-line plain text
Single-line plain text
Rich Text Format
You can see also from the # remming lines out that I've tried a few methods that others have said they used:
[Windows.Forms.Clipboard]::SetText($rtf, [System.Windows.Forms.TextDataFormat]::Rtf)
This method pastes the plain text into MS word, but doesn't paste anything into NotePad++ or PuTTY (where I need them. The RTF is correct in MS Word (tables preserved), but throws a bunch of errors about "This is not a valid style name."
[Windows.Forms.Clipboard]::SetDataObject($rtf, $true)
The second method copies the single-line text to the clipboard no worries, and it pastes OK in NotePad++, PuTTY and MS Word. The multi-line text and RTF buttons appear to put nothing on the clipboard.
[Windows.Forms.Clipboard]::SetText($rtf, [System.Windows.Forms.TextDataFormat]::Text)
With the last method, the single-line scripts work fine, but the multi-line script all ends up on one line. RTF files get their raw data converted to text - i.e.: a tonne of RTF formating code displayed as plain text; again, all on one line:
{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang2057\deflangfe2057\themelang2057\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi
\froman\fcharset0\fprq2{*\panose 02020603050405020304}Times New
Roman;}{\f0\fbidi \froman\fcharset0\fprq2{*\panose
02020603050405020304}Times New Roman;} {\f37\fbidi
\fswiss\fcharset0\fprq2{*\panose
020f0502020204030204}Calibri;}{\f38\fbidi
\fswiss\fcharset0\fprq2{*\panose
00000000000000000000}Tahoma;}{\flomajor\f31500\fbidi
\froman\fcharset0\fprq2{*\panose 02020603050405020304}Times New
Roman;} etc., etc., etc., etc., etc., etc., etc., etc., etc.....
When there is nothing pasting from the clipboard, the PowerShell console is still showing the text value of the files when I call Write-Host $rtf, so I know that $rtf = Get-Content -Path $ScriptFilePath worked each time.
Has anyone got any pointers as to how to get the multi-line text copied correctly to the clipboard at least?
I should mention that some of my multi-line scripts (the files that I'm trying to copy to the clipboard) are bash scripts, not sure if their content is breaking the PowerShell clipboard, e.g.:
alias pingAll="while true; do datenow=$(date -D "hh:mm:ss"|cut
-c12-19);echo -ne $datenow; echo -ne " -> VPN: "; if ping -q -c 1 -w 5 $VPN_Server_IP>/dev/null; then echo -ne "Up "; else echo -ne " Down"; fi; echo -ne " -> Server A: "; if ping -q -c 1 -w 5
$ServerA_IP>/dev/null; then echo -ne "Up "; else echo -ne "
Down"; fi; echo -ne " -> Server B: "; if ping -q -c 1 -w 5
$ServerB_IP>/dev/null ; then echo -ne "Up "; else echo -ne "
Down"; fi; echo ; sleep 5; done"
Version = ";grep "<version" /etc/stationInfo.xml |cut -f2 -d">"|cut
-f1 -d"<"; echo -ne "Firmware Revision = ";grep "<revision" /etc/stationInfo.xml |cut -f2 -d">"|cut -f1 -d"<"; echo -ne "Firmware
Grade = ";grep "<grade" /etc/stationInfo.xml |cut -f2 -d">"|cut -f1
-d"<"; echo -ne "Firmware Date = ";grep "<date" /etc/stationInfo.xml |cut -f2 -d">"|cut -f1 -d"<"'alias fwver='echo -ne "Firmware
I also tried Set-Clipboard -Path "c:\temp\script.rtf", but it pastes nothing into NotePad++, and pastes the text or RTF into MS word as an embedded object that needs to be opened to edit, not as inline text.
Any clues how to get this working?
Thanks for your help!
Cheers.
Here's the solution that I'm running now, works just as I want it (would be nice to hide the PowerShell output window, but not a problem).
I've removed the array of script paths, and have my script scan the script folder and grab every *.txt and *.rtf in the script folder instead. To add a new script, you just need to copy it to the script folder, no changes to this script required.
As I had too many scripts to fit on one screen, I now start a new column of buttons when half of them are displayed.
If a script contains "#", its label is set to red font, to make my email addresses stand out so I can grab them quickly. (Yes, I use this for even short things I need to type regularly. :) )
# Scott Critchley
# 16/02/2022
# With help from https://stackoverflow.com/questions/71149309/powershell-copy-rtf-to-cliboard-multiple-line-text-not-working?noredirect=1#comment125769903_71149309
# and https://stackoverflow.com/questions/65543792/how-to-write-contents-of-an-rtf-file-to-windows-clipboard-with-one-liner
# Detect if we're running in the ISE or not, so can exit the shell when done, but not when in ISE
# https://www.sapien.com/forums/viewtopic.php?t=14149#:~:text=Another%20way%20to%20determine%20what,'Windows%20PowerShell%20ISE%20Host'.
[bool]$ISE = 0
switch ($Host.Name){
ConsoleHost { }
PrimalScriptHostImplementation { }
'Windows PowerShell ISE Host' {
Write-Host 'Running in Windows PowerShell ISE Host'
$ISE = 1
}
}
#https://community.spiceworks.com/topic/1710213-hide-a-powershell-console-window-when-running-a-script?page=1#entry-5990631
$Script:showWindowAsync = Add-Type -MemberDefinition #"
[DllImport("user32.dll")]
public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
"# -Name "Win32ShowWindowAsync" -Namespace Win32Functions -PassThru
Function Show-Powershell()
{
$null = $showWindowAsync::ShowWindowAsync((Get-Process -Id $pid).MainWindowHandle, 10)
}
Function Hide-Powershell()
{
$null = $showWindowAsync::ShowWindowAsync((Get-Process -Id $pid).MainWindowHandle, 2)
}
function RTF_to_Clipboard {
# param (
# [string]$ScriptName
# )
$ScriptName = $this.Tag
Write-Host "ScriptName = $ScriptName"
$LabelOutput.Text = "Button: $ScriptName clicked"
$ScriptFilePath = "$ScriptsFolder$ScriptName"
Write-Host "ScriptFilePath = $ScriptFilePath"
#https://stackoverflow.com/questions/65543792/how-to-write-contents-of-an-rtf-file-to-windows-clipboard-with-one-liner
Add-Type -AssemblyName System.Windows.Forms
$rtf = Get-Content -Path $ScriptFilePath -Raw
$IsRTF = ($rtf | Select-String '\{\\rtf1' -Quiet)
Write-Host "File $ScriptFilePath" (&{If($IsRTF) {"is"} Else {"IS NOT"}}) "an RTF file."
if ( $IsRTF )
{
[Windows.Forms.Clipboard]::SetText($rtf, [System.Windows.Forms.TextDataFormat]::Rtf)
}
else
{
[Windows.Forms.Clipboard]::SetDataObject($rtf, $true)
}
}
# https://theitbros.com/powershell-gui-for-scripts/
$ScriptsFolder = 'C:\Users\me\OneDrive\Documents\IT\scripts\'
Add-Type -assembly System.Windows.Forms
#Now create the screen form (window) to contain elements:
$main_form = New-Object System.Windows.Forms.Form
#Set the title and size of the window:
$main_form.Text ='Copy Scripts to Clipboard'
$main_form.Width = 60
$main_form.Height = 40
#If the elements on the form are out of window bounds, use the AutoSize property to make the form automatically stretch.
$main_form.AutoSize = $true
#Create button and label for each item in the $Scripts array
$loopcount = 0
$XPos = 0
$YPosCount = 0
# All files in folder from #https://www.winhelponline.com/blog/how-to-change-shortcut-lnk-targets-in-bulk-using-script/
# As implemented in Update Shortcuts.ps1
$ScriptCount = (gci $ScriptsFolder\* -Include *.txt, *.rtf |Measure-Object ).Count
Write-Host "Total Script Count = $ScriptCount"
gci $ScriptsFolder\* -File -Include *.txt, *.rtf | foreach {
$Command = $_.Name
$YPos = 25 * $YPosCount + 5
$YPosCount++
$loopcount++
if ($loopcount -eq ([math]::floor($ScriptCount/2))){
Write-Host "XPos = $XPos"
$XPos = 370
$YPosCount = 0
$YPosMax = $YPos
}
#Now put the button on the form:
Echo "Create button $loopcount for function $Command at $XPos,$YPos"
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size($XPos,$YPos)
$Button.Size = New-Object System.Drawing.Size(60,23)
$Button.Text = "Load"
$Button.Tag = $Command
$Button.Add_Click({
$LabelOutput.Text = "$Command clicked"
Write-Host "$Command clicked"
RTF_to_Clipboard
})
$main_form.Controls.Add($Button)
#Create a label element on the form:
$Label = New-Object System.Windows.Forms.Label
$Label.Text = $Command.Substring(0,$Command.Length - 4)
$LabelXPos = $XPos + 70
$Label.Location = New-Object System.Drawing.Point($LabelXPos,$YPos)
$Label.AutoSize = $true
if ($Command -like '*#*') {
$Label.ForeColor = 'Red'
$Label.BorderStyle = 'Fixed3D'
}
$main_form.Controls.Add($Label)
}
$YPos = $YPosMax + 5
$LabelOutput = New-Object System.Windows.Forms.Label
$LabelOutput.Text = "No command selected yet"
$LabelOutput.Location = New-Object System.Drawing.Point(0,$YPos)
$LabelOutput.AutoSize = $true
$main_form.Controls.Add($LabelOutput)
if($ISE)
{
Show-Powershell
}
else
{
Hide-Powershell
}
#Now you can display the form on the screen.
$main_form.ShowDialog()
if(-Not $ISE)
{[Environment]::Exit(1)}
Related
I am tring to create a script that will output the name of .txt files via for loop that counts the number of files and creates an option to open .txt file from just one click.
$s = (Get-ChildItem -Path C:\*.txt -Name | Measure-Object -Line).Lines
for($i=0; $1 -gt 5 ;$i++)
{
$c = [string[]](Get-ChildItem -Path C:\*.txt -Name)
[void] $objListBox.Items.Add('$i')
Write-Output $c
}
I am stuck with Get-childitem like in $c to get the list of file names into a variable so i can split or get the line for user option.
Thanks in advance
i am not sure what exactly you want from the script,
but here is my approach to set the items in $objListBox
$txtFiles=Get-ChildItem -Path C:\stackoverflow -Recurse -Filter *.txt
#Option 1 FullPath to Items
$txtFiles.fullname | ForEach-Object { $objListBox.Items.Add($_) }
#Option 2 Just the Name to Items
$txtFiles.name | ForEach-Object { $objListBox.Items.Add($_) }
#Option 3 Just the Name without Extentension to Items
$txtFiles.basename | ForEach-Object { $objListBox.Items.Add($_) }
#Count for All Files
$txtFiles.Count
You can use the .ToString
method to convert any given object to a string. If you want $c to be a string, the code you are looking for would look something like this:
$c = (Get-ChildItem -Path C:\*.txt -Name).ToString
Made a form for you that is created with a dynamic size and additionally a scrollbar if there is no space left. buttons that start the txt file and close the form. More comments in the code.
$basePath = "C:\"
$SearchString = Join-Path $basePath "*.txt"
$filenames = #(Get-ChildItem -Path $SearchString -Name | Sort)
$count = $filenames.count
#All you need for System.Windows.Forms to work
Add-Type -AssemblyName System.Windows.Forms
# Variables for generating size
$ButtonHeight = 35
$ButtonWidth = 450
$WindowTitle = "Choose a file"
$BottomSpace = $StartHeight = 10
$LeftSpace = $RightSpace = 30
$CurrentHeight = $Startheight
$FormHeight = 60 + $BottomSpace + $StartHeight + $ButtonHeight * $count
$FormWidth = 20 + $LeftSpace + $RightSpace + $ButtonWidth
# Create the form
$form = New-Object System.Windows.Forms.Form
$form.Text = $WindowTitle
$form.Size = New-Object System.Drawing.Size($FormWidth,$FormHeight)
$form.FormBorderStyle = "Fixed3d" # Sizeable: User may change size - Fixed3d: User may not change size
$form.StartPosition = "CenterScreen"
$Form.AutoScroll = $True # Scrollbars when you need it
$form.Topmost = $true #always on top
$form.MaximizeBox = $false #Allows to maximize window
# Generate the buttons in a foreach and arrange them
foreach ($filename in $filenames) {
$GeneratedButton = New-Object System.Windows.Forms.Button
$GeneratedButton.Location = New-Object System.Drawing.Size($LeftSpace,$CurrentHeight)
$GeneratedButton.Size = New-Object System.Drawing.Size($ButtonWidth,$ButtonHeight)
$GeneratedButton.Text = $filename
# Action to take when button is clicked -- Open file and close form
$GeneratedButton.Add_Click({ Start-Process (Join-Path $BasePath $this.text) ; $form.Close() })
$form.Controls.Add($GeneratedButton)
$CurrentHeight += $ButtonHeight
}
# Activate the Form when loaded
$form.Add_Load({
$form.Activate()
})
# Show the form when loaded, but hide any results
$form.ShowDialog() > $null # Trash any output
I hope someone can help me understand what I am doing wrong.
The script works, if I enter multiple computers or multiple lines, but if I only enter 1 line (1 value). Lets say: Computer 201... the result will be 1
I have disabled the PING feature for now until I can figure out why it does not work with 1 line.
I added the whole code so you can test it yourself.
Thank you
# Load required assemblies
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
# Drawing form and controls
$Harvester = New-Object System.Windows.Forms.Form
$Harvester.Text = "Ping Computers"
$Harvester.Size = New-Object System.Drawing.Size(490,300)
$Harvester.FormBorderStyle = "FixedDialog"
$Harvester.TopMost = $true
$Harvester.MaximizeBox = $false
$Harvester.MinimizeBox = $false
$Harvester.ControlBox = $true
$Harvester.StartPosition = "CenterScreen"
$Harvester.Font = "Segoe UI"
#======================== INPUTBOX - Computers ========================#
$label_message2 = New-Object System.Windows.Forms.Label
$label_message2.Location = New-Object System.Drawing.Size(20,10)
$label_message2.Size = New-Object System.Drawing.Size(100,15)
$label_message2.Text = "Computers"
$Harvester.Controls.Add($label_message2)
# Inputbox
$Inputbox = New-Object System.Windows.Forms.TextBox
$Inputbox.Multiline = $True;
$Inputbox.Location = New-Object System.Drawing.Size(20,30)
$Inputbox.Size = New-Object System.Drawing.Size(200,150)
$Inputbox.ScrollBars = "Vertical"
$Harvester.Controls.Add($Inputbox)
#======================== INPUTBOX - Completed ========================#
$label_message_success = New-Object System.Windows.Forms.Label
$label_message_success.Location = New-Object System.Drawing.Size(250,10)
$label_message_success.Size = New-Object System.Drawing.Size(100,15)
$label_message_success.Text = "Successful"
$Harvester.Controls.Add($label_message_success)
# Inputbox
$Inputbox_success = New-Object System.Windows.Forms.TextBox
$Inputbox_success.Multiline = $True;
$Inputbox_success.Location = New-Object System.Drawing.Size(250,30)
$Inputbox_success.Size = New-Object System.Drawing.Size(200,150)
$Inputbox_success.ScrollBars = "Vertical"
$Harvester.Controls.Add($Inputbox_success)
#======================== Ping ========================#
$button_Ping = New-Object System.Windows.Forms.Button
$button_Ping.Location = New-Object System.Drawing.Size(120,200)
$button_Ping.Size = New-Object System.Drawing.Size(80,32)
$button_Ping.TextAlign = "MiddleCenter"
$button_Ping.Text = "Ping"
$button_Ping.Add_Click({
If ($Inputbox.TextLength -eq 0){
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.MessageBox]::Show('Please enter at 1 computer to perform this action')
Return
}
[collections.arraylist] $ref = #(($Inputbox.Text -split '\r?\n').Trim() -ne '')
if(-not $ref) { return } # if the textbox is empty, don't do anything
$i = 0
1..$ref.Count | ForEach-Object {
#if(Test-Connection $ref[$i] -Quiet) {
$Inputbox_success.Text += $ref[$i] + [environment]::NewLine
$ref.RemoveAt($i)
#}
$Inputbox.Text = $ref | Out-String
$Inputbox, $Inputbox_success | ForEach-Object Refresh
}
})
$Harvester.Controls.Add($button_Ping)
# show form
$Harvester.Add_Shown({$Harvester.Activate()})
[void] $Harvester.ShowDialog()
On a single object you get a value of 1 because of this line:
[collections.arraylist] $ref = #(($Inputbox.Text -split '\r?\n').Trim() -ne '')
More specifically because when a string is evaluated against -ne '' it will return $true or $false, but when you pass an array to that it will instead output all results where that evaluated as true. The way to fix this is to force it to be an array every time. That can be done like this:
[collections.arraylist] $ref = [string[]]($Inputbox.Text -split '\r?\n') -ne ''
That fixes that, but it still leaves other issues, or, at least one other issue I see. You set $i to 0, then loop through things starting at 1, but always reference $i, which will always evaluate to 0, and effectively manipulate item 0 in the $ref array for each thing you evaluate against. So if your If statement succeeds on 3 out of 6 things it will always move the first 3 things over, regardless of what succeeds. To resolve that I changed your code as little as I could, but ended up with this:
0..($ref.Count -1) | ForEach-Object {
# if(Test-Connection $ref[$i] -Quiet) {
if($ref[$_][-1]%2){
$Inputbox_success.Text += $ref[$_] + [environment]::NewLine
}
}
$Inputbox.Text = $ref |Where{$Inputbox_success.Text -notmatch ([regex]::Escape($_))}| Out-String
$Inputbox, $Inputbox_success | ForEach-Object Refresh
So rather than remove things from the array, I rather copy the successes to the $Inputbox_success box, and then rebuild $Inputbox based on the items that are not present in $Inputbox_success at the end. Now, I just set $Inputbox.text to be this:
$Inputbox.Text = #'
Computer201
Computer202
Computer203
Computer204
Computer206
Computer207
'#
And evaluated which name ended in an odd number, but you can comment out my If statement, and re-implement your own to make it actually ping things and go off that.
What I am trying to accomplish is to create buttons that launch exe files in a certain directory when clicked, but when I try using a foreach loop to create a few buttons, all of the buttons just launch the file the last button is supposed to launch.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
$form = New-Object System.Windows.Forms.Form
$form.Text = 'Main Window'
$form.Size = New-Object System.Drawing.Size(600,400)
$flp = New-Object System.Windows.Forms.FlowLayoutPanel
$flp.Location = New-Object System.Drawing.Point(0,0)
$flp.Height = $form.Height
$flp.Width = $form.Width
$form.Controls.Add($flp)
$files = Get-ChildItem "$home\Downloads" -Include *.exe -Name
foreach ($file in $files){
$button = New-Object System.Windows.Forms.Button
$flp.Controls.Add($button)
$button.Width = 100
$button.Height = 50
$button.Text = $file
$button.Add_Click{
Start-Process -FilePath "$home\Downloads\$file"
}
}
$form.Topmost = $true
$form.ShowDialog()
Whatever I'm doing is probably pretty stupid, so I was just looking for any alternatives or solutions to this other than to just hard code everything.
It is likely that you need to use .GetNewClosure() ScriptBlock method so that each script block (button click event) holds the current value of the $file variable at the moment of enumeration.
Example of what this means:
$blocks = foreach($i in 0..5) {
{ "hello $i" }
}
& $blocks[0] # => hello 5
& $blocks[1] # => hello 5
$blocks = foreach($i in 0..5) {
{ "hello $i" }.GetNewClosure()
}
& $blocks[0] # => hello 0
& $blocks[1] # => hello 1
In that sense, and assuming this is the issue, the following should work:
foreach ($file in $files) {
$button = New-Object System.Windows.Forms.Button
$button.Width = 100
$button.Height = 50
$button.Text = $file
$thisEvent = {
Start-Process -FilePath "$home\Downloads\$file"
}.GetNewClosure()
$button.Add_Click($thisEvent)
$flp.Controls.Add($button)
}
A nice alternative to having a need to use .GetNewClosure() can be seen on this answer. The .Tag property of the Button can be used to store the information of the file's path which then can be used on the button's .Click event:
foreach ($file in $files) {
$button = New-Object System.Windows.Forms.Button
$button.Width = 100
$button.Height = 50
$button.Text = $file
# Store the file's path in the Tag's property of this Button
$button.Tag = "$home\Downloads\$file"
$button.Add_Click({
Start-Process -FilePath $this.Tag
})
$flp.Controls.Add($button)
}
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
I'm having trouble with multiple issues with a checkedlistbox. Its content is the Windows feature name (commandline parameter to install a Windows feature via Powershell) and a description which really is its more readable name. Because I develop on Windows 7 and this command is only available on a Server platform I read the data from a XML file source. The XML file was created by the output of function BuildFeaturesFile, below. I've pasted a sample at the bottom if that is a problem.
1) I have to click an item twice to check it.
2) I have to double click an item slowly and surely to avoid the program missing the second click. Would I need to change the Windows control panel config to affect this or if I wanted (not too bothered but more curious) could I reduce the polling time to slicken the interface response?
3) This is my main issue. If I use the mouse to select an item it normally updates the count correctly however when I use the keyboard despite calling the same function I get different results; the first check is seemingly missed (doubled by the look of it reading further online to undo its action - however the event "only" fires once which causes misalignment of the displayed count to the true count. I think it maybe linked to the remark in the ItemCheck event documentation "The check state is not updated until after the ItemCheck event occurs."
http://msdn.microsoft.com/en-us/library/system.windows.forms.checkedlistbox.itemcheck(v=vs.110).aspx
The main code also updates a description text box but I removed that for brevity.
4) In function ftn_CheckAllItemsInCheckList the top remmed out line works for the first item but as I'm calling it by index seemingly the action of checking the item causes the index to change which unseats the action causing it to break.
Please can you assist - at least with questions #3 and #4 if possible?
Thanks in advance.
Shaun
function BuildForm
{
# Declare objects
$frm_BuildConfigurator = New-Object System.Windows.Forms.Form
$btn_Cancel = New-Object System.Windows.Forms.Button
$gb_CC_Features = New-Object Windows.Forms.GroupBox
$clb_CC_Features = New-Object System.Windows.Forms.CheckedListBox
$btn_CC_Features_UncheckList = New-Object System.Windows.Forms.Button
$btn_CC_Features_SelectAll = New-Object System.Windows.Forms.Button
$gb_CCF_Description = New-Object Windows.Forms.GroupBox
$tb_CCF_Description = New-Object System.Windows.Forms.TextBox
#Build the form
$frm_BuildConfigurator.Text = "Build Configurator"
$frm_BuildConfigurator.StartPosition = "CenterScreen"
$frm_BuildConfigurator.Width = 380
$frm_BuildConfigurator.Height = 200
$frm_BuildConfigurator.FormBorderStyle = "FixedSingle"
$frm_BuildConfigurator.ControlBox = $false
$frm_BuildConfigurator.Controls.Add($btn_Cancel)
#Set default button behaviour
$frm_BuildConfigurator.KeyPreview = $True
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Enter") {$frm_BuildConfigurator.Close()}})
$frm_BuildConfigurator.Add_KeyDown({if ($_.KeyCode -eq "Escape") {$frm_BuildConfigurator.Close()}})
# Create the Cancel button
$btn_Cancel.Location = New-Object System.Drawing.Size(50,145)
$btn_Cancel.Size = New-Object System.Drawing.Size(55,23)
$btn_Cancel.Text = "Cancel"
$btn_Cancel.Add_Click({$frm_BuildConfigurator.Close()})
# Create the Features form elements
$frm_BuildConfigurator.Controls.Add($gb_CC_Features)
$frm_BuildConfigurator.Controls.Add($gb_CCF_Description)
# Create the Features group box
$gb_CC_Features.DataBindings.DefaultDataSourceUpdateMode = [System.Windows.Forms.DataSourceUpdateMode]::OnValidation
$gb_CC_Features.Location = New-Object System.Drawing.Point(10,6)
$gb_CC_Features.Name = "gb_CC_Features"
$gb_CC_Features.Size = New-Object System.Drawing.Size(350,132)
$gb_CC_Features.Text = "Features (0 selected)"
$gb_CC_Features.Controls.Add($clb_CC_Features)
$gb_CC_Features.Controls.Add($btn_CC_Features_SelectAll)
$gb_CC_Features.Controls.Add($btn_CC_Features_UncheckList)
$clb_CC_Features.Location = New-Object Drawing.Point 11,16
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.Add_ItemCheck({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
$clb_CC_Features.Add_Click({ftnUpdateFeatureSelectionCount})
# Populate the Features checked list box
ForEach ($FeatureItem in $script:xmlFeatures.FeatureList.Feature | Select-Object -Property Name) {
$clb_CC_Features.Items.Add($FeatureItem.Name) | Out-Null
}
# Create the Check All button
$btn_CC_Features_SelectAll.Location = New-Object System.Drawing.Point(250,20)
$btn_CC_Features_SelectAll.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_SelectAll.Text = "Check All"
$btn_CC_Features_SelectAll.Add_Click({ftn_CheckAllItemsInCheckList $clb_CC_Features})
$btn_CC_Features_SelectAll.TabIndex = 2
# Create the Uncheck All button
$btn_CC_Features_UncheckList.Location = New-Object System.Drawing.Point(250,50)
$btn_CC_Features_UncheckList.Size = New-Object System.Drawing.Size(80,23)
$btn_CC_Features_UncheckList.Text = "Uncheck All"
$btn_CC_Features_UncheckList.Add_Click({ftn_UncheckList $clb_CC_Features})
$btn_CC_Features_UncheckList.TabIndex = 2
ftnUpdateFeatureSelectionCount
#Show the Form
$frm_BuildConfigurator.ShowDialog() | Out-Null
}
function ftnUpdateFeatureDescription()
{
$tb_CCF_Description.Text = $script:FeatureDisplayNames[$clb_CC_Features.SelectedIndex]
}
function ftnUpdateFeatureSelectionCount ()
{
$gb_CC_Features.Text = "Features (" + $clb_CC_Features.CheckedItems.Count + " selected)"
}
function BuildFeaturesFile ()
{
# Only runs on Windows Server
$Features = Get-WindowsFeature
$xml = "<xml>"
$NewFeaturesFilePath = $ScriptDir + "\FeaturesNew.xml"
ForEach($Feature in $Features)
{
$xml += "<Feature Name='" + $Feature.Name + "' DisplayName='" + $Feature.DisplayName + "'>"
$xml += "</Feature>"
}
$xml += "</xml>"
$xml | Out-File -FilePath $NewFeaturesFilePath
}
function ReadFeaturesFile ()
{
[array] $script:FeatureNames = $null
[array] $script:FeatureDisplayNames = $null
$FileExists = Test-Path $FeaturesFilePath
if ($FileExists -eq $true) {
$script:xmlFeatures.Load($FeaturesFilePath)
$xml_Features = $script:xmlFeatures.SelectNodes("/FeatureList/Feature")
ForEach ($Feature in $xml_Features) {
[array] $script:FeatureNames += $Feature.Name
[array] $script:FeatureDisplayNames += $Feature.DisplayName
}
}
}
function ftn_UncheckList( $checkedListBoxObject )
{
ForEach($i in $checkedListBoxObject.CheckedIndices) { $checkedListBoxObject.SetItemCheckState($i, 'Unchecked') | Out-Null }
}
function ftn_CheckAllItemsInCheckList( $checkedListBoxObject )
{
#ForEach($i in $checkedListBoxObject.Items) { $checkedListBoxObject.SetItemChecked($checkedListBoxObject.Items.IndexOf($i), $true) }
For($index =0; $index -lt $checkedListBoxItem.Items.Count; $index++){ if($checkedListBoxItem.GetItemChecked($index)) { $checkedListBoxItem.SetItemChecked($i, $true); }}
}
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$script:xmlFeatures = $null; $script:xmlFeatures = New-Object -TypeName XML
$ScriptPath = $MyInvocation.MyCommand.Path
$ScriptDir = Split-Path -parent $ScriptPath
$FeaturesFilePath = $ScriptDir + "\Features.xml"
$BuildScriptStr = $MyInvocation.MyCommand.Definition #Full path - for script name only use $MyInvocation.MyCommand.Name
$noSelectedFeatures = 0
ReadFeaturesFile
BuildForm
# End of script
File: Features.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<FeatureList>
<Feature Name="AD-Certificate" DisplayName="Active Directory Certificate Services"/>
<Feature Name="ADCS-Cert-Authority" DisplayName="Certification Authority"/>
<Feature Name="ADCS-Web-Enrollment" DisplayName="Certification Authority Web Enrollment"/>
</FeatureList>
There are a lot of questions :
1)-2) If you want to click once to check a box you have to set the CheckOnClick property on the CheckedListBox. In you case a line can be checked only once it's selected.
$clb_CC_Features.Size = New-Object System.Drawing.Size(220,110)
$clb_CC_Features.CheckOnClick = $true
$clb_CC_Features.Add_ItemCheck({})
3) The message ItemCheck append before the line is really checked so in the function you call on this even you have look if the line is going to be cheched or unchecked.
I change
$clb_CC_Features.Add_ItemCheck({ftnChecked})
#$clb_CC_Features.Add_SelectedIndexChanged({ftnUpdateFeatureSelectionCount})
#$clb_CC_Features.Add_Click({})
...
$_ represent the value of the event.
function ftnChecked ()
{
if ($_.NewValue -eq 'checked')
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count + 1) + " selected)"
}
else
{
$gb_CC_Features.Text = "Features (" + $($clb_CC_Features.CheckedItems.Count -1) + " selected)"
}
}
4) You can try the following :
function ftn_CheckAllItemsInCheckList #( $checkedListBoxObject )
{
For($index =0; $index -lt $clb_CC_Features.Items.Count; $index++){ $clb_CC_Features.SetItemChecked($index, $true)}
}