запустить новый экземпляр процесса после завершения последнего или как обновить метод $ timer.add_tick - PullRequest
2 голосов
/ 12 января 2020

Моя цель - следить за процессом блокнота. Если существующий экземпляр закрыт, следует запустить новый. У меня есть следующий код (это упрощенный фрагмент из моей большой базы кода, где я использую WinForms):

function ShowRunningPids($startedProcesses)
{
    foreach ($proc in $startedProcesses){
        Write-Host ("`$proc.Id: {0}" -f $proc.Id)
    }
}

function CheckPids($procPid, $startedProcesses)
{
    Write-Host "Already started PIDS:"
    ShowRunningPids $startedProcesses

    $proc = Get-Process -Id $procPid -ErrorAction Ignore
    # PROBLEM: Not updating this PID means that after closing first instance of notepad the new instance is always spawned.
    # Because if statement is always checking against PID that was created as first.
    if (!$proc){
        Write-Host ("Process with PID: {0} not running - starting new process" -f $procPid)
        $processStatus = Start-Process -passthru -FilePath notepad
        Write-Host ("Process with PID: {0} started" -f $processStatus.Id)
        # PROBLEM: PID of closed notepad are not deleted from $startedProcesses
        [System.Collections.ArrayList]$startedProcesses = $startedProcesses | Where-Object { $_.Id -ne $procPid }
        $startedProcesses.add($processStatus)
        Write-Host ("Removed PID {0} and added PID {}" -f $procPid, $processStatus.Id)
    }
}


[System.Collections.ArrayList]$startedProcesses = @()

$processStatus = Start-Process -passthru -FilePath notepad
$startedProcesses.add($processStatus)

# start timer
$timer = New-Object System.Windows.Forms.Timer
    $timer.Interval = 1000
    # PROBLEM: How to update PID to instance of newly created notepad?
    $timer.add_tick({CheckPids $processStatus.id $startedProcesses})
    $timer.Start()


# Form
$objForm = New-Object System.Windows.Forms.Form 
    $objForm.Text = "Auto restart of process"
    $objForm.Size = New-Object System.Drawing.Size(330,380) 
    $objForm.StartPosition = "CenterScreen"
    $objForm.Add_Shown({$objForm.Activate()})
    $objForm.Add_Closing({$timer.Stop(); })
    [void] $objForm.ShowDialog()

После закрытия существующего экземпляра блокнота новый запускается каждую секунду, пока я не остановлю скрипт. Я вижу несколько проблем здесь, которые описаны в комментариях в приведенном выше коде. Также я получаю следующее сообщение об ошибке на консоли:

Cannot convert the "System.Diagnostics.Process (notepad)" value of type "System.Diagnostics.Process" to type "System.Collections.ArrayList".
At C:\Users\wakatana\PS\ps.ps1:19 char:9
+         [System.Collections.ArrayList]$script:startedProcesses = $scr ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : MetadataError: (:) [], ArgumentTransformationMetadataException
    + FullyQualifiedErrorId : RuntimeException

Error formatting a string: Input string was not in a correct format..
At C:\Users\wakatana\ps.ps1:21 char:9
+         Write-Host ("Removed PID {0} and added PID {}" -f $procPid, $ ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (Removed PID {0} and added PID {}:String) [], RuntimeException
    + FullyQualifiedErrorId : FormatError

Как исправить этот код?

1 Ответ

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

Итак, исходя из вашего первоначального примера, здесь есть кое-что, работающее по назначению.

На самом деле у меня есть 2 примера для показа.

Моей первой мыслью было сделать это без использования таймера форм, поскольку на основе вашего примера это выглядит довольно бесполезно.

Во втором примере мы применяем аналогичные логики c, но сохранить форму. В обоих случаях произошли значительные изменения, необходимые для того, чтобы это работало.

В обоих случаях

  • Register-ObjectEvent использовалось для регистрации события таймера типа System.Timers.Timer
  • $ProcPid передается в таймер как [ref], поэтому мы можем обновить его значение (хотя вы можете обновить объект через таймер, простая структура, такая как целое число и строка, не будет обновляться, если вы пытаетесь сделать это без специальной ссылки на переменную, используя [ref]. Это потому, что исходная ссылка переопределяется при назначении нового значения

Пример # 1 - Система. Windows .timer only

Сначала я сделал этот пример, так как при просмотре вашего кода не было никакого реального использования формы, кроме ее объекта таймера. Сначала я подумал, что, возможно, вы не знали System.Timers.Timer


function ShowRunningPids($startedProcesses) {
    foreach ($proc in $startedProcesses) {
        Write-Host ("`$proc.Id: {0}" -f $proc.Id)
    }
}

function CheckPids($procPid, $startedProcesses) {
    Write-Host "Already started PIDS:"
    ShowRunningPids $startedProcesses
    $proc = Get-Process -Id $procPid -ErrorAction Ignore
    if ($proc.Handles) {
        return $procPid
    }

    $DeadProc = $startedProcesses.where( { $_.id -eq $procPid }, 'first') | Select -first 1
    [void]$startedProcesses.Remove($DeadProc)
    Write-Host ("Process with PID: {0} not running - starting new process" -f $procPid)
    $processStatus = Start-Process -passthru -FilePath notepad

    Write-Host ("Process with PID: {0} started" -f $processStatus.Id)
    # PROBLEM: PID of closed notepad are not deleted from $startedProcesses

    $startedProcesses.add($processStatus)
    Write-Host ("Removed PID {0} and added PID {1}" -f $procPid, $processStatus.Id)
    return $processStatus.Id

}

# Array list is deprecated.
$startedProcesses = [System.Collections.Generic.List[PSObject]]::new()
$ProcPid = 0

$Timer = [System.Timers.Timer]::new()
$Timer.Interval = 1000
$Timer.Elapsed
$Timer.AutoReset = $true
$Timer.Start()
$proc = (Start-Process 'notepad.exe'  -PassThru)
$startedProcesses.Add($proc)
$procPid = $proc.Id
Unregister-Event -SourceIdentifier 'ProcessTimerEvent' -errorAction SilentlyContinue
Register-ObjectEvent $Timer elapsed -SourceIdentifier 'ProcessTimerEvent' -Action {
    try {
        $Data = $event.MessageData
        $Data.ProcessID.value = CheckPids -procPid $Data.ProcessID.value -startedProcesses $Data.startedProcesses
    }
    catch {
        Write-Warning $_
    }

} -MessageData @{
    startedProcesses = $startedProcesses
    ProcessID        = [ref]$procPid
}


while ($true) {
    start-sleep -Seconds 1
}


Unregister-Event -SourceIdentifier 'ProcessTimerEvent'

Затем я снова подумал о ваших вопросах и подумал, что, возможно, эта форма в своей первоначальной форме содержала больше материала, чем просто пустой интерфейс, и вы фактически использовали Это.

Итак, я придумал второе решение, которое сохранило первоначальную форму на месте.

Эта новая версия использует 2 разных таймера.

  • Исходный таймер из моего первого примера, который выполняется как задание на неопределенное время в отдельном потоке.
  • Таймер формы, который делает get-job -name 'TimerThing' | Receive-Job данные из нашего другого Таймер возвращается в основной поток, и вы действительно видите все те записи-хосты, которые вы добавили в свои функции.

Когда вы существуете форму, таймер формы останавливается, и также останавливается задание, содержащее наблюдение за процессом.

Пример # 2 - запуск наблюдения процесса в отдельных потоках и используя вашу оригинальную форму

$InitScript = {
    function ShowRunningPids($startedProcesses) {
        foreach ($proc in $startedProcesses) {
            Write-Host ("`$proc.Id: {0}" -f $proc.Id)
        }
    }

    function CheckPids($procPid, $startedProcesses) {
        Write-Host "Already started PIDS:"
        ShowRunningPids $startedProcesses
        $proc = Get-Process -Id $procPid -ErrorAction Ignore
        if ($proc.Handles) {
            return $procPid
        }

        $DeadProc = $startedProcesses.where( { $_.id -eq $procPid }, 'first') | Select -first 1
       [void]$startedProcesses.Remove($DeadProc)
        Write-Host ("Process with PID: {0} not running - starting new process" -f $procPid)
        $processStatus = Start-Process -passthru -FilePath notepad

        Write-Host ("Process with PID: {0} started" -f $processStatus.Id)
        # PROBLEM: PID of closed notepad are not deleted from $startedProcesses

        $startedProcesses.add($processStatus)
        Write-Host ("Removed PID {0} and added PID {1}" -f $procPid, $processStatus.Id)
        return $processStatus.Id

    }
}


Start-Job -InitializationScript $InitScript  -Name 'TimerThing' -ScriptBlock {

    # Array list is deprecated.
    $startedProcesses = [System.Collections.Generic.List[PSObject]]::new()
    $ProcPid = 0

    $Timer = [System.Timers.Timer]::new()
    $Timer.Interval = 1000
    $Timer.Elapsed
    $Timer.AutoReset = $true
    $Timer.Start()
    $proc = (Start-Process 'notepad.exe'  -PassThru)
    $startedProcesses.Add($proc)
    $procPid = $proc.Id
    Unregister-Event -SourceIdentifier 'ProcessTimerEvent' -errorAction SilentlyContinue
    Register-ObjectEvent $Timer elapsed -SourceIdentifier 'ProcessTimerEvent' -Action {
        try {
            $Data = $event.MessageData
            $Data.ProcessID.value = CheckPids -procPid $Data.ProcessID.value -startedProcesses $Data.startedProcesses
        }
        catch {
            Write-Warning $_
        }

    } -MessageData @{
        startedProcesses = $startedProcesses
        ProcessID        = [ref]$procPid
    }

    while ($true) {
        Start-Sleep -Seconds 1
    }
}

# Form
add-type -AssemblyName 'System.Windows.Forms'
# start timer
$timer = New-Object System.Windows.Forms.Timer
$timer.Interval = 1000
# PROBLEM: How to update PID to instance of newly created notepad?
$timer.add_tick( { get-job -name 'TimerThing' | Receive-Job })
$timer.Start()

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Auto restart of process"
$objForm.Size = New-Object System.Drawing.Size(330, 380)
$objForm.StartPosition = "CenterScreen"
$objForm.Add_Shown( { $objForm.Activate() })
$objForm.Add_Closing( { $timer.Stop(); Stop-Job -Name 'TimerThing' })
[void] $objForm.ShowDialog()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...