I am trying to create in PowerShell a GUI window with dynamic content. I need to:
create a window with a random count of buttons (or other clickable items)
after a click button and related text label will be removed from the window
IMPORTANT: I cannot use a list or datagrid.
I have the following code but it still returns only last item value.
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(400,400)
$Array = New-Object System.Collections.ArrayList
$Array.add('AAA')
$Array.add('BBB')
$Array.add('CCC')
$Array.add('DDD')
foreach ($item in $Array) {
New-Variable -Force -Name "button$membershipCount" -Value (New-Object System.Windows.Forms.Button)
$thisButton = Get-Variable -ValueOnly -Include "button$membershipCount"
$thisButton.Location = New-Object System.Drawing.Size(175,(35+26*$membershipCount))
$thisButton.Size = New-Object System.Drawing.Size(250,23)
$thisButton.Text = $item
$thisButton.Add_Click({(Write-Host $thisButton.Text | Out-Null)})
$form.Controls.Add($CHANGEButton)
}
I also tried Invoke-Expression, but it doesn't return expected results:
Invoke-Expression -Command "`$thisButton.Add_Click({`$x=`"$($item)`";`write-host $x})"
Or any better idea how can I get details which button was clicked since the number of buttons is random?
The problem is that the scriptblock inside add_Click() now contains a reference to $thisbutton, which at runtime will have been replaced with the last value in the foreach loop - this is expected behavior.
You can do 1 of 2 things here.
1. Capture the $thisButton.Text (or $item) value in a closure:
# Piping to Out-Null has zero effect here, just remove it
$thisButton.Add_Click({Write-Host $thisButton.Text}.GetNewClosure())
2. Use the event arguments to determine which button was clicked at runtime:
$thisButton.Add_Click({param($Sender,$EventArgs) Write-Host $Sender.Text})
Related
I have a button on a WinForm. After clicking on the button, a function will be called which should execute Get-ADUser cmdlet.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Data
Import-Module ActiveDirectory
$ctl_frm_aduserlist = New-Object System.Windows.Forms.Form -Property #{
Size = New-Object System.Drawing.Size(500,500)
StartPosition = "CenterScreen" }
$ctl_btn_generatepreview = New-Object System.Windows.Forms.Button -Property #{
Size = New-Object System.Drawing.Size(200,30)
Location = New-Object System.Drawing.Point(10,30)
Text = "Generate Preview" }
$ctl_frm_aduserlist.Controls.Add($ctl_btn_generatepreview)
$ctl_btn_generatepreview.Add_Click({ GeneratePreview })
function GeneratePreview(){
Write-Host "GO"
Get-ADUser -Identity "user123" -Properties Name,SamAccountName | select Name,SamAccountName
Write-Host "END" }
$ctl_frm_aduserlist.ShowDialog()
Only the two "Write-Host" cmdlets will be executed by clicking on the button.
If I only execute the single line Get-ADUser in the ISE console, it works and I get the user object.
Why does Get-ADUser not work when triggered via button?
Thanks
First off, I don't have a full solution as I'm not an expert in WinForms, to feel free to correct/complete this answer.
As far as I know, the command is actually executed. You can check it by stepping though the code or Write-Host-ing bits of the object you collected with Get-ADUser and it will display the correct info:
function GeneratePreview() {
Get-ADUser -Identity "someone" | Write-Host
}
Note that if you replace Write-Host by Write-Output it does not work anymore.
The thing is, when you make a WinForms application, the standard output isn't the console anymore. I don't know where it is redirected to, but it's not visible by default. You need to either specify that you want to see your data in the console with write-host, export it to a file (Set-Content, Export-Csv, you name it) or display it in a WinForm element:
I added a new TextBox to your form (called $TextBox1 in my example), and changed GeneratePreview like this:
function GeneratePreview() {
$user = Get-ADUser "someone"
$TextBox1.Text = "$($user.name),$($user.samaccountname)"
}
I have designed a Powershell Gui Tool, the tool is designed works pretty well but I am stuck on how to populate the listview columns with the data from several xml files.
I have four column that I want populate
# ListView
$LvMain= New-Object System.Windows.Forms.Listview
$LvMain.Location = '300, 55'
$LvMain.Name = "ListViewInfo"
$LvMain.Size = "850, 600"
$LvMain.Text = "Pattern text"
$LvMain.Scrollable = $true
$LvMain.ContextMenuStrip = $ContextMenu
$LvMain.FullRowSelect = $True
$LvMain.GridLines = $True
$LvMain.UseCompatibleStateImageBehavior = $False
$LvMain.View = "Details"
$LvMain.font ="lucida console"
$LvMain.Controls.Add($ApplicationFormListViewInfo)
# Columns
$LvMain.Columns.Add("FileName")
$LvMain.Columns.Add("Folder")
$LvMain.Columns.Add("Line")
$LvMain.Columns.Add("Path")
It is meant when i hit the button " Run "
I will draw out the data from several xml files with
$GetXmlData = Get-ChildItem -Path $Path -Recurse -force -Include $FileTypes | Select-String -Pattern $Pattern
And populate the columns with the $Patterns Filename,Foldername,Line ( which line the pattern is ) and Path ( UncPath ).
However, a realy good tutorial on how listview works is hard to find.
Anyone who knows how to guide me to the right way ?
I have a single dimensional array that I get from either a get-content command or from multi-line text box input. I want to assign a property to the entries in this array, then add more properties to use later in my script.
Something like:
$items = new-object psobject
$items | add-member -membertype NoteProperty –name Name –value NotSet
$items | add-member -membertype NoteProperty –name Percent –value NotSet
$names = #($textboxInputText.Lines)
$names | % { $items | Add-Member noteproperty $_.Name $temp.($_.Name) }
foreach ($item in $items)
{
$percent = {script block}
$item.percent = $percent
}
I know this is broken code, but I wanted to give an example of where I was headed. I've searched far and wide but haven't been able to find exactly what I was looking for.
EDIT:
Code Goal: Get input from a text box or text file (single line entries). Have those entries be assigned to the "name" property, then add a second property to the array (Percent) that will need to be filled in with another block of code.
EDIT 2:
Collection is being used in the following code:
foreach ($item in $collection) {
$psConsoleFile = "PATH TO FILE.pc1"
$variable1 = "something"
$variable2 = "something else"
$command = ".`"Command1 $item.name | Command2 -Switch $variable1 -Switch2 $variable2`""
$OutputScriptBlock = "powershell.exe -PSConsoleFile $psConsoleFile -command $command"
}
The output of this is as follows:
powershell.exe -PSConsoleFile "PATH TO FILE.psc1" -command ."Command1 #{Name=name1; Percentage=}.name | Command2 -Switch1 something"
Why is the code outputting the full row instead of the name?
Also, I'm using PS 4.0 for all implementations of this script.
Ok, I see the problem here. So you have an array of Strings that you got either from a multi-line text box form object, or from a text document with the Get-Content command, but what you really want is an array of PSObjects.
A string object can not have additional properties added to it like you want (well, not conventionally, let's just not go there because you won't be happy with where things end up, trust me on this one). Instead let's take that array of strings, and for each string create a PSObject for it like you want. You will want a ForEach-Object loop for this to be simple. Either way you will want to pipe your input (either the textbox or the get-content command) to a ForEach loop, and you can assign the whole thing to a variable that will collect all of the objects to be worked with later (to update the Percent property). Something like this should accomplish what you want:
[Array]$Collection = $textboxInputText.Lines | ForEach{
New-Object PSObject -Property #{
'Name' = $_
'Percentage' = $null
}
}
I specified $Collection as the type [Array] so that if you wanted to index into it later there wouldn't be any issues should your input only be a single item. Then if you want to update the percentages you can do that by running $Collection through a ForEach loop (either inline or not)
$Collection | ForEach{ $_.Percentage = {Script Block} }
or
ForEach($Item in $Collection){
$Item.Percentage = {Script Block}
}
Now, things to note here... You are not going to be able to just assign $Collection back to your textbox. You could probably assign $Collection.Name, but that may require a newer version of PS since I don't know how backwards compatible that is. If you use a Get-Content command instead of referencing the textbox, simply change $textboxInputText.Lines | ForEach{ to Get-Content "C:\Path\To\File.txt" | ForEach{ and you should be all set.
Edit: Ok, the problem you have now isn't with the object but with how you're trying to expand a property of it within a double quotes. To access the name you would have to create a sub expression within the double quotes by wrapping $Item.Name within $(). So that line for you would look like:
$command = ".`"Command1 $($item.name) | Command2 -Switch $variable1 -Switch2 $variable2`""
I'm using WinForms with PowerShell. In my tool, I'd like a checkbox that when checked, will display a message next to it, and when unchecked, it will remove the message.
I've gotten this far (I'm sure there's a much better way to do it). This makes the message pop-up, but it doesn't go away when you uncheck the box. Any help would be appreciated. Thanks!
$Checkbox_Errors.Add_CheckStateChanged({ ### Checkbox_Errors is the name of the checkbox
if ($Checkbox_Errors.Checked -eq $true)
{
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "WARNING: May take 3-5 Minutes" ### When checked, this is what it should display
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$groupbox.Controls.Add($ErrorWarning1)
}
})
$Checkbox_Errors.Add_CheckStateChanged({
if ($Checkbox_Errors.Unchecked -eq $true)
{
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "" ### I attempted this, where it would re-write
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$groupbox.Controls.Add($ErrorWarning1)
}
})
It would be simpler to create the label with the rest of your form elements. If you're using a designer, you can just drag it on with the rest of the controls. Then set the label's Visible property to $False initially to hide it.
$ErrorWarning1 = New-Object System.Windows.Forms.Label
$ErrorWarning1.Text = "WARNING: May take 3-5 Minutes"
$ErrorWarning1.ForeColor = "Red"
$ErrorWarning1.AutoSize = $True
$ErrorWarning1.Location = new-object System.Drawing.Point(170,13)
$ErrorWarning1.Visible = $False # This line hides the label initially
$groupbox.Controls.Add($ErrorWarning1)
Now in your event handler, instead of generating the label, just show or hide it based on the state of the checkbox:
$ErrorWarning1.Visible = $Checkbox_Errors.Checked
The label will always exist, but it will only be visible when the checkbox is checked.
Hi i am trying to create a dynamic form in pwoershell , this is a form which has 5 buttons(color names) and each button opens a different text file(such as if red button is clicked,it should open red.txt; here is the full code;
Script Start
$var = "Red","Blue","Yellow","Black","White"
$testForm = New-Object System.Windows.Forms.Form
$testForm.Text = "Color List"
$testForm.AutoSize = $True
$testForm.AutoSizeMode = "GrowAndShrink"
$Font = New-Object System.Drawing.Font("Times New Roman",24, [System.Drawing.FontStyle]::Bold)
$testForm.Font = $Font
$Label = New-Object System.Windows.Forms.Label
$Label.Text = "Select the Color"
$Label.AutoSize = $True
$testForm.Controls.Add($Label)
$x=100
$y=50
foreach($color in $var)
{
$run = New-Object System.Windows.Forms.Button
$run.Location = New-Object System.Drawing.Size($x,$y)
$run.Size = New-Object System.Drawing.Size(100,50)
$run.Text = "$Color"
$run.Add_Click({ Invoke-Expression "notepad C:\Users\User\$color.txt" })
$testForm.Controls.Add($run)
$Font = New-Object System.Drawing.Font("Times New Roman",14,[System.Drawing.FontStyle]::Regular)
$run.font = $Font
$run.AutoSize = $True
$y+=50
}
$testForm.ShowDialog()
END Script
Everything went fine until, when the form opens with buttons, and all the buttons when clicked, open the file "White.txt" since its the last element in the array; is there any way to change the script to make each button open only their respective files and not the last color file?
please do let me know if any further questions or clarifications needed.
Looks like the problem is the Add_Click line. The script block contains a link to the variable $color, rather than evaluating it immediately to create a new "notepad..." string for Invoke-Expression and linking to that. The string for Invoke-Expression will be created when the button is clicked. By this time $color is White 'cos the loop has finished, so all the buttons end up creating a string using White.
You can fix it with a call to GetNewClosure() which will build cause the string for the Invoke-Expression to be created during the loop, not later on when the button is clicked. So change the line to:
$run.Add_Click({ Invoke-Expression "notepad C:\Users\User\$color.txt" }.GetNewClosure())
And it should work as expected.