Powershell: Creating Custom DataGridView - arrays

So I'm trying to create a custom Datagridview to put into a GUI program I've made but I'm having some trouble.
So far I have this:
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Size=New-Object System.Drawing.Size(800,400)
$form.Controls.Add($dataGridView)
$dataGridView.ColumnCount = 4
$dataGridView.ColumnHeadersVisible = $true
$dataGridView.Columns[0].Name = "Process"
$dataGridView.Columns[1].Name = "ID"
$dataGridView.Columns[2].Name = "Description"
$dataGridView.Columns[3].Name = "Memory"
$row1 = get-process -property name | select Name
$rows = #($row1)
foreach ($row in $rows)
{
$dataGridView.Rows.Add($row.name)}
$form.ShowDialog()
My question is this:
How do I go about assigning different columns to differnt properties, so column 'process' would be for the procress name, column 'id' would be for the process id and so on.
so far, all I've managed to do is to assign one column a input range: Process Name.
Please help!
Thanks

Loop on all processes, and add each process properties in the order you have defined the columns:
get-process | foreach{
$dataGridView.Rows.Add($_.Name,$_.ID,$_.Description,$_.WorkingSet)
}
You could also generate the columns dynamically by selecting the properties you want to display, each property becomes a column name, and use the grid's DataSource property and an Array list to add the objects to the grid:
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$gps = get-process | select Name,ID,Description,#{n='Memory';e={$_.WorkingSet}}
$list = New-Object System.collections.ArrayList
$list.AddRange($gps)
$dataGridView = New-Object System.Windows.Forms.DataGridView -Property #{
Size=New-Object System.Drawing.Size(800,400)
ColumnHeadersVisible = $true
DataSource = $list
}
$form.Controls.Add($dataGridView)
$form.ShowDialog()

But why no to user Out-GridView from PoSH?:
get-process | select name, process, id, description, workingSet | Out-GridView

Akim - scripters try one-liners with output you can see but do little with, while programmers think about a user interface and putting control of the output in the user's hands, thus System.Windows.Forms.Form is a powershell programmer's best friend.
Shay - I have been doing some programming to use a DataGridView and my experience so far shows me I must take control of defining the DataGridView properties and NOT use .DataSource as shown in one example above. Convenient as it is, you do not then do much with your DataGridView other than show it on the form.
I started by setting .ColCount to the number of columns I wanted. Then named the columns. Thereafter I can tweak each column's properties by numbered location or name. I chose to let the user SORT on selected columns. As of 2013-03-23 I am still working on how to set the backgroundcolor of cells that I want to highlight. Should have that answer soon for those wanting to do the same. One example I found uses the value in the cell, not the location.
$datagridview = New-Object System.Windows.Forms.DataGridView
$datagridview.ColumnCount = 8
$datagridview.Columns[0].Name = "#ID"
$datagridview.Columns[1].Name = "Name"
...[snip]...
$datagridview.Columns[7].Name = "Company"
$datagridview.Columns["Name"].SortMode = "Automatic"
$datagridview.Columns[8].SortMode = "Automatic"
$datagridview.Columns[0].Width = 50
$datagridview.Columns["Description"].Width = 350
...[snip]...
foreach ($_ in $arraylist){[void]$datagridview.Rows.Add($($_.ID), $($_.Name),$($_.Path), $($_.Description), $($_.VM), $($_.WS), $($_.CPU), $($_.Company))}

I tried several ways to fiddle with cell backgroundcolors and only had success with Add_CellPainting. Heed the MSDN warning to NOT set the cellstyle in a specific location unless you truly want that location changed no matter what the user does. In the code below, row 2 column 4 is red no matter how you sort the datagridview. That could be an OOPS or did you really want that. Hmmm.... setting by value does all matching values, so if you have non-unique values then maybe you need additional logic to change only the one you want and repaint if the contents change.
$datagridview.Add_CellPainting(
{
param($Null, $EventArgs)
if ($([String]$EventArgs.Value).ToLower().Contains("ms") -eq $True) { $EventArgs.CellStyle.BackColor = "Blue" ; $EventArgs.CellStyle.ForeColor = "White" }
if ($([String]$EventArgs.Value).ToLower().Contains("windows") -eq $True) { $EventArgs.CellStyle.BackColor = "Yellow" }
if ($([String]$EventArgs.Value).ToLower().Contains("windows powershell") -eq $True) { $EventArgs.CellStyle.BackColor = "Green" }
if (($EventArgs.RowIndex -eq 2) -and ($EventArgs.ColumnIndex -eq 4)) {$EventArgs.CellStyle.BackColor = "Red" }
}
) # End of Add_CellPainting

Have since found another way to highlight the cell of my choosing:
$Script:PS_Log_Viewer_Form_row = $PS_Log_Viewer_Form_dataGridView1.Rows.Add("$($PS_Log_Viewer_total_records_ctr)", "$($PS_Log_Viewer_Form_line_date_time_sub)","$($PS_Log_Viewer_Form_line_acct)","$($PS_Log_Viewer_Form_line_msg)", "$($PS_Log_Viewer_Form_full_filename)-$($PS_Log_Viewer_file_records)")
$PS_Log_Viewer_Form_dataGridView1.Rows[$PS_Log_Viewer_Form_row].Cells[1].Style.BackColor = "BlanchedAlmond"
And searching all rows and setting (think find all):
for ($i = ($PS_Log_Viewer_Form_dataGridView1.FirstDisplayedScrollingRowIndex - 1) ; $i -gt 0 ; $i-- )
{
if ($PS_Log_Viewer_Form_dataGridView1.Rows[$i].Cells[3].Value.Contains("$($find_form_middle_flp_textbox_1.Text)") )
{
$PS_Log_Viewer_Form_dataGridView1.Rows[$i].Cells[3].Style.BackColor = $find_form_middle_flp_color_combobox_dropdownlist.SelectedItem
$PS_Log_Viewer_Form_dataGridView1.FirstDisplayedScrollingRowIndex = $i
$find_form_bottom_remarks = "Previous found at $($i)."
$i = 0
} # End of if ($PS_Log_Viewer_Form_dataGridView1.Rows[$i].Cells[3].Value.Contains("$($Script:PS_Log_Viewer_search_string)") )
} # End of for ($i = 0 ; $i -lt $PS_Log_Viewer_Form_dataGridView1.RowCount ; $i++ )

Related

Powershell populate word table from an array

I have a PS script that will import a csv into several arrays and I need it to populate a table in word. I am able to get the data into the arrays, and create a table with headers and the correct number of rows, but cannot get the data from the arrays into the table. Doing lots of google searches led me to the following code. Any help is greatly appreciated.
Sample of My_File.txt
Number of rows will vary, but the header row is always there.
component,id,iType,
VCT,AD-1234,Story,
VCT,Ad-4567,DR,
$component = #()
$id = #()
$iType =#()
$vFile = Import-CSV ("H:\My_file.txt")
$word = New-Object -ComObject "Word.Application"
$vFile | ForEach-Object {
$component += $_.components
$id += $_.id
$iType +=_.iType
}
$template = $word.Documents.Open ("H:\Test.docx")
$template = $word.Document.Add()
$word.Visible = $True
$Number_rows = ($vFile.count +1)
$Number_cols = 3
$range = $template.range()
$template.Tables.add($range, $Number_rows, $Number_cols) | out-null
$table = $template.Tables.Item(1)
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
for ($i=0; $i -lt; $vFile.count+2, $i++){
$table.cell(($i+2),1).Range.Text = $component[$i].components
$table.cell(($i+2),2).Range.Text = $id[$i].id
$table.cell(($i+2),3).Range.Text = $iType[$i].iType
}
$Table.Style = "Medium Shading 1 - Accent 1"
$template.SaveAs("H:\New_Doc.docx")
Don't separate the rows in the parsed CSV object array into three arrays, but leave the collection as-is and use the data to fill the table using the properties of that object array directly.
I took the liberty of renaming your variable $vFile into $data as to me at least this is more descriptive of what is in there.
Try
$data = Import-Csv -Path "H:\My_file.txt"
$word = New-Object -ComObject "Word.Application"
$word.Visible = $True
$template = $word.Documents.Open("H:\Test.docx")
$Number_rows = $data.Count +1 # +1 for the header
$Number_cols = 3
$range = $template.Range()
[void]$template.Tables.Add($range, $Number_rows, $Number_cols)
$table = $template.Tables.Item(1)
$table.Style = "Medium Shading 1 - Accent 1"
# write the headers
$table.cell(1,1).Range.Text = "Component"
$table.cell(1,2).Range.Text = "ID"
$table.cell(1,3).Range.text = "Type"
# next, add the data rows
for ($i=0; $i -lt $data.Count; $i++){
$table.cell(($i+2),1).Range.Text = $data[$i].component
$table.cell(($i+2),2).Range.Text = $data[$i].id
$table.cell(($i+2),3).Range.Text = $data[$i].iType
}
$template.SaveAs("H:\New_Doc.docx")
When done, do not forget to close the document, quit word and clean up the used COM objects:
$template.Close()
$word.Quit()
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($template)
$null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word)
[System.GC]::Collect()
[System.GC]::WaitForPendingFinalizers()

Default text for DropDownList

For a long time I used the usual DropDown as a ComboBoxStyle. However, I have only 2 items in the ComboBox and searching manually in this case looks unreasonable. Therefore, I decided to refer to DropDownList, since the text in it is immutable.
However, along with this I ran into a problem. In the case when no element is selected (if I understand correctly, in this case the -1 element is selected) I can not display the default text, for example, an invitation to select an element from the list. The variant with ComboBox.Text ("Please, select any value") no longer works (because the text is immutable) and here I am stumped, because I do not know what to do.
Sure, I have tried to look for something in the C# branch, but did not find anything working for powershell. Here is the option I have tried and which does not works:
$MethodComboBox.Add_TextChanged($defaultLabel)
$defaultLabel =
{
if ($ComboBox.SelectedIndex -lt 0)
{
$ComboBox.Text = "Please, select any value";
}
else
{
$ComboBox.Text = $ComboBox.SelectedText;
}
}
You can set the DrawMode of the ComboBox to OwnerDrawFixed and then handle DrawItem event and render a custom select text when index is -1:
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$combo = New-Object System.Windows.Forms.ComboBox
$combo.DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
$combo.DrawMode = [System.Windows.Forms.DrawMode]::OwnerDrawFixed
$combo.Width = 200
$combo.ItemHeight = 24
$combo.Items.Add("Male")
$combo.Items.Add("Female")
$form.Controls.Add($combo)
$combo.Add_DrawItem({param($sender,$e)
$text = "-- Select Gender --"
if ($e.Index -gt -1){
$text = $sender.GetItemText($sender.Items[$e.Index])
}
$e.DrawBackground()
[System.Windows.Forms.TextRenderer]::DrawText($e.Graphics, $text, $combo.Font, `
$e.Bounds, $e.ForeColor, [System.Windows.Forms.TextFormatFlags]::Default)
})
$form.ShowDialog() | Out-Null
$form.Dispose()

Listbox Bound to a PSObject list DisplayMember not working

I have a form with a listbox on it. In this listbox I want to put PSObjects that have these members: Name, Location, ID.
I have created a variable: $list that holds all the PSObjects and I want to bind $list to the $listbox so that anytime $list is modified the $listbox automatically updates.
I am able to bind $list to the $listbox but it doesn't display it properly. I would like only the name member to be visible. The problem is, regardless of if I set the DisplayMember property or not, the $list is still displayed like this:
_______________________________________
|#{Name=Jim; Location=Somewhere; ID=0}|
|#{Name=Sam; Location=Somewhere; ID=1}|
|_____________________________________|
I want it to look like this:
______
|Jim |
|Sam |
|____|
Here's the relevant code I'm working with:
#####PSObject Initialization Code:##########
$obj1 = New-Object -TypeName PSObject -Property #{
'Name' = 'Jim';'Location' = 'Somewhere';'ID' = 0
};$obj2 = New-Object -TypeName PSObject -Property #{
'Name' = 'Sam';'Location' = 'Somewhere';'ID' = 1
};$list = $obj1,$obj2
#####Listbox Code:##########################
$listbox = New-Object System.Windows.Forms.ListBox
$listbox.Dock = 'Fill'
$listbox.SelectionMode = 'MultiExtended'
$listbox.DisplayMember = 'Name'
$listbox.DataSource = $list
#####Form Code##############################
[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(500,500)
$form.Controls.Add($listbox); $form.ShowDialog()
Does anyone have an idea about why the DisplayMember property is seemingly overlooked when I bind the list and how to get the display into the desired format?
Note: If the PSObjects are added from the list one at a time, then it displays properly, but this gets clunky in my application.
Figured I would post this even if it's old, a bunch of folk have looked
The trick was to cast the array as an arraylist:
$listbox.DataSource = [system.collections.arraylist]$itemList

Powershell | Combining multiple Arrays and compare them to a table

I have encountered a new problem and I don't even know where exactly to start explaining. I will try my best, if something is unclear just ask me please.
I have an Excel workbook with informations (multiple rows) about DNS records - pretty similar to the powershell DNS syntax. e.g:
HostName RecordType TimeStamp TimeToLive RecordData
# A 0 00:05:00 127.0.0.1
I read them as arrays with the following little code - not very fast, but it works!:
#Read Excel
$row = [int]2
do {
if ($Sheet4.Cells.Item($Row,1).Text) {$ZoneName += $Sheet4.Cells.Item($Row,1).Text}
$HostName += $Sheet4.Cells.Item($Row,2).Text
$RecordType += $Sheet4.Cells.Item($Row,3).Text
$TimeStamp += $Sheet4.Cells.Item($Row,4).Text
$TimeToLive += $Sheet4.Cells.Item($Row,5).Text
$RecordData += $Sheet4.Cells.Item($Row,6).Text
$row = $row + [int] 1
} until (!$Sheet4.Cless.Item($row,2))
Now I have 6 arrays all stuck with information in different arrays, but all with the same amount of lines.
And now the tricky (atleast for me!) part:
I would like to stuff those 6 arrays into some special array I do not know, or in some sort of table I do not know how to create.
Why?
Because I want to compare those lines to this code ($Records to be specific):
$ZoneNames = (Get-DnsServerZone -ComputerName $DnsServer).zonename
$ZoneNames | foreach {$Records = (Get-DnsServerResourceRecord -ComputerName $DnsServer -ZoneName $_)}
$Records[0] would show me this (e.g.):
HostName RecordType Timestamp TimeToLive RecordData
-------- ---------- --------- ---------- ----------
# A 0 00:05:00 127.0.0.1
BUT: If I go deeper: $Records[0].RecordData:
IPv4Address PSComputerName
----------- --------------
127.0.0.1
So I would need to recreate this (above) sort of hierarchy to compare them (If I am right?).
I have tried it with a table like this (didn't work):
#Create Table object
$table = New-Object system.Data.DataTable “$ExcelRecords”
#Define Columns
$col2 = New-Object system.Data.DataColumn HostName,([string])
$col3 = New-Object system.Data.DataColumn RecordType,([string])
$col4 = New-Object system.Data.DataColumn TimeStamp,([string])
$col5 = New-Object system.Data.DataColumn TimeToLive,([string])
$col6 = New-Object system.Data.DataColumn RecordData,([string])
#Add the Columns
$table.columns.add($col2)
$table.columns.add($col3)
$table.columns.add($col4)
$table.columns.add($col5)
$table.columns.add($col6)
#Create a row
$r = $table.NewRow()
#Enter data in the row
$r.HostName = $HostName[$counter]
$r.RecordType = $RecordType[$counter]
$r.TimeStamp = $TimeStamp[$counter]
$r.TimeToLive = $TimeToLive[$counter]
$r.RecordData = $RecordData[$counter]
$RecordData
#Add the row to the table
$table.Rows.Add($r)
Tried comparing like this (didn't work):
if ($records[0] -like $table[0]) {write-host "works"}
This did work:
if ($records[0].hostname -like $table[0].hostname) {write-host "works"}
works
This did not (I guess this is the root of my problems):
if ($Records[0].RecordData -like $table[0].RecordData) {write-host "works"}
My main objective:
Check if there are Records on the DNS-Server, which aren't stated in the Excel sheet and delete them from the DNS-Server!
If you read through all the text, thanks for doing that! Appreciate every help.
Thanks in advance!
I'd start by creating an array of PS objects from your spreadsheet data.
#Read Excel
$row = [int]2
$DNS_Records =
do {
if ($Sheet4.Cells.Item($Row,1).Text) {
New-Object PSObject -Property #{
ZoneName = $Sheet4.Cells.Item($Row,1).Text
HostName = $Sheet4.Cells.Item($Row,2).Text
RecordType = $Sheet4.Cells.Item($Row,3).Text
TimeStamp = $Sheet4.Cells.Item($Row,4).Text
TimeToLive = $Sheet4.Cells.Item($Row,5).Text
RecordData = $Sheet4.Cells.Item($Row,6).Text
}
}
} until (!$Sheet4.Cless.Item($row,2))

Powershell: Selecting DataGridView Row

So far I have this code.
$form = New-Object System.Windows.Forms.Form
$form.Size = New-Object System.Drawing.Size(900,600)
$dataGridView = New-Object System.Windows.Forms.DataGridView
$dataGridView.Size=New-Object System.Drawing.Size(800,400)
$go = New-Object System.Windows.Forms.Button
$go.Location = New-Object System.Drawing.Size(300,450)
$go.Size = New-Object System.Drawing.Size(75,23)
$go.text = "Select"
$form.Controls.Add($go)
$form.Controls.Add($dataGridView)
$dataGridView.ColumnCount = 4
$dataGridView.ColumnHeadersVisible = $true
$dataGridView.Columns[0].Name = "Name"
$dataGridView.Columns[1].Name = "ID"
$dataGridView.Columns[2].Name = "Description"
$dataGridView.Columns[3].Name = "Memory"
$dataGridView.Columns[0].width = 240
get-process | foreach {
$dataGridView.Rows.Add($_.Name,$_.ID,$_.Description,$_.WorkingSet) | out-null
}
$go.Add_Click({
$selectedRow = $dataGridView.CurrentRowIndex
write-host $selectedRow
})
[void]$form.ShowDialog()
It simply gets the Process Name, ID, etc. properties and puts them into pre-defined headers in a DataGridView.
My problem is that I want to see the row I've clicked on via $selectedRow = $dataGridView.CurrentRowIndex and output it to the console. Instead, when the 'Select' button is pushed, a blank string is output to the terminal.
You can also get the row index with:
$dataGridView.CurrentCell.RowIndex
or
$dataGridView.SelectedRows[0].Index
You may also want to set the grid MultiSelect property to $false. Currently it allows multiple rows selection. Another thing to consider is setting the SelectionMode property to 'FullRowSelect'. When the grid is populated the first column is selected, not the whole row.
Change
$selectedRow = $dataGridView.CurrentRowIndex
to
$selectedRow = $dataGridView.CurrentRow.Index

Resources