Powershell checkbox, put checked items on top - powershell

Searched all over the web for a seemingly simple issue:
I have a working script involving a Checkedlistbox:
New-Object System.Windows.Forms.CheckedListBox
I am trying to get it to behave so that all the checked items are put on top like this:
[x]
[x]
[x]
[ ]
[ ]
Now it looks like this:
[ ]
[ ]
[X]
[ ]
[X]
Getting the checklist items to sort alphabetically is easy, but i want them to sort on "selected state"
Is this even possible?
UPDATE -> the code:
$clbGroups = New-Object System.Windows.Forms.CheckedListBox
$UserGroups = $clbGroups.CheckedItems
$formMain.Controls.Add($clbGroups)
$clbGroups.Location = '305, 258'
$clbGroups.Name = "clbGroups"
$clbGroups.Size = '400, 150'
$clbGroups.CheckedItems
$clbGroups.TabIndex = 37
$clbGroups.Sorted = "True"
Write-Verbose "Adding groups to checked list box"
$XML.Options.SecurityGroups.SecurityGroup | %{[Void]$clbGroups.Items.Add($_)}
$cboGroup_SelectedIndexChanged={
Write-Verbose "Updating groups fields with list information"
$Group = #($XML.Options.Groups.Group | ? {$_.Name -match $cboGroup.Text})
$arrayGroups = #($Group | % { $_.List } | ? { $_.Type -match "SecurityGroup" } | % { $_.'#text' } )
for ($i = 0; $i -lt $clbGroups.Items.Count; $i++) { if($arrayGroups -Contains $clbGroups.Items[$i]){ $clbGroups.SetItemChecked( $i, $true ) } else { $clbGroups.SetItemChecked( $i, $false ) } }
}

So, here is a script that will work with CheckedListBox with only CheckedItems property. As there is no builtin sort method, the trick here is to create a custom function that will do it. That is done by function SortItems below. The function will prepare new items (checked and unchecked), clear existing, and add new ones in proper order. Additionally, it must programmatically preserve the checked state of items.
# Simplified form setup
$formMain = New-Object System.Windows.Forms.Form
$clbGroups = New-Object System.Windows.Forms.CheckedListBox
$UserGroups = $clbGroups.CheckedItems
$formMain.Controls.Add($clbGroups)
1..10 | % {[void]$clbGroups.Items.Add("Group '$_'")}
# Function doing actual sort
function SortItems {
$CheckedItems = $clbGroups.CheckedItems | % {$_}
$UncheckedItems = $clbGroups.Items | where {$_ -notin $CheckedItems}
$clbGroups.Items.Clear()
$CheckedItems | % {$clbGroups.Items.Add($_)} | % {$clbGroups.SetItemChecked($_,$true)}
$UncheckedItems | % {$clbGroups.Items.Add($_)}
}
# Add button that will trigger the sort
$sortBtn = New-Object System.Windows.Forms.Button
$sortBtn.Add_Click({SortItems})
$sortBtn.Location = '15, 158'
$sortBtn.Text = 'Sort it!'
$formMain.Controls.Add($sortBtn)
# Show main form as dialog window
$formMain.ShowDialog()
And two comments about code
$CheckedItems = $clbGroups.CheckedItems | % {$_} Second pipeline
element is required to create a copy of items, not just reference. If
we use just reference, variable will point to empty array after
clearing items.
$CheckedItems | % {$clbGroups.Items.Add($_)} | %
{$clbGroups.SetItemChecked($_,$true)} Second pipeline element is
adding items. It is returning index of newly created item. We use
that index in third pipeline element to set its checked state to true

EDIT: Based on additional user input, this is not a suitable solution. I will post a better one.
I am not quite sure about your setup, but you might use something like this.
First, I simulate creating new CheckedListBox with 5 CheckBoxes
$CL = New-Object System.Windows.Forms.CheckedListBox
1..5 | % {$CB = New-Object System.Windows.Forms.CheckBox; $CB.Text = "CheckBox $_"; $CL.Items.Add($CB)}
$CL.Items | Select Text, Checked
Then, lets make third and fifth item checked
$CL.Items[2].Checked = $true
$CL.Items[4].Checked = $true
$CL.Items | Select Text, Checked
And then, lets sort it and print it to screen
$CL.Items | Sort Checked | Select Text, Checked

Related

How to confirm data changes in DataGridView GUI (PowerShell)?

In my case i want to use PS script to build WinForm with some elements including DGV contains of 3 columns (#, Page_Name, shrt). First row need to be template row with default values(1;index;NDX)so i get it from csv-file.My code:
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.location = New-Object System.Drawing.Point(20,121)
$DataGridView1.Name = "Page-List"
$DataGridView1.AllowUserToAddRowsChanged = $true
$DataGridView1.AllowUserToAddRows = $true
# $DataGridView1.DataBindings
$DataGridView1.width = 363
$DataGridView1.height = 150
$DataGridView1.ColumnCount = 3
$DataGridView1.ColumnHeadersVisible = $true
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = "Page_Name"
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.ReadOnly = $false
$DataGridView1.EditMode = "EditOnEnter"
$templateROW = #(Import-Csv -Delimiter ";" "C:\Users\vkons\OneDrive\Документы\PowerShell\Scripts\test\DGV\index.csv" -Header "#", "Page_Name", "shrt" )
$datatable = ($templateROW + $DataGridView1Rows)
$DataGridView1Data = $datatable
foreach ($Row in $DataGridView1Data){
$DataGridView1.Rows.Add($Row.'#', $Row.Page_Name, $Row.shrt)
}
If user will change Page_Name cells value in first row or will fill Page_Name cell`s in the next row (or rowS) - cells value in column "#" and column "shrt" in edited row(s) would get values programmly by this part code:
$DataGridView1.Add_CellValueChanged({autofill})
Function autofill{
$Numbr = $DataGridView1.CurrentRow.Index+1
$DataGridView1.CurrentRow.Cells[0].value = $Numbr
$Name_Page = $DataGridView1.CurrentRow.Cells[1].value
$preshrt = $Name_Page.ToString($Value) -ireplace "[aoueyi]"
$preshrt = $preshrt.ToUpper()
$shrt = $preshrt[0]+$preshrt[1]+$preshrt[2]
$DataGridView1.CurrentRow.Cells[2].value = $shrt
}
My main target is getting the values of all cells in a column Page_Name as a variable(or as array). So I tried to add next string to the function above.
$Pages = $Row.Page_Name+$DataGridView1.CurrentRow.Cells[1].value
But it returns nothing...(Either $Row.Page_Name)
I can get values of all cells in all rows by
$Page_NamesListRows = #($DataGridView1.Rows.Cells.Value)
(Unfortunately) it returns varriable, consist of all existing cells, not rows array.But when i try
$Page_Names = $DataGridView1.Rows.Cells[1].Value
or
$Page_Names = $DataGridView1.Columns[1].Cells.Value
to get only Names of the Pages, it returns error "cannot get value of a null-array" (either in case with #(...) for right part)
Could anybody answer... Is there any way to get values of all existing cells in "Page_Name" Column.Honestly it doesnt matter would the DGVData automaticly edit by changing cells value event or not.
I need to get only column "Page_Name" values.
In the end I want to apologize for my english language. It has rather poor level. And thank the moderator in advance for corrections my mistakes.
I'm afraid you will have to get the array of values by looping over the rows in the "Page_Name" column.
The last row in the DataGridView will always be the "New" row to create by the user, so you need to skip that one.
Either by doing this:
# -1 to skip the always present empty row at the bottom
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
Or something like:
$Page_Names = $DataGridView1.Rows | ForEach-Object {
$data = $_.Cells.Item("Page_Name").Value
if ($data) { $data }
}
Or:
$Page_Names = foreach ($row in $DataGridView1.Rows) {
$row.Cells.Item("Page_Name").Value
}
$Page_Names = $Page_Names[0..($Page_Names.Count - 2)]
The last alternative is costly, because it needs to recreate the entire array when removing the last item
P.S.1 Don't forget to call the Dispose() methods on both the $DataGridview1 object and the main form when done with the GUI
P.S.2 I don't see a property called AllowUserToAddRowsChanged on the DataGridView..
EDIT
To hopefully show better what I mean, here's a demo form with a DataGridView control on it.
The initial data comes from a dummy CSV file with this inside:
"1";"Page number 1";"PN1"
"2";"Page number 2";"PN2"
"3";"Page number 3";"PN3"
"4";"Page number 4";"PN4"
"5";"Page number 5";"PN5"
$form = New-Object System.Windows.Forms.Form
$form.ClientSize = New-Object Drawing.Size 580, 505
$form.text = "20/4/2020 v. 0.1 All Right reserved (c) "
$form.TopMost = $true
$DataGridView1 = New-Object system.Windows.Forms.DataGridView
$DataGridView1.Location = New-Object System.Drawing.Point 20,25
$DataGridView1.Width = 363
$DataGridView1.Height = 150
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.Name = "Page-List"
$DataGridView1.ColumnCount = 3
$DataGridView1.Columns[0].Name = '#'
$DataGridView1.Columns[0].Width = "40"
$DataGridView1.Columns[1].Name = 'Page_Name'
$DataGridView1.Columns[1].Width = "205"
$DataGridView1.Columns[2].Name = "shrt"
$DataGridView1.Columns[2].Width = "75"
$DataGridView1.AllowUserToAddRows = $true
$DataGridView1.ReadOnly = $false
# Populate the DGV with the data from the CSV
$CsvData = Import-Csv -Path 'D:\Test\TEMPLATE_ROW.csv' -Delimiter ";" -Header "#", "Page_Name", "shrt"
foreach ($row in $CsvData) {
[void]$DataGridView1.Rows.Add($row.'#', $row.Page_Name, $row.shrt)
}
# add the DGV to the form
$form.Controls.Add($DataGridView1)
# show the form and capture the result so you can check if the user cancelled or pressed OK
$result = $form.ShowDialog()
# at this point, you can read the data from the DataGridView column of interest
$Page_Names = for($row = 0; $row -lt $DataGridView1.Rows.Count - 1; $row++) {
$DataGridView1.Rows[$row].Cells.Item("Page_Name").Value
}
# cleanup memory by destroying the DGV and the from
$DataGridView1.Dispose()
$form.Dispose()
In variable $Page_Names you will now have the data from the "Page_Name" column in the DataGridView control.
# show on screen
$Page_Names

for loop through 5 textboxes

I have created a GUI with 5 Textboxes. I call them $textboxHost1 - 5.
Now I have an array in which I'm gonna save up to 5 values and then write each value according to the order into the textboxes. The first value in the array should be written into the first $textboxHost1 box.
To do that, I would like to make a for loop and have written this code
#$hostnameneingabe: Array, in which the values are saved.
$hostnameneingabeCount = $hostnameneingabe.Count
for($i = 0; $i -le $hostnameneingabeCount; $i++) {
#code here
}
Now, I'm looking for a way to go down the order, so that the first $textboxHost1 comes firstly and so on.
To be accurate, the variable $textboxHost should be incrementally increased in the loop and the values at the position $i in the array should be written into that textbox.
sth like
for($i = 0; $i -le $hostnameneingabeCount; $i++) {
$textboxHost$i =
}
I suppose you would be liking something like this?
$textboxHosts = Get-Variable | ? {$_.Name -match "textBoxHost[0-9]" -and $_.Value -ne $null} | sort Name
After this you can process that var with eg. a foreach:
foreach ($textboxHost in $textboxHosts) {<# Do some stuff #>}
You have to use an array, because otherwise you can't loop through them:
$textboxHost = #(0..4)
#Textbox 0
$textboxHost[0] = New-Object System.Windows.Forms.TextBox
$textboxHost[0].Text = "test"
#Textbox 1
$textboxHost[1] = New-Object System.Windows.Forms.TextBox
$textboxHost[1].Text = "test"
foreach ($textbox in $textboxHost){
#Do whatever you want with the textbox
$textbox =
}

PowerShell: Adding "rows" to custom object

I want to create an object in powershell that stores information about the state of a script. I can do this:
$myScriptObject =
#("status", "Selected Operation(s):", "None"),
("status", "Current Operation:", "None"),
("status", "Current Step:", "Prompting for Script Action" ),
("test", "This is just for testing", "1,2,3") `
| ForEach-Object {[pscustomobject]#{kind = $_[0]; name = $_[1]; value
= $_[2]}}
And that works:
$myScriptObject
kind name value
---- ---- -----
status Selected Operation(s): None
status Current Operation: None
status Current Step: Prompting for Script Action
test This is just for testing 1,2,3
...and I can even do this:
foreach($myObject in $myScriptObject) {
if ($myObject.kind -eq 'status') {
Write-Host $myObject.name $myObject.value
}
}
which outputs this:
Selected Operation(s): None
Current Operation: None
Current Step: Prompting for Script Action
My questions are:
1. how do I add something like the following to $myScriptObject:
-kind "ActionMenuChoice" -Name "Do This" -Value 1
-kind "ActionMenuChoice" -Name "Do That" -Value 2
How do I change items already in the object?
status Current Step: Prompting for Script Action
to
status Current Step: Prompting for Login
Or am I going about it all wrong? The idea came from the difficulty in returning numerous variables back from a function, and I read using objects is much better to pass back and forth in functions, and found using objects to be much easier to keep track of and to a certain extent manipulate.
Cheers!
If we are keeping your current object array structure, you can create $myScriptObject as an generic list type by casting [collections.generic.list[object]]. Then you can use the .Add() method to add items to your collection.
[collections.generic.list[object]]$myScriptObject =
#("status", "Selected Operation(s):", "None"),
("status", "Current Operation:", "None"),
("status", "Current Step:", "Prompting for Script Action" ),
("test", "This is just for testing", "1,2,3") |
ForEach-Object {[pscustomobject]#{kind = $_[0]; name = $_[1]; value = $_[2]}}
[void]$myScriptObject.add([pscustomobject]#{"Kind" = "ActionMenuChoice"; "Name" = "Do This"; "Value" = 1})
[void]$myScriptObject.add([pscustomobject]#{"Kind" = "ActionMenuChoice"; "Name" = "Do That"; "Value" = 2})
If you want to update an item property in that collection, you will first need to find the object/item within the collection and then access the property you want to update.
($myScriptObject | Where-Object {$_.name -eq 'Current Step:'}).value = "Prompting for Login"
Where-Object can provide the condition needed to locate the target object. Then you can use the object.property syntax to access the property. With PowerShell objects, you can do direct assignment syntax (object.property = value) to update the property value.
I would use a datatable instead:
Add-Type -AssemblyName System.Collections
$dt = New-Object system.Data.DataTable
[void]$dt.Columns.Add('kind',[string]::empty.GetType() )
[void]$dt.Columns.Add('name',[string]::empty.GetType() )
[void]$dt.Columns.Add('value',[string]::empty.GetType() )
# Add new rows:
$newRow = $dt.NewRow()
$newRow.kind = 'status'
$newRow.name = 'Selected Operation(s):'
$newRow.value = 'None'
[void]$dt.Rows.Add( $newRow )
$newRow = $dt.NewRow()
$newRow.kind = 'status'
$newRow.name = 'Selected'
$newRow.value = 'None'
[void]$dt.Rows.Add( $newRow )
# Find row(s):
$rows = $dt.Select("kind = 'status'")
"Found:"
$dt
# Change first row by condition:
$rows = $dt.Select("kind = 'status'")
$rows[0].value = 'test'
[void]$dt.AcceptChanges()
"Changed one row:"
$dt
# Change all rows:
$rows = $dt.Select("")
$rows | % { $_.value = 'new' }
[void]$dt.AcceptChanges()
"Changed all:"
$dt
# Change all rows by condition:
$rows = $dt.Select("name = 'Selected'")
$rows | % { $_.value = 'newer' }
[void]$dt.AcceptChanges()
"Changed by condition:"
$dt

Using -eq across two datasets in powershell

I'm in a juncture here. I have two datasets in powershell. Dataset 1($table) is received via an sql query (varies from 12 to 17 rows and has 8 columns) and Dataset 2($team) is hard coded in the script (has 18 rows and 2 columns). Both of these have a common column, Contest. Now the script I have to get working is - for each Contest in $table.contest, get other corresponding parameters from $table and match the Contest in $team.contest and get the corresponding $team.column2 value into play.
I'm able to get the data individually from each table, but when I use "-eq" condition across $table.contest & $team.contest, nothing happens.
This is the snippet from the code where I'm facing the problem.
$Contests = ($DataSet.Contest)
$Team = ($Team.cont)
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$newbies = $stats.Newbies
$uploads = $stats.Uploads
$views = $stats.Views
$eviews = $stats.EViews
$votes = $stats.Votes
$date = $stats.EndDate
$teamx = $team | where {$_ -eq $stats.contest}
$contest
$teamx
}
$contest shows the contest name, but $teamx is blank
The following is the changed code with respect hash tables. I tried to convert object array to string but in vain.
$team = #{
"Short Film" = "Member4";
"Student Photography" = "Member0";
"Student Art" = "Member1";
"Macro Photography" = "Member2";
"Landscape Photography" = "Member3";
}
$Contests = ($DataSet.Contest)
$Contests = $Contests | where {$_ -ne "" -and $_ -ne $null -and $_ -ne [dbnull]::value}
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$datatemp = $stats.Contest
if ($team.ContainsKey($datatemp)) {write-output "Exists"}
else {write-output "Doesn't Exist"}
$datatemp
$team.count
}
I tried directly feeding $Contest, $ContestName, and $stast.Contest inside ContainsKey, but all the time output is the same -
Doesn't Exist
Short Film
5
Doesn't Exist
Student Photography
5
Doesn't Exist
Student Art
5
Doesn't Exist
Macro Photography
5
Doesn't Exist
Landscape Photography
5
What am I doing wrong?
I can't tell for sure without knowing exactly what's in $Dataset, but your symptoms all point to trailing whitespace in the Contest value that's causing your tests to fail.
Try this and see if you get different results:
foreach($Contest in $Contests)
{
$ContestName = $Contest
$stats = $DataSet | where {$_.contest -eq $contest}
$signups = $stats.SignUps
$datatemp = $stats.Contest.trim()
if ($team.ContainsKey($datatemp)) {write-output "Exists"}
else {write-output "Doesn't Exist"}
$datatemp
$team.count
}

Powershell array of arrays loop process

I need help with loop processing an array of arrays. I have finally figured out how to do it, and I am doing it as such...
$serverList = $1Servers,$2Servers,$3Servers,$4Servers,$5Servers
$serverList | % {
% {
Write-Host $_
}
}
I can't get it to process correctly. What I'd like to do is create a CSV from each array, and title the lists accordingly. So 1Servers.csv, 2Servers.csv, etc... The thing I can not figure out is how to get the original array name into the filename. Is there a variable that holds the list object name that can be accessed within the loop? Do I need to just do a separate single loop for each list?
You can try :
$1Servers = "Mach1","Mach2"
$2Servers = "Mach3","Mach4"
$serverList = $1Servers,$2Servers
$serverList | % {$i=0}{$i+=1;$_ | % {New-Object -Property #{"Name"=$_} -TypeName PsCustomObject} |Export-Csv "c:\temp\$($i)Servers.csv" -NoTypeInformation }
I take each list, and create new objects that I export in a CSV file. The way I create the file name is not so nice, I don't take the var name I just recreate it, so if your list is not sorted it will not work.
It would perhaps be more efficient if you store your servers in a hash table :
$1Servers = #{Name="1Servers"; Computers="Mach1","Mach2"}
$2Servers = #{Name="2Servers"; Computers="Mach3","Mach4"}
$serverList = $1Servers,$2Servers
$serverList | % {$name=$_.name;$_.computers | % {New-Object -Property #{"Name"=$_} -TypeName PsCustomObject} |Export-Csv "c:\temp\$($name).csv" -NoTypeInformation }
Much like JPBlanc's answer, I kinda have to kludge the filename... (FWIW, I can't see how you can get that out of the array itself).
I did this example w/ foreach instead of foreach-object (%). Since you have actual variable names you can address w/ foreach, it seems a little cleaner, if nothing else, and hopefully a little easier to read/maintain:
$1Servers = "apple.contoso.com","orange.contoso.com"
$2Servers = "peach.contoso.com","cherry.contoso.com"
$serverList = $1Servers,$2Servers
$counter = 1
foreach ( $list in $serverList ) {
$fileName = "{0}Servers.csv" -f $counter++
"FileName: $fileName"
foreach ( $server in $list ) {
"-- ServerName: $server"
}
}
I was able to resolve this issue myself. Because I wasn't able to get the object name through, I just changed the nature of the object. So now my server lists consist of two columns, one of which is the name of the list itself.
So...
$1Servers = += [pscustomobject] #{
Servername = $entry.Servername
Domain = $entry.Domain
}
Then...
$serverList = $usaServers,$devsubServers,$wtencServers,$wtenclvServers,$pcidevServers
Then I am able to use that second column to name the lists within my foreach loop.