使用Windows Forms会锁定PowerShell ISE,在脚本终止几分钟后。

19

我这里遇到了一个有趣的问题。我正在创建一个用于创建账户时使用的日历选择器。它工作得很好,但仍在进行中,但我注意到当我在PowerShell ISE中运行脚本几分钟后,它会锁定(在那之前我可以编辑和保存代码几分钟)。事件日志中没有任何信息。我收到一个对话框,说PowerShell无响应。内存使用情况也正常。我不知道发生了什么。

无论我如何运行PowerShell ISE(以管理员身份运行,以另一个帐户运行和正常ISE),这种情况都会发生。我正在运行Windows 8.1。

一位同事建议可能是单元模型的问题,因此我尝试了STA和MTA,但无论哪种方式都会出现问题。当从控制台主机运行相同的代码时,不会发生这种情况。

[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()
回应 @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()

}
}

几个问题:1. 它是在运行日历时锁定还是在关闭“选择日期”窗口后锁定?2. 它是在第一次运行时就这样做,还是在运行几次后才发生?3. 这是32位还是64位的PowerShell/ISE。提供更精确的重现步骤会很有用。 - Kev
1
这不会有任何帮助,但是当我使用公开我的对话框的表单调用用C#编写的外部类时,我遇到了完全相同的问题。当我使用启动这些类的脚本时,我的ISE总是冻结,而不是在使用期��,在ISE空闲几分钟后。好像在ISE中开始使用Windows Forms会消耗一些资源。我花了很多时间在上面。 - JPBlanc
1
@JPBlanc,你是如何解决这个问题的? - DJ Torres
1
目前我只能接受这种方式,从非 ISE PowerShell 启动这些脚本。 - JPBlanc
1
我的第一个想法是它是异步死锁。然而,看起来不正确的是$objForm.Add_KeyDown。由于这是Powershell,你应该真正研究一下Register-ObjectEvent - Eris
显示剩余8条评论
5个回答

4
错误是MTA/STA。
请不要使用


$form.showDialog()

使用
[system.windows.forms.application]::run($form)

另一种方法是将其放在另一个线程中:

而且它每次都能很好地工作。

另一种方法是将其放在另一个线程中:

$code
{
  //form code here
  $form.showDialog()
}
$newThread = [Powershell]::Create()
$newThread.AddScript($code)
$handle = $newThread.BeginInvoke() 

提供来自调用脚本的变量:

$newThread.Runspace.SessionStateProxy.SetVariable("variablenname",value)

BeginInvoke 之前使用 variablenname 而不要使用 $...


因此,对于每个人来说都应该很清楚,这不是一个问题,而是一个编程错误,因为ISE是MTA,而表单需要STA。如果您调用showDialog(),则父级是ISE(MTA)。 - Dennis
run($form) 可以正常工作,谢谢!Dennis,你认为从ISE调用showDialog()的正确方法是什么? - IODEV
[System.Windows.Forms.Application]::Run($form) 就是解决方案。干杯! - Atheek

1
我正在使用 combobox.items.add:
$configCombo.Items.Add($wks)

我查了一下如何防止密钥被打印到控制台上,并将添加操作更改为:

[void]$configCombo.Items.Add($wks)

自那时起,我添加了空值 - 我一直在ISE中运行它,自此以后它就没有再挂起过。

1
这可能只是一种猜测,但问题可能在于powershell没有正确关闭$objForm对象,在脚本终止后,ISE等待输入时仍留存在内存中。如果您检查任务管理器,表单是否仍在后台运行?您还可以尝试在dispose()之后添加'Remove-Variable objForm'(无$),看看是否有帮助。
更多信息:https://technet.microsoft.com/en-us/library/ff730962.aspx 就像我说的,这只是一个猜测。

1
  1. 当对话框打开时,窗口在任务管理器中出现,但当我们选择日期并按Enter键后它会消失。
  2. 我将尝试添加Remvove-Variable obForm来看看是否有效。
- DJ Torres
当我运行该脚本时,我注意到 $objForm 实际上从未被处理。我认为这是因为它仍然附加在管道上,所以它永远无法真正地进行垃圾回收。尝试将脚本放入一个简单的函数中,在该函数的 begin{} 部分执行所有逻辑,并在 end{} 部分进行处理。更多详细信息请参见 Powershell & Disposing - The Unique Paul Smith
@JeremyThompson 没有错误需要捕获。整个 ISE 就会锁定(不会重新绘制窗口,不接受输入,不更改鼠标光标等),而且这种情况发生在 PowerShell 脚本执行完成之后。 - briantist
@TheUniquePaulSmith 我已经编辑了我的问题,包括我的函数,让其他人测试你在最后处理表单的想法。我正在运行"do{Start-Sleep -Seconds 10;Get-Date}while($aa -eq $null)",并将在它崩溃时更新此响应。 - DJ Torres
1
@TheUniquePaulSmith,我不确定周末发生了什么事情,但新方法再次锁定了ISE。我在多台计算机上运行多个ISE实例的相同测试集:1)仅运行脚本2)作为函数3)作为带有End Process处理的函数。 - DJ Torres
显示剩余8条评论

0

我也遇到了这个问题。通常是当我锁定计算机并返回后发生的。经过一番探索和搜索,我找到了这个https://support.microsoft.com/en-us/help/943139/windows-forms-application-freezes-when-system-settings-are-changed-or,似乎就是手头的问题。

问题

应用程序无响应,UI线程在处理OnUserPreferenceChanged通知时挂起在Invoke调用中。

原因

如果在不泵送消息的线程上创建控件,并且UI线程接收到WM_SETTINGCHANGE消息,则会发生这种情况。

解决方法

应用程序永远不应该将控件对象保留在没有活动消息泵的线程上。如果控件不能在主UI线程上创建,则应该在专用的次要UI线程上创建,并在不再需要它们时立即释放。


-1

我曾经遇到过同样的问题,但解决方法是:在表单完成后立即清理。 在这种情况下:

$objForm.Dispose()

到目前为止(几个小时),我再也没有遇到过这个问题。以前它会在10分钟后锁定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接