как отлаживать. NET исключений фреймворка в powershell - PullRequest
0 голосов
/ 03 февраля 2020

У меня есть рабочий код, который изменит иконку в трее в зависимости от наличия процесса в блокноте.

Set-StrictMode -Version Latest

Add-Type -AssemblyName System.Windows.Forms    
Add-Type -AssemblyName System.Drawing

function Test-Notepad {
    [bool](Get-Process -Name 'notepad' -ErrorAction SilentlyContinue)
}

function menu1clickEvent ($label) {
    # Build Form object
    $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
        $Form.Controls.Add($Label)               # Add label to form
        $form.ShowDialog()| Out-Null             # Show the Form
}

function menu4clickEvent ($icon) {
    $icon.Visible = $false
    [System.Windows.Forms.Application]::Exit()
}

function create_taskbar_menu($label){
    # Create menu item
    $MenuItem1 = New-Object System.Windows.Forms.MenuItem
        $MenuItem1.Text = "Menu Item 1"

    # Create menu item
    $MenuItem2 = New-Object System.Windows.Forms.MenuItem
        $MenuItem2.Text = "Exit"

    # Add menu items to context menu
    $contextmenu = New-Object System.Windows.Forms.ContextMenu
        $Main_Tool_Icon.ContextMenu = $contextmenu
        $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem1)
        $Main_Tool_Icon.contextMenu.MenuItems.AddRange($MenuItem2)

    # Add click events
    $MenuItem1.add_Click({menu1clickEvent $label})
    $MenuItem2.add_Click({menu4clickEvent $Main_Tool_Icon})
}

# Add assemblies for WPF and Mahapps
[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')    | out-null
[System.Reflection.Assembly]::LoadWithPartialName('presentationframework')   | out-null
[System.Reflection.Assembly]::LoadWithPartialName('System.Drawing')          | out-null
[System.Reflection.Assembly]::LoadWithPartialName('WindowsFormsIntegration') | out-null

# Choose an icon to display in the systray
$onlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:/WINDOWS/system32/notepad.exe")
# use this icon when notepad is not running
$offlineIcon = [System.Drawing.Icon]::ExtractAssociatedIcon("C:/WINDOWS/system32/resmon.exe")

# create tray icon
$Main_Tool_Icon = New-Object System.Windows.Forms.NotifyIcon
$Main_Tool_Icon.Text = "Icon Text"
$Main_Tool_Icon.Icon = if (Test-Notepad) { $onlineIcon } else { $offlineIcon }
$Main_Tool_Icon.Visible = $true

# Build Label object
$Label = New-Object System.Windows.Forms.Label
    $Label.Name = "labelName"
    $Label.AutoSize = $True

# Initialize the timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
$timer.Add_Tick({
    if ($Label){
        $Label.Text, $Main_Tool_Icon.Icon = if (Test-Notepad) {
            "Notepad is running", $onlineIcon
        } else {
            "Notepad is NOT running", $offlineIcon
        }
    }
})
$timer.Start()

create_taskbar_menu $label

# ERROR:
# create_taskbar_menu2 $label $Main_Tool_Icon


# Make PowerShell Disappear - Thanks Chrissy
$windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
$asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
$null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)

# Use a Garbage colection to reduce Memory RAM
# https://dmitrysotnikov.wordpress.com/2012/02/24/freeing-up-memory-in-powershell-using-garbage-collector/
# https://docs.microsoft.com/fr-fr/dotnet/api/system.gc.collect?view=netframework-4.7.2
[System.GC]::Collect()

# Create an application context for it to all run within - Thanks Chrissy
# This helps with responsiveness, especially when clicking Exit - Thanks Chrissy
$appContext = New-Object System.Windows.Forms.ApplicationContext
try
{
    [System.Windows.Forms.Application]::Run($appContext)    
}
finally
{
    foreach ($component in $timer, $Main_Tool_Icon, $offlineIcon, $onlineIcon, $appContext)
    {
        # The following test returns $false if $component is
        # $null, which is really what we're concerned about
        if ($component -is [System.IDisposable])
        {
            $component.Dispose()
        }
    }

    Stop-Process -Id $PID
}

Моя цель состоит в том, чтобы реорганизовать create_taskbar_menu метод для использования переданных переменных, а не глобальной, вот мой код :

function create_taskbar_menu2($label, $trayIcon){
    # Create menu item
    $MenuItem1 = New-Object System.Windows.Forms.MenuItem
        $MenuItem1.Text = "Menu Item 1"

    # Create menu item
    $MenuItem2 = New-Object System.Windows.Forms.MenuItem
        $MenuItem2.Text = "Exit"

    # Add menu items to context menu
    $contextmenu = New-Object System.Windows.Forms.ContextMenu

    # Set icton properties
    $trayIcon.ContextMenu = $contextmenu
    $trayIcon.contextMenu.MenuItems.AddRange($MenuItem1)
    $trayIcon.contextMenu.MenuItems.AddRange($MenuItem2)

    # Add click events
    $MenuItem1.add_Click({menu1clickEvent $label})
    $MenuItem2.add_Click({menu4clickEvent $trayIcon})
}

Я называю этот метод как create_taskbar_menu2 $label $Main_Tool_Icon вместо create_taskbar_menu $label. Проблема в том, что после этого рефактора мое приложение вылетает, когда я нажимаю кнопку выхода на панели задач. Появляется следующее сообщение:

Unhandled exception has occurred in your application. If you click Continue, the application will ignore this error and attempt to continue. If you click Quit, the application will close immediately.

The variable '$trayIcon' cannot be retrieved because it has not been set.

Я запускаю свой код с cmd.exe как powershell -f script.ps1 И ничего не получаю в консоли. Что я делаю неправильно, и как я могу отладить такие. NET Исключения Framework?

PS: вот журнал, который я могу скопировать из диалогового окна сообщения об ошибке (он немного длиннее, поэтому я наклеил на pastebin )

1 Ответ

1 голос
/ 03 февраля 2020

Этот ответ не отвечает на ваш вопрос, но он решает вашу проблему: -) ...

В вашей функции create_taskbar_menu2 измените:

$MenuItem2.add_Click({menu4clickEvent $trayIcon})

на

$MenuItem2.add_Click({
        menu4clickEvent $trayIcon
    }.GetNewClosure()
)

Я думаю, что происходит то, что обратный вызов ScriptBlock - то есть { menu4clickEvent $trayIcon } - выполняется в отдельной области видимости для вашей функции, и нет никакой переменной с именем $trayIcon в области видимости, когда ScriptBlock выполняется.

Вызов GetNewClosure() создает новый скрипт-блок и «связывает» (из-за отсутствия лучшего слова - не уверен в точной терминологии) текущее значение параметра $trayIcon функции с $trayIcon переменная внутри области видимости ScriptBlock, поэтому теперь когда ваш обратный вызов будет выполнен, $trayIcon имеет значение, которое вы ожидаете получить.

Чтобы увидеть меньший пример этого в действии, Сравните эти два сценария:

Set-StrictMode -Version "Latest";

function Get-MyCallback1
{
    param( $x )
    return { write-host $x }
}

$callback = Get-MyCallback1 "aaa"
$callback.Invoke()

, который завершается с:

Exception calling "Invoke" with "0" argument(s): "The variable '$x' cannot be retrieved because it has not been set."
At line:1 char:1
+ $callback.Invoke()
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : RuntimeException

против

Set-StrictMode -Version "Latest";

function Get-MyCallback2
{
    param( $x )
    return { write-host $x }.GetNewClosure()
}

$callback = Get-MyCallback2 "aaa"
$callback.Invoke()

, который выводит:

aaa

As для отладки PowerShell я никогда не пробовал, но может попробовать то, что @Lex Li предложил - присоединить отладчик (например, используя Visual Studio -> Debug -> Attach to Process ...) к работающему экземпляру powershell.exe и посмотреть, запускает ли он отладку при возникновении исключения ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...