winform displaying in classic view outside of ISE - winforms

Not sure what is causing this issue but I have a PowerShell script that uses Windows Forms as the UI. When developing it inside of the PowerShell ISE everything looked nice with modern style buttons. When I run it from PowerShell it displays in a Windows Classic style view and crashes when I make a call to New-Object System.Windows.Forms.SaveFileDialog. Is there a fix for this that I can add to my code so my script not only looks better but actually functions outside of the ISE?
Edit:
This is my function where I call SaveFileDialog. This works inside of the ISE but when I run the script from PowerShell itself it crashes when I call this.
function exportToCSV([System.Object[]] $expArray) {
$save = New-Object System.Windows.Forms.SaveFileDialog
$save.CreatePrompt = $False
$save.SupportMultiDottedExtensions = $True
$save.DefaultExt = "csv"
$save.Filter = "CSV (*.csv) | *.csv*"
$save.Title = "Export to CSV"
if ($save.ShowDialog() -eq "OK") {
$expArray | export-csv $save.FileName
}
}

Include the following command in your script before showing the form to get modern style appearance.
[System.Windows.Forms.Application]::EnableVisualStyles()
This should however not affect the savefiledialog. I'm gonna need more to help you with that since you're saying it works in ISE, but not in normal console.
A workaround for your SaveFileDialog is adding:
$save.ShowHelp = $true
In PS3.0 everything works fine, but in PS2.0 the dialog doesn't show. The ShowHelp fixes that, but also gives you an old-style dialog. However, functionality is more imporant then appearance. :)

Related

PowerShell/WPF: Disable Button on Click

I'm writing a little toolbox at work for the common vSphere tasks. This time around, I wanted to dip into WPF.
I have created an initial connect-to-server GUI, and everything works as designed. But now I'm stuck at the following:
When clicking the Connect button (btnConnect), I want it to become greyed out/disabled, followed by the connection attempt. Once the attempt is done, it can become active again. This is to prevent people from clicking on it multiple times in a row.
This is my first attempt (I'm unable to show the complete thing, given that it's 4 files and there's some stuff in it that I'm not allowed to freely share, so I will post what's relevant):
$Window.btnConnect.Add_Click({
$Window.btnConnect.IsEnabled = $False
$Window.btnConnect.Content = 'Connecting...'
Connect-CompanyVIServer -VIServer 'ServerName' -VICredential 'PSCredentialObject'
$Window.btnConnect.Content = 'Connect'
$Window.btnConnect.IsEnabled = $True
})
Essentially, I disable the button, change it's text to Connecting..., and connect to the VMware vCenter Server. Afterwards I change the button label back and re-enable it.
When I click on the button, the form hangs while it's loading and making the connection attempt. This is to be expected, being that they're in the same thread. That's fine, since the form should do anything until the attempt is completed anyway (and learning both WPF AND runspaces at the same time would make it too complex).
The problem is that even though the $Window.btnConnect.IsEnabled = $False is at the top, the form doesn't actually update until the entire block is done processing. Until that time, I can continue clicking on the button and it'll just buffer the attempts and execute them after one-another.
So I figured I'd split them up into separate events:
$Window.btnConnect.Add_Click({ $Window.btnConnect.IsEnabled = $False })
$Window.btnConnect.Add_IsEnabledChanged({
If ($Window.btnConnect.IsEnabled -eq $False) {
$Window.btnConnect.Content = 'Connecting...'
Connect-CompanyVIServer -VIServer 'ServerName' -VICredential 'PSCredentialObject'
$Window.btnConnect.Content = 'Connect'
$Window.btnConnect.IsEnabled = $True
}
})
A valiant attempt, didn't work though. I get the exact same thing.
So now I'm out of ideas. I was considering writing a trigger into the WPF code to disable the button, but I have no idea if that works. If I do something to run the connection attempt asynchronously, I'd need to be able to query fir the outcome somehow. I'm still experimenting. In the meantime, I come here for help.
My goal is to open a new window once the connection is present and then let it close this window. The important part is that I prevent users from clicking on the button more than once.
Anyone?
As a comment above alludes to, turning the button off, doing work, and then turning it on again is on a particular thread, but not the thread that updates the GUI. It seems as though it happens instantaneously from the GUI thread perspective, i.e. off then on, nothing happens.
If you use the dispatcher to send a message to the GUI thread, i.e.
$Window.Dispatcher.invoke([action]{$Window.btnConnect.IsEnabled -eq $False})
you would see that now you have successfully disabled the button (grayed it out) after a single click. Unfortunately, that's now a permanent situation on the GUI.
To do the "turn off, work, turn on" in a different thread, using a runspace, would go something like (forgive me if not perfect):
function my_func {
param($Window,$btnConnect)
$Runspace = [runspacefactory]::CreateRunspace()
$Runspace.ApartmentState = "STA"
$Runspace.ThreadOptions = "ReuseThread"
$Runspace.Open()
$Runspace.SessionStateProxy.SetVariable("Window",$Window)
$Runspace.SessionStateProxy.SetVariable("btnConnect",$btnConnect)
#add server name/credentials with SetVariable so they can be seen in code block
$code = {
$Window.Dispatcher.invoke(
[action] {$Window.Dispatcher.btnConnect.IsEnabled = $False})
$Window.btnConnect.Content = 'Connecting...'
Connect-CompanyVIServer -VIServer 'ServerName' -VICredential 'PSCredentialObject'
$Window.btnConnect.Content = 'Connect'
#below line throws away any clicks since we've disabled button
[System.Windows.Forms.Application]::DoEvents()
$Window.Dispatcher.invoke(
[action] {$Window.Dispatcher.btnConnect.IsEnabled = $True})
}
$PSinstance = [powershell]::Create().AddScript($code)
$PSinstance.Runspace = $Runspace
$job = $PSinstance.BeginInvoke()
}
$Window.btnConnect.Add_Click({
my_func $Window $Window.btnConnect
})

PowerShell Forms Won't Trigger DragDrop

There's nothing to suggest that a simple Windows Forms DragDrop won't work in PowerShell, and several resources explaining that it does work, however I cannot get any of them working. Even something as simple as this:
Add-Type -AssemblyName System.Windows.Forms
[System.Windows.Forms.Application]::EnableVisualStyles()
$form = New-Object System.Windows.Forms.Form
$TBcode = New-Object System.Windows.Forms.TextBox
$form.Controls.Add($TBcode)
$TBcode.Dock = "Fill"
$TBcode.Multiline = $true
$TBCode.ScrollBars = "Vertical"
$TBCode.AllowDrop = $true
$TBcode.Add_DragEnter({ Write-Host "DragEnter"; $_.Effects = "Copy" })
$TBcode.Add_DragDrop({ Write-Host "DragDrop" })
$TBcode.Add_MouseEnter({ Write-Host "Mouse Enter" })
$form.ShowDialog()
The MouseEnter event triggers normally however when attempting to drag anything in to the TextBox nothing happens.
I had a sudden realisation and was able to confirm that this is in fact User Account Control.
Because I'm on Windows 10 I've got my PowerShell running as Administrator so I can actually do what I need to, however an object from the User-level Explorer process (or anything else running as a user) will simply refuse to interact with the Administrator-level PowerShell form.
Running PowerShell as a user allows the DragDrop to work as expected, but good luck if you need it to be admin! >_>

Check if form is Opened Winform / Powershell

I'm searching how I could check if a winform form is opened with Powershell, like this response for VB.net. I'm working with two runspaces, and I need to start the second, when my form is opened.
My first runspace is for the GUI. When the UI creation is completed, I opened it
$CommonHashTable.MainForm.ShowDialog()
And then, I'm trying to test if this form is opened (snipet from VB.net) from PowerShell main thread:
If Application.OpenForms().OfType(Of $CommonHashTable.MainForm).Any Then
... startsecondrunspace
A better way to test if the form is open might be to
if ($CommonHashTable.MainForm.IsHandleCreated) {
startsecondrunspace
}
Application.OpenForms() would be a method on an Application Class rather than the Form class. I am unsure if there is an instance of the Application class to even be able to use that method. If there was, I would imagine it should look something like this:
If ($ApplicationObject.OpenForms().OfType(Of $CommonHashTable.MainForm).Any) {
startsecondrunspace
}
Thanks you very much, I have created this function :
do {
RecordToLog -Message "Waiting..."
start-sleep -m 100
} until ($CommonHashTable.MainForm.IsHandleCreated)
startsecondrunspace
It's working.

PowerShell with System Window Form Objects How to display command pane or confim boxes

I have a PowerShell Script that I like to run with a Visual Interface (GUI - with Windows Form elements) Everything is working so far but I have one big problem:
Is it possible to display the command pane from PowerShell on the created Windows Form?
For Example: In one part of my PowerShell Script I am running the following command:
Upgrade-SPContentDatabase DBName
This command requires to confirm some messages with "Yes/No" that will be normally displayed in the command pane from PowerShell... Can this be done over the Windows Form so that I can hide the PowerShell-Script Window in the background?
Or is there any other way to display it in a new window that comes up?
Screenshot:
Any Ideas?
A messagebox (GUI) is different from a prompt. As long as you run a script in powershell console(not ISE), prompts will show up in the console. (There may be a setting to make them GUI like in ISE). A workaround would be to try and disable the confirm-prompts in the script, and create a messagebox yourself.
Try the following in your button's click-handler:
$handler_button1_Click=
{
$n = [System.Windows.Forms.MessageBox]::Show("Are you sure?", "Confirm", [System.Windows.Forms.MessageBoxButtons]::YesNo)
if ($n -eq "Yes") {
#Ignore confirm dialogs with -Confirm:$false
Upgrade-SPContentDatabase DBName -Confirm:$false
}
}

In PowerShell Form.Show() does not work right, but Form.ShowDialog() does

I am trying to display an image via powershell. I made a script based on this forum post.
If I use ShowDialog() it works fine, except the powershell execution stops while the dialog is up. However, that is by design for a modal dialog. If I call Form.Show() in PowershellISE the form shows up, but freezes and cannot be moved or dismissed. Behavior is similar if I copy and past the code to a powershell console.
How do I make the dialog non-modal, and not freeze.
First Answer Why it appends.
In a Windows graphic program the thread which create a Window must loop in a message pump in order to redistribute (translate) messages coming from the user action to events in his Windows.
In a modal window, the modal code that handles the window display runs its own message pump loop and doesn't return until the window is closed. That's why the code after ShowDialog() won't execute until the window is closed.
Show(), just ask to show the Window, but if there is no pump loop to manage the messages coming from user action, it just freezes.
Second a simple way to have two threads
The CmdLet start-job use another thread from the pool allocated by Powershell so it makes the dialog non-modal, and it does not freeze.
function goForm
{
[void][reflection.assembly]::LoadWithPartialName("System.Windows.Forms")
$file = (get-item 'C:\temp\jpb.png')
#$file = (get-item "c:\image.jpg")
$img = [System.Drawing.Image]::Fromfile($file);
# This tip from http://stackoverflow.com/questions/3358372/windows-forms-look-different-in-powershell-and-powershell-ise-why/3359274#3359274
[System.Windows.Forms.Application]::EnableVisualStyles();
$form = new-object Windows.Forms.Form
$form.Text = "Image Viewer"
$form.Width = $img.Size.Width;
$form.Height = $img.Size.Height;
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $img.Size.Width;
$pictureBox.Height = $img.Size.Height;
$pictureBox.Image = $img;
$form.controls.add($pictureBox)
$form.Add_Shown( { $form.Activate() } )
$form.ShowDialog()
}
Clear-Host
start-job $function:goForm
$name = Read-Host "What is you name"
Write-Host "your name is $name"
There are ways to make this work, but nothing is worth spending five hours explaining on an open forum. There are other free, shrink-wrapped ways to do this on powershell. Most notably with the free WPF powershell toolkit: Show-UI at http://showui.codeplex.com/ (previously known as WPK and/or PowerBoots - they are merged now.)
If your goal is actually to not block the interactive console when an image is shown then you still can use the script as it is with ShowDialog but you should start it using, for example, Start-Job. Thus, the dialog is still modal but it blocks execution in another runspace. The main runspace still can be used for invoking other commands.
Caveats: 1) You should close all opened dialogs before closing the interactive console. 2) If you care, you should remove completed jobs yourself (when a dialog is closed a job that started it still exists).
I use a similar approach in my custom host and it works fine. I also tested it with the script from your link. I changed it slightly so that it is called show-image.ps1 and accepts a file path as a parameter.
This command shows an image and blocks the calling runspace:
show-image.ps1 C:\TEMP\_110513_055058\test.png
This command shows an image and does not block the calling runspace:
Start-Job { show-image.ps1 $args[0] } -ArgumentList C:\TEMP\_110513_055058\test.png
Building on #JPBlanc's anwer, this would also be possible (and faster) using a runspace.
Here's a basic example (the rest would basically remain the same)
$ps = [PowerShell]::Create()
[void]$ps.AddScript({
Add-Type -AssemblyName System.Windows.Forms
$form = [Windows.Forms.Form]::new()
$form.ShowDialog()
})
[void]$ps.BeginInvoke()

Resources