SelectedNode Property Of TreeView Object Hangs The Form Multithreading - multithreading

I am working on a GUI script and I ran into a problem with my TreeView object. The problem occurs when I try to display the SelectedNode property or try to use the GetNodeAt() method of a TreeView that is displayed in another thread. I am able to display the TreeView object itself though. This is a sample of what I'm trying to do:
$form = new-object system.windows.forms.form
$treeview = new-object system.windows.forms.treeview
$treeview.name = "tree view"
$treeview.add_afterselect({write-host $this.selectednode})
$treenode = new-object system.windows.forms.treenode
$treenode.name = "tree node"
$treenode.text = "tree node"
$treenode2 = new-object system.windows.forms.treenode
$treenode2.name = "second tree node"
$treenode2.text = "second tree node"
$treeview.nodes.addrange(#($treenode,$treenode2))
$form.controls.add($treeview)
$thread = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$thread.Open()
$thread.SessionStateProxy.SetVariable("form",$form)
$thepipe = $thread.CreatePipeline({$form.Showdialog()})
$thepipe.Input.Close()
$thepipe.InvokeAsync()
The issue comes with the line $treeview.add_afterselect({write-host $this.selectednode}) When I click a node in the treeview the form will hang and I have to kill the Powershell process. The weird thing is I can display the TreeView object with no issue. Like this: $treeview.add_afterselect({write-host $this}). This will return the TreeView Object with no issues. I have also tried to use this instead $treeview.add_nodemouseclick({write-host $this.getnodeat($_.x,$_.y)}). This will also hang the form.
The problem comes from the form being displayed in another thread because when I do the same code but keep the form in the same thread I have no issues with displaying the selected node. like this:
$form = new-object system.windows.forms.form
$treeview = new-object system.windows.forms.treeview
$treeview.name = "tree view"
$treeview.add_afterselect({write-host $this.selectednode})
$treenode = new-object system.windows.forms.treenode
$treenode.name = "tree node"
$treenode.text = "tree node"
$treenode2 = new-object system.windows.forms.treenode
$treenode2.name = "second tree node"
$treenode2.text = "second tree node"
$treeview.nodes.addrange(#($treenode,$treenode2))
$form.controls.add($treeview)
$form.Showdialog()
Any help will be greatly appreciated. Thank you!

If you take a look at the TreeViewEventHandler method signature, you'll find that it takes two arguments, a sender and a TreeViewEventArgs object.
You can either "intercept" these by declaring your own named parameters, like so:
$treeview.add_AfterSelect({
param($s,$e)
# $e now refers to the TreeViewEventArgs
Write-Host $e.Node
})
Or you can rely on the automatic event variables $Sender and $EventArgs:
$treeview.add_AfterSelect({
Write-Host $EventArgs.Node
})
You may want to have a look at the help files for Register-ObjectEvent and about_Automatic_Variables
Don't use $this inside an event action - it's meant to be used as an instance reference in script method definitions, like so:
PS C:\> $obj = New-Object psobject -Property #{ Number = 3 }
PS C:\> $obj |Add-Member -Type ScriptMethod -Name GetSquare -Value { return $this.Number * $this.Number }
PS C:\> $obj.GetSquare()
9
PS C:\> $obj.Number = 4
PS C:\> $obj.GetSquare()
16

Related

How to delete 10000 items from a Custom list in sharepoint using powershell script

my sharepoint site list is having too many items and i need to delete 10000 files from it.How to do that using powershell script.
Create the query:
$list = (Get-Spweb http://devmy101).GetList("http://devmy101/Lists/smarEnteredTerritorialWaters")
$query = New-Object Microsoft.SharePoint.SPQuery;
$query.ViewAttributes = "Scope='Recursive'";
$query.RowLimit = 2000;
$query.Query = '<Where><Gt><FieldRef Name="Created"/><Value Type="DateTime" IncludeTimeValue="TRUE">2013-07-10T14:20:00Z</Value></Gt></Where>';
Build the command (note the query is limited to returning 2000 items at a time, and uses the ListItemCollectionPosition property to continue retrieving items in batches of 2000 until all the items have been queried. See this MSDN documentation for more info.)
$itemCount = 0;
$listId = $list.ID;
[System.Text.StringBuilder]$batchXml = New-Object "System.Text.StringBuilder";
$batchXml.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?><Batch>");
$command = [System.String]::Format( "<Method><SetList>{0}</SetList><SetVar Name=`"ID`">{1}</SetVar><SetVar Name=`"Cmd`">Delete</SetVar></Method>", $listId, "{0}" );
do
{
$listItems = $list.GetItems($query)
$query.ListItemCollectionPosition = $listItems.ListItemCollectionPosition
foreach ($item in $listItems)
{
if($item -ne $null){$batchXml.Append([System.String]::Format($command, $item.ID.ToString())) | Out-Null;$itemCount++;}
}
}
while ($query.ListItemCollectionPosition -ne $null)
$batchXml.Append("</Batch>");
$itemCount;
And lastly (and most importantly!), run the query
$web = Get-Spweb http://inceweb/HKMarineDB;
$web.ProcessBatchData($batchXml.ToString()) | Out-Null;
You will want to run this in SharePoint Management Shell as Admin.
This is verbatim takin from a blog post by Matthew Yarlett. I posted the majority of the post here incase his blog ever goes away. http://matthewyarlett.blogspot.com/2013/07/well-that-was-fun-bulk-deleting-items.html

Update a WPF GUI using Powershell Jobs

I've been trying to create responsive GUIs for my personal Powershell scripts. I've come up with a problem that is highly discussed online: Freezing GUI (since Powershell is single threaded).
Similar to this problem, but my case is specific to Powershell. I successfully implemented a Powershell based solution for creating GUIs relying on XAML form. Now, let's consider this code:
#EVENT Handler
$Somebutton.add_Click({
$SomeLabel.Content = "Calculating..."
Start-Job -ScriptBlock {
#Computation that takes time
#...
$SomeLabel.Content = "Calculated value"
}
})
#Show XAML GUI
$xamlGUI.ShowDialog() | out-null
xamlGUI is the form itself and $Somebutton/$SomeLabel are controls I was able to read from xaml and transform to Powershell variables.
I'm trying to understand why the Job that I start is not updating my label when the computation is done. It actually does nothing.
Im new to Powershell jobs and I'm wondering if there is something I'm missing.
Here's a little boilerplate I use for reactive WPF forms in PowerShell:
# Hide yo console
$SW_HIDE, $SW_SHOW = 0, 5
$TypeDef = '[DllImport("User32.dll")]public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);'
Add-Type -MemberDefinition $TypeDef -Namespace Win32 -Name Functions
$hWnd = (Get-Process -Id $PID).MainWindowHandle
$Null = [Win32.Functions]::ShowWindow($hWnd,$SW_HIDE)
# Define your app + form
Add-Type -AssemblyName PresentationFramework
$App = [Windows.Application]::new() # or New-Object -TypeName Windows.Application
$Form = [Windows.Markup.XamlReader]::Load(
[Xml.XmlNodeReader]::new([xml]#'
WPF form definition goes here
'#)
)
# or ::Load((New-Object -TypeName Xml.XmlNodeReader -ArgumentList ([xml]#'
#wpfdef
#'#))
#)
# Fixes the "freeze" problem
function Update-Gui {
# Basically WinForms Application.DoEvents()
$App.Dispatcher.Invoke([Windows.Threading.DispatcherPriority]::Background, [action]{})
}
# Event handlers go here
$Form.add_Closing({
$Form.Close()
$App.Shutdown()
Stop-Process -Id $PID # or return your console: [Win32.Functions]::ShowWindow($hWnd,$SW_SHOW)
})
# Finally
$App.Run($Form)
Remember to clean up when your app is shutting down:
$Form.Close()
$App.Shutdown()
Stop-Process -Id $PID
Whenever you need your changes to the GUI to be reflected, call the Update-Gui function.

Import Excel, Export CSV with PowerShell

Due to restrictions I either need to use VB or PowerShell for this task.
I have an Excel that looks like:
ColumA, ColumB,ColumC,ColumD,ColumE,ColumF
000|Txt,MoreTxt , ColumB,ColumC,ColumD,ColumE,ColumF
I read about import_csv -header, but I'm under to successfully do it. I'll post my script below. The export I expect is:
ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
000, ColumB, ColumC, ColumD, ColumE, ColumF
Only Colum gets modified, and I -only- need the digits from before that pipe. It also has to stay three digits, so 1 becomes 001, etc.
This is the script I modified based on some previous inquiries I saw, and the MS Tutorial.
$file = import-csv "C:\path\to\my\file\test.csv"
foreach ($row in $file){
$tempfile = New-Object psobject -Property #{
ColumA = $row. 'ListName'.substring(0,2)
ColumB = $row. 'ColumB'
ColumC = $row. 'ColumC'
ColumE = $row. 'ColumE'
ColumF = $row. 'ColumF'
}
$expandfile = #()
$expandfile += $tempfile | select ColumA, ColumB, ColumC, ColumD, ColumE, ColumF
}
PS gives me both errors on not liking everything I have in quotes (Which I thought was the column name, but I guess not. And also a parse error on the entire array. Essentially the entire script.
UPDATE
Providing real examples of source.
"Tiam
Name",SiamName,Siam,Ciam,Piam,Liam,Niam,Diam
"002|City, State","City, State - Some text (15092)",1,"3,408",99,"3,408",780,22.89%
"009|City, State","City, State - Some Text (E) (15450)",1,"1,894",81,"1,894",543,28.67%
Edit:
$expandfile = Import-Csv "C:\path\to\my\file\test.csv" | ForEach-Object {
$_."Tiam`r`nName" = $_."Tiam`r`nName".SubString(0,3)
$_
}

Powershell GUI, Progress Bar, and Hash Tables

Part of a script that I'm working on takes the specified username, searches active directory for computers named like the username, pings them, and then adds the online machines to an array for use later. I'd like to add a progress bar to the ping portion of the script but I'm running into problems. The form I have to present the progress bar freezes execution of the script if I invoke it prior to the ping loop.
Here is the code for the progress bar (pretty basic):
#Progress Bar
$objFormPBar = New-Object System.Windows.Forms.Form
$objFormPBar.Text = "Loading"
$objFormPBar.Size = New-Object System.Drawing.Size(200,100)
$objFormPBar.StartPosition = "CenterScreen"
$objFormPBar.Topmost = $True
$objFormPBar.Icon = $Icon
$progressBar = New-Object System.Windows.Forms.ProgressBar
$progressBar.Size = New-Object System.Drawing.Size(175,20)
$progressBar.Location = New-Object System.Drawing.Size(5, 20)
$progressBar.Minimum = 0
$progressBar.Maximum = 0
$objFormPBar.Controls.Add($progressBar)
Here is where I'm attempting to display it, this way halts execution of the script (called from inside a click handler function, thus the global variables):
$global:progressBar.Maximum = $computers.count
$global:objFormPBar.Add_Shown({$objFormPBar.Activate()})
[void] $global:objFormPBar.ShowDialog()
Foreach ($computer in $computers) {
$computer = $computer.trim()
If(Test-Connection $computer -Count 1) {
$arrayComputers.add($computer) | Out-Null
}
$global:progressBar.Increment(1)
}
$global:objFormPBar.Close()
Looking into the problem I've discovered that you can run the progress bar in a separate thread and pass variables between the two (See: Marquee Progress Bar freezes in Powershell ). However I have had no luck getting it to work or even to get another thread to launch. How can I get this progress bar and the form along with it to run in another thread?
Why don't you just use the built-in UI for reporting progress in PowerShell? It's a one line call to Write-Progress e.g.:
$computers = 1..10 | % {"server$_"}
$numComputers = $computers.Length
for ($ndx = 0; $ndx -le $numComputers; $ndx++) {
$computer = $computers[$ndx].Trim()
Write-Progress "Testing connection to $computer" -PercentComplete ($ndx/$numComputers * 100)
#if (Test-Connection $computer -Count 1) {
# $arrayComputers.add($computer) | Out-Null
#}
Start-Sleep -Seconds 1
}
BTW the primary problem with your approach above is that the ShowDialog() method call doesn't return until the form is closed. If you want to use ShowDialog(), you have to execute your logic in the context of an event handler inside the form (Load/Activate). The other option is to use Show() but that means you'll have to pump messages in your loop using Application.DoEvents() so the form will have a chance to update itself.

Powershell WinForms UI refresh / live update

I'm currently pulling my hair out trying to update a Winforms UI using the 'Register-objectevent' Cmdlet.
What I'm trying to do is get the Register-ObjectEvent to update the label in the form ever tick on the timer.
I've done hours of research on this, and I know it's something to do with multithreading / invoking, but I can't get my head around how to make it work !
If someone could show me / help me to get this script to update the label on the form by the timer, that would be amazing ! I've got lots of Winforms that would benifit from multithreading, but I need to get my head around it first !
Here's the script I'm trying to get working, any help is greatly appreciated :)
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles() | out-null
$form1 = New-Object System.Windows.Forms.Form
$OnLoadForm_StateCorrection=
{
$form1.WindowState = $InitialFormWindowState
}
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Height = 600
$System_Drawing_Size.Width = 1200
$form1.ClientSize = $System_Drawing_Size
$form1.MaximizeBox = $False
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.KeyPreview = $True
$form1.FormBorderStyle = 1
$form1.Name = "form1"
$form1.StartPosition = 1
$form1.backcolor = [System.Drawing.Color]::FromArgb(255,240,240,240)
$timer = New-Object System.Timers.Timer
$timer.Interval = 1000
$timer.AutoReset = $true
$timeout = 0
$num=0
$action = {
$num++
write-host "test"
$vollabel.text=$num
$timer.stop()
}
Register-ObjectEvent -InputObject $timer -SourceIdentifier TimerElapsed -EventName Elapsed -Action $action
$timer.start()
$vollabel = New-Object System.Windows.Forms.Label
$vollabel.Location = "0,0"
$form1.Controls.Add($vollabel)
$InitialFormWindowState = $form1.WindowState
$form1.add_Load($OnLoadForm_StateCorrection)
$form1.Add_Shown({$form1.Activate()})
$form1.ShowDialog()| Out-Null
I pared down your script a bit for a working proof of concept - add back in anything you needed:
#('System.Drawing','System.Windows.Forms') | %{ [reflection.assembly]::LoadWithPartialName($_) | Out-Null }
[System.Windows.Forms.Application]::EnableVisualStyles() | out-null
$form1 = New-Object System.Windows.Forms.Form -Property #{
MaximizeBox = $False
KeyPreview = $True
FormBorderStyle = 1
Name = "form1"
StartPosition = 1
backcolor = [System.Drawing.Color]::FromArgb(255,240,240,240)
ClientSize = New-Object System.Drawing.Size -Property #{Height = 600;Width = 1200}
}
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$timer = New-Object System.Windows.Forms.Timer -Property #{Interval = 1000} #Forms.Timer doesn't support AutoReset property
$script:num=0 #scope must be at script level to increment in event handler
$timer.start()
$timer.add_Tick({
$script:num +=1
write-host "test $script:num"
$vollabel.text=$script:num
})
$vollabel = New-Object System.Windows.Forms.Label -Property #{Location = "0,0"}
$form1.Controls.Add($vollabel)
$form1.ShowDialog()| Out-Null
$timer.stop() #This will keep running in the background unless you stop it
A few notes:
Form.ShowDialog() is blocking and stops the script execution.
System.Windows.Forms.Timer has slightly different properties than System.Timers.Timer and can take a ScriptBlock or a function name as a parameter to add_Tick()
An event handler ScriptBlock has its own scope, but you can share variables with the $ScopeName:VariableName syntax. I couldn't get $num to increment unless I set the scope to $Script

Resources