I have a button on a WinForm. After clicking on the button, a function will be called which should execute Get-ADUser cmdlet.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Data
Import-Module ActiveDirectory
$ctl_frm_aduserlist = New-Object System.Windows.Forms.Form -Property #{
Size = New-Object System.Drawing.Size(500,500)
StartPosition = "CenterScreen" }
$ctl_btn_generatepreview = New-Object System.Windows.Forms.Button -Property #{
Size = New-Object System.Drawing.Size(200,30)
Location = New-Object System.Drawing.Point(10,30)
Text = "Generate Preview" }
$ctl_frm_aduserlist.Controls.Add($ctl_btn_generatepreview)
$ctl_btn_generatepreview.Add_Click({ GeneratePreview })
function GeneratePreview(){
Write-Host "GO"
Get-ADUser -Identity "user123" -Properties Name,SamAccountName | select Name,SamAccountName
Write-Host "END" }
$ctl_frm_aduserlist.ShowDialog()
Only the two "Write-Host" cmdlets will be executed by clicking on the button.
If I only execute the single line Get-ADUser in the ISE console, it works and I get the user object.
Why does Get-ADUser not work when triggered via button?
Thanks
First off, I don't have a full solution as I'm not an expert in WinForms, to feel free to correct/complete this answer.
As far as I know, the command is actually executed. You can check it by stepping though the code or Write-Host-ing bits of the object you collected with Get-ADUser and it will display the correct info:
function GeneratePreview() {
Get-ADUser -Identity "someone" | Write-Host
}
Note that if you replace Write-Host by Write-Output it does not work anymore.
The thing is, when you make a WinForms application, the standard output isn't the console anymore. I don't know where it is redirected to, but it's not visible by default. You need to either specify that you want to see your data in the console with write-host, export it to a file (Set-Content, Export-Csv, you name it) or display it in a WinForm element:
I added a new TextBox to your form (called $TextBox1 in my example), and changed GeneratePreview like this:
function GeneratePreview() {
$user = Get-ADUser "someone"
$TextBox1.Text = "$($user.name),$($user.samaccountname)"
}
Related
Below is the exact code that I am having trouble with.
A brief description:
I am trying to set up a PowerShell class that will hold objects of different types for easy access. I've done this numerous times in C#, so I thought it would be fairly straight forward. The types wanted are [System.Printing] and WMI-Objects.
Originally I had tried to write the class directly to my PowerShell profile for easy usage, but my profile fails to load when I have to class code in it. Saying that it can’t find the type name "System.Printing.PrintServer", or any other explicitly listed types.
After that failed, I moved it to its own specific module and then set my profile to import the module on open. However, even when stored in its own module, if I explicitly list a .NET type for any of the properties, the entire module fails to load. Regardless of whether I have added or imported the type / dll.
The specific problem area is this:
[string]$Name
[System.Printing.PrintServer]$Server
[System.Printing.PrintQueue]$Queue
[System.Printing.PrintTicket]$Ticket
[System.Management.ManagementObject]$Unit
[bool]$IsDefault
When I have it set to this, everything "kind of" works, but then all my properties have the _Object type, which is not helpful.
[string]$Name
$Server
$Queue
$Ticket
$Unit
$IsDefault
Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework
Class PrinterObject
{
[string]$Name
[System.Printing.PrintServer]$Server
[System.Printing.PrintQueue]$Queue
[System.Printing.PrintTicket]$Ticket
[System.Management.ManagementObject]$Unit
[bool]$IsDefault
PrinterObject([string]$Name)
{
#Add-Type -AssemblyName System.Printing
#Add-Type -AssemblyName ReachFramework
$this.Server = New-Object System.Printing.PrintServer -ArgumentList [System.Printing.PrintSystemDesiredAccess]::AdministrateServer
$this.Queue = New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() |
Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))
$this.Ticket = $this.Queue.UserPrintTicket
$this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`""
}
PrinterObject([string]$Name, [bool]$IsNetwork)
{
#Add-Type -AssemblyName System.Printing
#Add-Type -AssemblyName ReachFramework
if($IsNetwork -eq $true) {
$this.Server = New-Object System.Printing.PrintServer ("\\Server")
$this.Queue = New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() |
Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))
$this.Ticket = $this.Queue.UserPrintTicket
$this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`""
}
else {
$This.Server = New-Object System.Printing.PrintServer -argumentList [System.Printing.PrintSystemDesiredAccess]::AdministrateServer
$this.Queue = New-Object System.Printing.PrintQueue (($this.Server), ($this.Server.GetPrintQueues() |
Where-Object {$_.Name -match $Name} | Select-Object -ExpandProperty Name))
$this.Ticket = $this.Queue.UserPrintTicket
$this.Unit = Get-WmiObject -Query "SELECT * FROM Win32_Printer WHERE Name LIKE `"%$Name%`"" }
}
[void]SetPrintTicket([int]$Copies, [string]$Collation, [string]$Duplex)
{
$this.Ticket.CopyCount = $Copies
$this.Ticket.Collation = $Collation
$this.Ticket.Duplexing = $Duplex
$this.Queue.Commit()
}
[Object]GetJobs($Option)
{
if($Option -eq 1) { return $this.Queue.GetPrintJobInfoCollection() | Sort-Object -Property JobIdentifier | Select-Object -First 1}
else { return $this.Queue.GetPrintJobInfoCollection() }
}
static [Object]ShowAllPrinters()
{
Return Get-WmiObject -Class Win32_Printer | Select-Object -Property Name, SystemName
}
}
Every PowerShell script is completely parsed before the first statement in the script is executed. An unresolvable type name token inside a class definition is considered a parse error. To solve your problem, you have to load your types before the class definition is parsed, so the class definition has to be in a separate file. For example:
Main.ps1:
Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework
. $PSScriptRoot\Class.ps1
Class.ps1:
using namespace System.Management
using namespace System.Printing
Class PrinterObject
{
[string]$Name
[PrintServer]$Server
[PrintQueue]$Queue
[PrintTicket]$Ticket
[ManagementObject]$Unit
[bool]$IsDefault
}
The other possibility would be embed Class.ps1 as a string and use Invoke-Expression to execute it. This will delay parsing of class definition to time where types is available.
Add-Type -AssemblyName System.Printing
Add-Type -AssemblyName ReachFramework
Invoke-Expression #'
using namespace System.Management
using namespace System.Printing
Class PrinterObject
{
[string]$Name
[PrintServer]$Server
[PrintQueue]$Queue
[PrintTicket]$Ticket
[ManagementObject]$Unit
[bool]$IsDefault
}
'#
To complement PetSerAl's helpful answer, which explains the underlying problem and contains effective solutions, with additional background information:
To recap:
As of PowerShell 7.3.1, a PowerShell class definition can only reference .NET types that have already been loaded into the session before the script is invoked.
Because class definitions are processed at parse time of a script, rather than at runtime, Add-Type -AssemblyName calls inside a script execute too late for the referenced assemblies' types to be known to any class definitions inside the same script.
A using assembly statement should solve this problem, but currently doesn't:
using assembly should be the parse-time equivalent of an Add-Type (analogous to the relationship between using module and Import-Module), but this hasn't been implemented yet, because it requires extra work to avoid the potential for undesired execution of arbitrary code when an assembly is loaded.
Implementing a solution has been green-lighted in GitHub issue #3641, and the necessary work is being tracked as part of GitHub issue #6652 - but it is unclear when this will happen, given that the issue hasn't received attention in several years.
A better solution (than just invoking the entire class in a string) would be to just create your objects and pass them to the class as parameters. For example, this runs fine:
Add-Type -AssemblyName PresentationCore,PresentationFramework
class ExampleClass {
$object
ExampleClass ($anotherClass) {
$this.object = $anotherClass
}
[void] Show () {
$this.object::Show('Hello')
}
}
$y = [ExampleClass]::new([System.Windows.MessageBox])
$y.Show()
However, if you were to do something like this, you can expect Unable to find type [System.Windows.MessageBox].
Add-Type -AssemblyName PresentationCore,PresentationFramework
class ExampleClass2 {
$object
ExampleClass () {
$this.object = [System.Windows.MessageBox]
}
[void] Show () {
$this.object::Show('Hello')
}
}
I'm facing a problem that I cannot get solved or understand with playing music from Powershell, I made a WPF GUI on top of my Powershell script.
It all works perfect except that when I press the play music button I made the music starts but after a few seconds stops.
Or when moving the mouse over the WPF GUI the music stops and I cannot get it solved. When I throw the code for playing the music in the project it works flawless, only when I assign a button to it the problems start.
So I made a stripped down version with a simple old form and a button nothing more, made an add_Click event to connect the button the code and tested again. Same problem again music stops playing either after a few seconds or when you move your mouse over the form.
Now I still had an old Windows 7 machine hanging around with Powershell V2 still on it, and guess what it worked flawlessly! Then I upgraded Powershell v2 to V5 on that machine and I had the same problem as on Win 10 (1909 with PS 5.1) laptop, so something changed with Powershell between V2 and V2 that causes this behavior, but I cannot find what.
Some examples, when I throw these lines of code in the project it works:
Add-Type -AssemblyName presentationcore
$location = (C:\users\myuserid\test.mp3)
$PlaySound = New-Object System.Windows.Media.MediaPlayer
$PlaySound.open($location)
$PlaySound.Play()
But as soon as I assign a button to it the problem as described above appears
So stripped all down to bare bones to rule out as much as I can:
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName presentationcore
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
# Add Button
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(35,35)
$Button.Size = New-Object System.Drawing.Size(120,23)
$Button.Text = "Play music"
$Form.Controls.Add($Button)
#Add Button event
$Button.Add_Click({
$location = 'D:\test\test.mp3'
$PlaySound = New-Object System.Windows.Media.MediaPlayer
$PlaySound.open($location)
$PlaySound.Play()
})
#Show the Form
$form.ShowDialog()| Out-Null
So when resizing the form when the music plays will cause it to stop 95% of the time. But when I throw the code in for playing the music without the button like this it never breaks.
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName presentationcore
# Build Form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "My Form"
$Form.Size = New-Object System.Drawing.Size(200,200)
$Form.StartPosition = "CenterScreen"
$Form.Topmost = $True
# Add Button
$Button = New-Object System.Windows.Forms.Button
$Button.Location = New-Object System.Drawing.Size(35,35)
$Button.Size = New-Object System.Drawing.Size(120,23)
$Button.Text = "Play music"
$Form.Controls.Add($Button)
#Add Button event
$Button.Add_Click({
#Button now does nothing.. and music plays without breaking...ever
})
#Now it will always play to the end no matter what :-S
$location = 'D:\test\test.mp3'
$PlaySound = New-Object System.Windows.Media.MediaPlayer
$PlaySound.open($location)
$PlaySound.Play()
#Show the Form
$form.ShowDialog()| Out-Null
(Posted solution on behalf of the question author, to move it to the answer space).
I fixed the problem myself, the trick is to load the player at the beginning of your script like this:
#Clear the Console
CLS
#Determine Script location
$SCRIPT_PARENT = Split-Path -Parent $MyInvocation.MyCommand.Definition
#Add in the presentation core
Add-Type -AssemblyName presentationframework, presentationcore
#Load music player and set location here!
$location = ($SCRIPT_PARENT + "\Music.mp3")
$PlaySound = New-Object System.Windows.Media.MediaPlayer
###############################################################
# here comes a whole lot of code (XAML for WPF GUI etc etc) #
###############################################################
# Then in your event system only put:
#Play button action
$MainGUI.Playmusic.add_Click({
#Open file and play music
$PlaySound.open($location)
$PlaySound.Play()
})
This solved the playing problem 100%.
I have a windows form. When I click on the close (X) button of the Windows Form Control Box, I want to display a message or may be do something.
Below is the code:
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
[void] [System.Windows.Forms.Application]::EnableVisualStyles()
$frmTest = New-Object System.Windows.Forms.Form
$frmTest.Size = New-Object System.Drawing.Size(640,480)
$frmTest.MaximizeBox = $False
$frmTest.ShowDialog()
When the user clicks on the Close (X) button, I want to display a message box:
$choice = [System.Windows.Forms.MessageBox]::Show('Are you you want to exit?','TEST','YesNo','Error')
switch($choice)
{
'Yes'
{
$frmTest.Close()
}
}
I found this article: Message on Form Close, but I am not sure how to use this. Please advice. Thanks
The event to capture is Closing event of the form which has an event argument which allows you to cancel the event. To learn how to use event args in PowerShell, you may want to take a look at Windows Forms Controls Events in PowerShell - Use Sender and EventArgs.
Example
Add-Type -AssemblyName System.Windows.Forms
$form = New-Object System.Windows.Forms.Form
$form.Text ="Test"
$form.Add_Closing({param($sender,$e)
$result = [System.Windows.Forms.MessageBox]::Show(`
"Are you sure you want to exit?", `
"Close", [System.Windows.Forms.MessageBoxButtons]::YesNoCancel)
if ($result -ne [System.Windows.Forms.DialogResult]::Yes)
{
$e.Cancel= $true
}
})
$form.ShowDialog() | Out-Null
$form.Dispose()
I am trying to create in PowerShell a GUI window with dynamic content. I need to:
create a window with a random count of buttons (or other clickable items)
after a click button and related text label will be removed from the window
IMPORTANT: I cannot use a list or datagrid.
I have the following code but it still returns only last item value.
[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(400,400)
$Array = New-Object System.Collections.ArrayList
$Array.add('AAA')
$Array.add('BBB')
$Array.add('CCC')
$Array.add('DDD')
foreach ($item in $Array) {
New-Variable -Force -Name "button$membershipCount" -Value (New-Object System.Windows.Forms.Button)
$thisButton = Get-Variable -ValueOnly -Include "button$membershipCount"
$thisButton.Location = New-Object System.Drawing.Size(175,(35+26*$membershipCount))
$thisButton.Size = New-Object System.Drawing.Size(250,23)
$thisButton.Text = $item
$thisButton.Add_Click({(Write-Host $thisButton.Text | Out-Null)})
$form.Controls.Add($CHANGEButton)
}
I also tried Invoke-Expression, but it doesn't return expected results:
Invoke-Expression -Command "`$thisButton.Add_Click({`$x=`"$($item)`";`write-host $x})"
Or any better idea how can I get details which button was clicked since the number of buttons is random?
The problem is that the scriptblock inside add_Click() now contains a reference to $thisbutton, which at runtime will have been replaced with the last value in the foreach loop - this is expected behavior.
You can do 1 of 2 things here.
1. Capture the $thisButton.Text (or $item) value in a closure:
# Piping to Out-Null has zero effect here, just remove it
$thisButton.Add_Click({Write-Host $thisButton.Text}.GetNewClosure())
2. Use the event arguments to determine which button was clicked at runtime:
$thisButton.Add_Click({param($Sender,$EventArgs) Write-Host $Sender.Text})
I have an interesting issue here. I'm creating a calendar picker for use when we create accounts. It works fine and is still in progress but I have noticed that when I run the script in powershell ISE, after a few minutes it locks up (I am able to edit and save the code for a few minutes prior to that). There is nothing in the event log. I get a dialog box saying that powershell is non responsive. Memory usage seems normal as well. I do not know what is happening.
This occurs no matter how I run Powershell ISE (Run as Administrator, Run as another account, and normal ISE) I am running windows 8.1.
A coworker suggested it may be the apartment model, so I've tried STA and MTA, but the problem occurs either way. It does not happen when the same code is run from the console host.
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$objForm = New-Object Windows.Forms.Form
$objForm.Text = "Select a Date"
$objForm.Size = New-Object Drawing.Size #(490,250)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$objForm.Add_KeyDown({
if ($_.KeyCode -eq "Enter")
{
$script:dtmDate=$objCalendar.SelectionStart
$objForm.Close()
}
})
$objForm.Add_KeyDown({
if ($_.KeyCode -eq "Escape")
{
$objForm.Close()
}
})
$objCalendar = New-Object System.Windows.Forms.MonthCalendar
$objCalendar.Text = "Start"
$objCalendar.ShowTodayCircle = $False
$objCalendar.MaxSelectionCount = 1
$objForm.Controls.Add($objCalendar)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
[void] $objForm.ShowDialog()
if ($dtmDate)
{
Write-Host "Date selected: $dtmDate"
}
$objForm.Dispose()
In Response to #The Unique Paul Smith
function Find-CalenderDateTest {
[CmdletBinding()]
param(
[Parameter(
Mandatory=$false
)]
[ValidateSet('long','short','powerpoint')]
[ValidateNotNullOrEmpty()]
[string]
$DateFormat
)
Begin{
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
$objForm = New-Object Windows.Forms.Form
$objForm.Text = "Select a Date"
$objForm.Size = New-Object Drawing.Size #(243,250)
$objForm.StartPosition = "CenterScreen"
$objForm.KeyPreview = $True
$dtmDate = $null
$objForm.Add_KeyDown( {
if ($_.KeyCode -eq "Enter")
{
$dtmDate=$objCalendar.SelectionStart
$objForm.Close()
}
})
$objForm.Add_KeyDown({
if ($_.KeyCode -eq "Escape")
{
$objForm.Close()
}
})
#region OK Button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(20,175)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
# Got rid of the Click event for OK Button, and instead just assigned its DialogResult property to OK.
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$objForm.Controls.Add($OKButton)
# Setting the form's AcceptButton property causes it to automatically intercept the Enter keystroke and
# treat it as clicking the OK button (without having to write your own KeyDown events).
$objForm.AcceptButton = $OKButton
#endregion
#region Cancel Button
$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(80,175)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
# Got rid of the Click event for Cancel Button, and instead just assigned its DialogResult property to Cancel.
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$objForm.Controls.Add($CancelButton)
# Setting the form's CancelButton property causes it to automatically intercept the Escape keystroke and
# treat it as clicking the OK button (without having to write your own KeyDown events).
$objForm.CancelButton = $CancelButton
#endregion
$objCalendar = New-Object System.Windows.Forms.MonthCalendar
$objCalendar.ShowTodayCircle = $False
$objCalendar.MaxSelectionCount = 1
$objForm.Controls.Add($objCalendar)
$objForm.Topmost = $True
$objForm.Add_Shown({$objForm.Activate()})
$Results = $objForm.ShowDialog()
}
Process{}
End{
if ($Results -eq "OK")
{
$objCalendar.SelectionStart
}
$objForm.Dispose()
}
}
The error is MTA/STA
Don't use
$form.showDialog()
Use
[system.windows.forms.application]::run($form)
instead
and it works fine every time
Another way is to put it in another thread:
$code
{
//form code here
$form.showDialog()
}
$newThread = [Powershell]::Create()
$newThread.AddScript($code)
$handle = $newThread.BeginInvoke()
Provide variables from the calling script:
$newThread.Runspace.SessionStateProxy.SetVariable("variablenname",value)
before the BeginInvoke use variablenname without $...
It's a long shot but the problem might be that powershell is not closing the $objForm object correctly, leaving it running in memory while the ISE waits for input after the script has terminated. If you check your taskmanager, is the form still running in the background? You could also try adding 'Remove-Variable objForm' (no $) after the dispose() and see if that helps.
More information: https://technet.microsoft.com/en-us/library/ff730962.aspx
As I say, it's a long shot.
I was using combobox.items.add:
$configCombo.Items.Add($wks)
and I looked up how to keep the keys from printing to the console - and changed the add to:
[void]$configCombo.Items.Add($wks)
Since then I have added the void - I have been running it in ISE and it has not hung since.
Ran into this issue too. Generally occurs when I lock my workstation and return. After a bit of poking about and googleing, I found this
https://support.microsoft.com/en-us/help/943139/windows-forms-application-freezes-when-system-settings-are-changed-or, which seems like the issue at hand.
Issue
The application will not respond and the UI thread will hang in an
Invoke call while handling the OnUserPreferenceChanged notification
Cause
This occurs if a control is created on a thread which doesn't pump
messages and the UI thread receives a WM_SETTINGCHANGE message.
Resolution
Applications should never leave Control objects on threads without an
active message pump. If Controls cannot be created on the main UI
thread, they should be created on a dedicated secondary UI thread and
Disposed as soon as they are no longer needed.
I had the same issue, but the solution is: Always clean up right after the form is done.
In this case:
$objForm.Dispose()
Up to now (a few hours) I didn't have that issue again. Previously it locked up after > 10 Minutes.