Обработка ошибок команд командной строки в Powershell - PullRequest
2 голосов
/ 07 февраля 2020

Моя цель - проверить, отключить и удалить запланированные задачи на многочисленных Windows серверах с помощью Powershell. Некоторые из серверов Windows 2008R2, поэтому о Get-ScheduledTask не может быть и речи. Я должен использовать schtasks

Вот что я имею до сих пор

$servers = (Get-ADComputer -Server DomainController -Filter 'OperatingSystem -like "*Server*"').DNSHostname

$servers |
    ForEach-Object {
        if (Test-Connection -Count 1 -Quiet -ComputerName  $_) {
            Write-Output "$($_) exists, checking for Scheduled Task"
            Invoke-Command -ComputerName $_ {
                    If((schtasks /query /TN 'SOMETASK')) {
                        Write-Output "Processing removal of scheduled task`n"
                        schtasks /change /TN 'SOMETASK' /DISABLE
                        schtasks /delete /TN 'SOMETASK' /F
                    }
                    else {
                        Write-Output "Scheduled Task does not exist`n"
                    }
            }
        }
    }

Это прекрасно работает, когда SOMETASK существует, но когда это не так Таким образом, Powershell выдает ошибку, например:

ERROR: The system cannot find the file specified.
    + CategoryInfo          : NotSpecified: (ERROR: The syst...file specified.:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError
    + PSComputerName        : SERVER1

NotSpecified: (:) [], RemoteException
Scheduled Task does not exist

Я могу обойти это поведение, установив $ ErrorActionPreference в «SilentlyContinue» , но это подавляет другие ошибки Я могу быть заинтересован. Я также пытался попробовать, поймать, но это все равно генерирует ошибку. Я не думаю, что могу добавить аргумент -ErrorHandling в оператор IF. Кто-нибудь может, пожалуйста, протянуть руку помощи?

Спасибо,

Ответы [ 4 ]

2 голосов
/ 07 февраля 2020

tl; dr :

Используйте 2>$null для подавления вывода stderr при вызове внешней программы (например, schtasksk.exe)

  • Чтобы обойти ошибку, присутствующую, по крайней мере, до PowerShell [Core] 7.0 (см. Ниже), убедитесь, что $ErrorActionPreferece не , а не установлено 'Stop'.
# Execute with stderr silenced.
# Rely on the presence of stdout output in the success case only
# to make the conditional true.
if (schtasks /query /TN 'SOMETASK' 2>$null) { # success, task exists
  "Processing removal of scheduled task`n"
  # ...
}

Для справочной информации и более общих случаев использования читайте дальше.


Учитывая, как строка из потока stderr внешней программы проявляется, как показано в вашем вопросе, это звучит как вы используете свой код в PowerShell ISE , от которого я предлагаю отказаться от: PowerShell ISE устарел и следует избегать в дальнейшем (нижняя часть связанного ответа).

То, что поверхность линий stderr ISE появляется через поток ошибок PowerShell по умолчанию , особенно проблематично c - см. эту проблему GitHub .

Обычная консоль этого не делает, удача ately - он передает строки stderr через хост (консоль) и печатает их обычно (не красным цветом), что является правильным решением, учитывая, что вы обычно не можете предполагать что все выходные данные stderr представляют ошибок (несмотря на имя потока).

В случае с хорошо работающими внешними программами вы должны только когда-либо получать успех или неудачу из их процесса код выхода (как отражено в автоматической переменной c $LASTEXITCODE [1] ), а не из-за наличия вывода stderr. : код выхода 0 указывает на успех, любой ненулевой код выхода (обычно) указывает на ошибку.


Что касается заданного вами c case:

В обычной консоли значение предпочтительной переменной $ErrorActionPreference не применяется к внешним программам , таким как schtasks.exe, кроме как в форме ошибки , когда вы также используете перенаправление 2> (начиная с PowerShell [Core] 7.0) - см. этот выпуск GitHub .

Поскольку ваша schtasks /query /TN 'SOMETASK' команда работает как test , вы можете сделать следующее:

# Execute with all streams silenced (both stdout and stderr, in this case).
# schtask.exe will indicate the non-existence of the specified task
# with exit code 1
schtasks /query /TN 'SOMETASK' *>$null

if ($LASTEXITCODE -eq 0) { # success, task exists
  "Processing removal of scheduled task`n"
  # ...
}

# You can also squeeze it into a single conditional, using
# $(...), the subexpression operator.
if (0 -eq $(schtasks /query /TN 'SOMETASK' *>$null; $LASTEXITCODE)) { # success, task exists
  "Processing removal of scheduled task`n"
  # ...
}

В вашем конкретном случае c возможно более краткое решение, которое опирается на Ваша schtasks команда (a) выдает stdout выходные данные в случае успеха (если задача существует) и (b) only делает так в случае успеха:

# Execute with stderr silenced.
# Rely on the presence of stdout output in the success case only
# to make the conditional true.
if (schtasks /query /TN 'SOMETASK' 2>$null) { # success, task exists
  "Processing removal of scheduled task`n"
  # ...
}

Если schtasks.exe производит вывод stdout (который сопоставляется с потоком успешных выходных данных PowerShell, 1), неявное преобразование PowerShell в Boolean будет считать условным $true (см. Нижний раздел этот ответ для обзора правил преобразования PowerShell в Boolean).

Обратите внимание, что условие всегда действует только на выходной поток success (1), другие потоки проходят через , например, вывод stderr (2) в этом случае (как вы уже видели).

2>$null заставляет замолчать вывод stderr , перенаправляя его на нулевое устройство.

1 и 2 - номера потоков вывода / ошибок PowerShell соответственно; в случае внешних программ они ссылаются на свои потоки stdout (стандартный вывод) и stderr (стандартная ошибка) соответственно - см. about_Redirection.

Вы также можете захватить вывод stderr с перенаправлением 2> , если вы хотите сообщить об этом позже (или вам нужно проверить это специально для плохой программы, которая не использует коды выхода должным образом) ,

  • 2> stderr.txt отправляет строки stderr в файл sdterr.txt; к сожалению, в настоящее время нет способа перехватить stderr в переменной - см. это предложение для GitHub , которое предлагает синтаксис 2>&variableName для этого.

    • Как следует из вышеупомянутой ошибки, вы должны убедиться, что $ErrorActionPreference не не установлен на 'Stop', потому что 2> будет по ошибке вызвать ошибку завершения скрипта.
  • Помимо вышеупомянутой ошибки, использование 2> в настоящее время (начиная с v7.0) имеет еще один неожиданный побочный эффект: строки stderr неожиданно также добавляются в коллекцию automati c $Error, как будто это ошибки (которых они не могут считать).

    • Причина обеих проблем root заключается в том, что строки stderr неожиданно маршрутизируются через поток ошибок PowerShell , даже если нет веских причин для этого - см. этот выпуск GitHub .

[1] Обратите внимание, что автоматы c $? переменная, которая указывает успех или неудачу как логическое значение ($true / $false), также установлена, но ненадежно, поэтому : поскольку вывод stderr в настоящее время (v7.0) неожиданно маршрутизируется через поток ошибок PowerShell , если перенаправлен с 2>&, присутствие любого вывода stderr неизменно устанавливает $? в $false, даже если внешняя программа сообщает об общем успехе через $LASTEXITCODE, сообщая 0. Таким образом, единственный надежный способ проверки успеха - это $LASTEXITCODE -eq 0, а не $?.

2 голосов
/ 07 февраля 2020

Лично я предпочитаю использовать планировщик ComObject для управления запланированными задачами. С его помощью вы можете подключаться к другим серверам и искать их достаточно просто, чтобы управлять их задачами.

$Scheduler = New-Object -ComObject Schedule.Service

$servers = (Get-ADComputer -Server DomainController -Filter 'OperatingSystem -like "*Server*"').DNSHostname

$servers |
    ForEach-Object {
        if (Test-Connection -Count 1 -Quiet -ComputerName  $_) {
            Write-Output "$($_) exists, checking for Scheduled Task"
            $Scheduler.Connect($_)
            $RootFolder = $Scheduler.GetFolder("\")
            $TargetTask = $RootFolder.GetTask('SOMETASK')
            # If the task wasn't found continue to the next server
            If(!$TargetTask){
                Write-Output "Scheduled Task does not exist`n"
                Continue
            }
            Write-Output "Processing removal of scheduled task`n"
            $TargetTask.Enabled = $false
            $RootFolder.DeleteTask('SOMETASK')
        }
    }
1 голос
/ 07 февраля 2020

То есть вы просто хотите скрыть сообщение об ошибке от schtasks? Одним из способов является перенаправление стандартной ошибки или «2» на $ null. Это пример, который любой может запустить от имени администратора. Оператор if работает только потому, что при ошибке нет выходных данных для стандарта. Похоже, что invoke-command генерирует удаленное исключение, когда что-то приходит из-за стандартной ошибки, но не останавливает следующие команды. Я не вижу способа попробовать / поймать это.

invoke-command localhost { if (schtasks /query /tn 'foo' 2>$null) {
  'yes' } ; 'hi'}

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

Похоже, что вы слишком усложняете выполнение этого усилия.

Зачем отключать и удалять, а просто удалять, поскольку это кажется немного избыточным?

Все запланированные задачи - ничто но xml файлов и записей reg, которые вы можете просто удалить, если не хотите больше выполнять задачу. Таким образом, вы можете использовать sue Get-ChildItem.

# File system:
(Get-ChildItem -Path "$env:windir\System32\Tasks").FullName

# Results
<#
...
C:\Windows\System32\Tasks\Microsoft
...
C:\Windows\System32\Tasks\MicrosoftEdgeUpdateTaskMachineCore
...
#>

# Registry:
Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tasks'
# Results
<#
Name                           Property                                                                                                                             
----                           --------                                                                                                                             
{01C5B377-A7EB-4FF3-9C6C-86852 Path               : \Microsoft\Windows\Management\Provisioning\Logon                                                                
...                                                                                                       
#>

Get-ChildItem -Path 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\Taskcache\Tree'
# Results
<#
Name                           Property                                                                                                                             
----                           --------                                                                                                                             
Adobe Acrobat Update Task      SD    : {1...
#>

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

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