With what method or variable can I find out the name of the parent object in this example? That is, during the event of hovering the mouse over the first button, get the name $GetParentName = "Button01", second $GetParentName = "Button02", third $GetParentName = "Button03". $GetParentName is [string].
If instead of the variable $GetParentName to apply $This, then the variable $This allows you to get the values of the object, but does not get the name of the object. But how to get the name of the object? Thanks
Edited: without using $This.Name.
$A = #{
Main = [System.Windows.Forms.Form] #{ StartPosition = 'CenterParent' }
Button01 = [System.Windows.Forms.Button] #{ Top = 0 }
Button02 = [System.Windows.Forms.Button] #{ Top = 30 }
Button03 = [System.Windows.Forms.Button] #{ Top = 60 }
}
$Script = { Write-host $GetParentName }
1..3 | % {
$A["Button0$_"].Add_MouseEnter($Script)
$A.Main.Controls.Add($A["Button0$_"])
}
[void]$A.Main.ShowDialog()
You need to set the .Name property for the button controls if you want to get their names inside the MouseEnter script:
Add-Type -AssemblyName System.Windows.Forms
$A = #{
Main = [System.Windows.Forms.Form] #{ StartPosition = 'CenterParent' }
Button01 = [System.Windows.Forms.Button] #{ Top = 0 ; Name = 'Button01'}
Button02 = [System.Windows.Forms.Button] #{ Top = 30 ; Name = 'Button02'}
Button03 = [System.Windows.Forms.Button] #{ Top = 60 ; Name = 'Button03'}
}
$Script = { Write-host $this.Name }
1..3 | ForEach-Object {
$A["Button0$_"].Add_MouseEnter($Script)
$A.Main.Controls.Add($A["Button0$_"])
}
[void]$A.Main.ShowDialog()
$A.Main.Dispose()
Edit
Creating and naming the buttons inside the ForEach-Object loop could save you typing the name for each button:
Add-Type -AssemblyName System.Windows.Forms
$A = #{
Main = [System.Windows.Forms.Form] #{ StartPosition = 'CenterParent' }
}
$Script = { Write-host $this.Name }
1..3 | ForEach-Object {
$A["Button0$_"] = [System.Windows.Forms.Button] #{ Top = ($_ -1) * 30 ; Name = "Button0$_"}
$A["Button0$_"].Add_MouseEnter($Script)
$A.Main.Controls.Add($A["Button0$_"])
}
[void]$A.Main.ShowDialog()
$A.Main.Dispose()
As Theo points out in his answer, you need to name your button objects by assigning to their .Name property.
To avoid naming your buttons twice - once as a property name in your hashtable, and again via the .Name property - you can create the buttons as an array, which simplifies the solution overall:
$A = #{
Main = [System.Windows.Forms.Form] #{ StartPosition = 'CenterParent' }
# Create an *array* of buttons
Buttons = [System.Windows.Forms.Button] #{ Top = 0; Name = 'Button1' },
[System.Windows.Forms.Button] #{ Top = 30; Name = 'Button2' },
[System.Windows.Forms.Button] #{ Top = 60; Name = 'Button3' }
}
# Print the name of the button being moused over.
$Script = { $this.Name | Out-Host }
# Attach the event-handler script block to each button.
$A.Buttons | % {
$_.Add_MouseEnter($Script)
}
# Add the buttons to the form.
$A.Main.Controls.AddRange($A.Buttons)
$null = $A.Main.ShowDialog()
If you want to avoid assigning to the .Name property, you can use the following approach:
Use PowerShell's ETS (Extended Type System) to add the key of the hashtable entry in which each button is stored as a custom property (a NoteProperty instance member), using Add-Member, which the event-handler script can query:
Add-Type -AssemblyName System.Windows.Forms
$A = #{
Main = [System.Windows.Forms.Form] #{ StartPosition = 'CenterParent' }
Button01 = [System.Windows.Forms.Button] #{ Top = 0 }
Button02 = [System.Windows.Forms.Button] #{ Top = 30 }
Button03 = [System.Windows.Forms.Button] #{ Top = 60 }
}
$Script = {
# Access the custom .CustomParent property added to each button instance below.
Write-Host $this.CustomParent
}
1..3 | % {
$key = "Button0$_"
$btn = $A[$key]
# Add a .CustomParent NoteProperty member to the button object
# storing the key of the hashtable entry in which that button is stored.
$btn | Add-Member -NotePropertyMembers #{ CustomParent = $key }
$btn.Add_MouseEnter($Script)
$A.Main.Controls.Add($btn)
}
[void]$A.Main.ShowDialog()
As for why PowerShell doesn't - and shouldn't - provide an automatic (built in) variable such as $GetParentName to support this scenario:
There are two unrelated worlds involved in this scenario:
PowerShell variables
The .NET types from the System.Windows.Forms namespace (WinForms)
WinForms is language-agnostic - any .NET language can use it; all it knows about is how instance of its types are nested at runtime and how to raise appropriate .NET events, typically in response to user events.
The source object of an event, as reported by WinForms, is surfaced as $this in a PowerShell script block acting as a .NET event delegate invoked directly by WinForms.
WinForms (rightfully) has no knowledge of what data structure or variable on the PowerShell side the event-originating object is stored in.
Even on the PowerShell side this is completely at your discretion - you could have chosen individual variables, for instance, or an array, as shown above, so there is no general pattern here that an automatic variable could support.
In the case at hand, PowerShell itself has no way of knowing that your $Script block happens to be indirectly associated with a button instance incidentally stored in a hashtable entry.
Related
I have a resource list filled with VMs.
I need to get first the tag of the corresponding NICs because the tags of the VMs were accidentally removed, the NIC tags are still there.
Then I need to reapply only the 'Monitored' tag over the corresponding VM.
I am confused with arrays.
Foreach ($Resource in $ResourceList)
{
$VM = Get-AzVM -Name $Resource
$NICs = $VM.NetworkProfile.NetworkInterfaces
Foreach($NIC in $NICs) {
$CurrentNIC = $NIC.Id
#CurrentTag should be filled with Get-AzResource instead of Az-Tag
$CurrentResource = Get-AzResource -ResourceId $CurrentNIC
$CurrentTag = $CurrentResource.Tags
#Get tag "Monitored" from $CurrentTag variable
$ResultsList += New-Object PsObject -property #{
'Monitored' = $CurrentResource.Monitored
}
#Apply Monitored tag over the current VM
Update-AzTag -ResourceId $VM.Id -Tag $ResultsList -Operation Merge
}
}
}
$ResultsList += New-Object PsObject -property #{
'Monitored' = $CurrentResource.Monitored
}
It is getting populated with :
Monitored
-
-------------------------
Instead of :
Tags:
Name Value
Monitored default
I can set form elements inside hashtable:
$Hash = #{}
$Hash.Main = New-Object System.Windows.Forms.Form
$Hash.Main.Left = 0
$Hash.Main.Top = 0
...
$Hash.Label = New-Object System.Windows.Forms.Label
$Hash.Label.Left = 0
$Hash.Label.Top = 0
...
$Hash.Panel = New-Object System.Windows.Forms.Panel
$Hash.Panel.Left = 0
$Hash.Panel.Top = 0
...
How can I write the same thing inside hashtable? I tried to make it as if it could be. And it works. But is this syntax correct?
$Hash = #{
Main = [System.Windows.Forms.Form] #{
Left = 0
Top = 0
...
}
Label = [System.Windows.Forms.Label] #{
Left = 0
Top = 0
...
}
Panel = [System.Windows.Forms.Panel] #{
Left = 0
Top = 0
...
}
}
Thank you
Yes, this syntax is correct:
Creating Objects from Hash Tables
Beginning in PowerShell 3.0, you can create an object from a hash
table of properties and property values.
The syntax is as follows:
[<class-name>]#{
<property-name>=<property-value>
<property-name>=<property-value>
…
}
This method works only for classes that have a null constructor,
that is, a constructor that has no parameters. The object properties
must be public and settable.
For more information, see about_Object_Creation.
Check the first condition (a constructor that has no parameters):
[System.Drawing.Font], ### does not have empty constructor
[System.Windows.Forms.Form],
[System.Windows.Forms.Label],
[System.Windows.Forms.Panel] |
Where-Object {
'new' -in ( $_ |
Get-Member -MemberType Methods -Static |
Select-Object -ExpandProperty Name ) -and
$_::new.OverloadDefinitions -match ([regex]::Escape('new()'))
} | Select-Object -ExpandProperty FullName
System.Windows.Forms.Form
System.Windows.Forms.Label
System.Windows.Forms.Panel
Check the latter condition (object properties must be public and settable):
[System.Windows.Forms.Form],
[System.Windows.Forms.Label],
[System.Windows.Forms.Panel] |
ForEach-Object {
#{ $_.FullName = (
$_.GetProperties('Instance,Public') | Where-Object CanWrite |
Select-Object -ExpandProperty Name | Sort-Object
)
}
}
Name Value
---- -----
System.Windows.Forms.Form {AcceptButton, AccessibleDefaultActionDescription, Acc...
System.Windows.Forms.Label {AccessibleDefaultActionDescription, AccessibleDescrip...
System.Windows.Forms.Panel {AccessibleDefaultActionDescription, AccessibleDescrip...
Getting both above code snippets together is a trivial task…
I'm trying to convert a set of SharePoint list items (and associated data) into a JSON object. To do this I'm trying to create a multi-dimensional array and then iterate over my SharePoint objects to populate it.
This is the relevant code so far:
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = #()
$listArray["DisplayTitle"] = #()
$listArray["Description"] = #()
$listArray["Setting"] = #()
$listArray["HealthAreas"] = #()
$listArray["ResourceType"] = #()
$listArray["ExternalURL"] = #()
$listArray["Active"] = #()
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray["DisplayTitle"].Add($item["Title"])
$listArray["Description"].Add($item["File Description"])
$listArray["Setting"].Add($item["Setting"])
$listArray["HealthAreas"].Add($item["Health_x0020_Area"])
$listArray["ResourceType"].Add($item["Resource_x0020_Type"])
$listArray["ExternalURL"].Add($item["External_x0020_file_x0020_path"])
$listArray["Active"].Add($item["Currently_x0020_active_x003f_"])
}
Write-Host "############################"
Write-Host $listArray | ConvertTo-Json
I know there's a gap in my thinking here (maybe I need a hashtable) but just can't see it. The error I'm receiving is:
You cannot call a method on a null-valued expression.
However I can't see where my null variable may be originating from as I've confirmed each item in the loop does contain data (by writing to console).
The error that you receive is not related to SharePoint but to PowerShell. You created the PowerShell array and tried to access its elements like it was associative array/hashtable.
Please try this code (I've tested it with my own list with different column names and it works fine):
#Lookup Source Address
$rootWeb = $Context.Web
$List = $rootWeb.lists.getByTitle($ListName)
$fields = $List.Fields;
$ListItems = $List.GetItems([Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery())
#Load the List
$Context.Load($rootWeb)
$Context.Load($List)
$Context.Load($ListItems)
$context.Load($fields)
$Context.ExecuteQuery()
$listArray = New-Object System.Collections.Generic.List[System.Object]
Write-Host "List items are"
foreach ($item in $ListItems)
{
$listArray.Add([hashtable]#{
DisplayTitle=$item["Title"];
Description= $item["File Description"];
Setting= $item["Setting"];
HealthAreas= $item["Health_x0020_Area"];
ResourceType= $item["Resource_x0020_Type"];
ExternalURL= $item["External_x0020_file_x0020_path"];
Active= $item["Currently_x0020_active_x003f_"];
}
)
}
Write-Host "############################"
$json = $listArray | ConvertTo-Json
Write-Host $json
I have an array of MailItems from Outlook. I want to search each mail item and return the Subject and a Category based on a list of search terms contained in an array - in the example called $searchArray.
For example:
$mailbox = "my.mailbox#example.com"
$outlook = New-Object -com Outlook.Application
$ns = $outlook.GetNamespace("MAPI")
$inbox = $ns.Folders.Item($mailbox).Folders.Item("Inbox")
$searchItems = $inbox.Folders.Item("MySubFolder").Folders.Item("MyNestedSubFolder").Items
$searchArray = (
"Category1", ("searchTerm1","searchTerm2","searchTerm3"),
"Category2", ("searchTerm4","searchTerm5"),
"Category3", ("searchTerm6")
)
foreach ($msg in $searchItems) {
$msg | select Subject, # <Category where email address contains one of the search terms>
}
I want to return the Subject, and then a column called Category which will look at the $msg.SenderEmailAddress and if any of the searchTerms in the $searchArray is contained within the address, return the category that had that search term in it.
For example if one of the SenderEmailAddress values was "searchTerm2#somewhere.com" then return Category1 as the Category.
I would flip that array on its head and create a hashtable from it. Then use the first matching search term as a lookup key for the category:
$searchArray = (
"Category1", ("searchTerm1","searchTerm2","searchTerm3"),
"Category2", ("searchTerm4","searchTerm5"),
"Category3", ("searchTerm6")
)
# Create hashtable
$searchTable = #{}
# Populate hash table with values from array
for($i=0;$i-lt$searchArray.Count;$i+=2){
foreach($term in $searchArray[$i+1])
{
$searchTable[$term] = $searchArray[$i]
}
}
# Select Category based on first matching search term
$msg |Select-Object Subject,#{Name="Category";Expression={
$sender = $_.SenderEmailAddress
$searchTable[$($searchTable.Keys |Where-Object{$sender -like "*$_*"} |Select -First 1)]
}
}
Still need to use a calculated expression just as Mathias did (It's really the simple way). However I wanted to show an approach where you had a custom object array for the $searchArray. If you were to tailor it from scratch it would look like this. I also converted the terms into regex pattern matches since you say they are unique. Only caveat there is you need to be sure that there are no regex meta-characters in your search terms.
$searchArray = (
[pscustomobject]#{
Category = "1"
Pattern = "searchTerm1|searchTerm2|searchTerm3"
},
[pscustomobject]#{
Category = "2"
Pattern = "searchTerm4|searchTerm5"
},
[pscustomobject]#{
Category = "3"
Pattern = "searchTerm6"}
)
foreach ($msg in $searchItems) {
$msg | select Subject, #{
Name="Category";
Expression={$searchArray | Where-Object{$msg.SenderEmailAddress -match $_.pattern } | Select-Object -ExpandProperty Category}
}
}
Solution is dependant on PowerShell 3.0 from the type accelerator [pscustomobject]. Could easily bring it back to 2.0 if need be.
To showcase similar structure using 2.0 and automatic conversion of you array to one that works with my code.
$newSearchArray = for($categoryIndex=0;$categoryIndex-lt$searchArray.Count;$categoryIndex+=2){
New-Object -TypeName pscustomobject -Property #{
Category = $searchArray[$categoryIndex]
Pattern = ($searchArray[$categoryIndex+1] | ForEach-Object{[regex]::Escape($_)}) -join "|"
}
}
Now the search terms are automatically escaped and joined into a search pattern.
Using a switch:
$mailbox = "my.mailbox#example.com"
$outlook = New-Object -com Outlook.Application
$ns = $outlook.GetNamespace("MAPI")
$inbox = $ns.Folders.Item($mailbox).Folders.Item("Inbox")
$searchItems = $inbox.Folders.Item("MySubFolder").Folders.Item("MyNestedSubFolder").Items
foreach ($msg in $searchItems) {
$object = $msg | select Subject,Category # <Category where email address contains one of the search terms>
Switch -Regex ($msg.SenderEmailAddress)
{
'searchTerm1|searchTerm2|searchTerm3' { $object.Catetory = 'Category1' ; break }
'searchTerm4|searchTerm5' { $object.Catetory = 'Category2' ; break }
'searchTerm6' { $object.Catetory = 'Category3' ; break }
}
$object
}
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++ )