Переменные в блоках Try-Catch с Powershell?

Я пытался написать сценарий в PowerShell, который переименовывает файл, если он не передается правильно через WinSCP. Скрипт выглядит примерно так:

# Variables
$error = 0
$currentFile = "test"

# Functions
function upload-files {
    param(
        $WinScpSession,
        $LocalDirectory,
        $FileType,
        $RemoteDirectory
    )
    
    get-childitem $LocalDirectory -filter $FileType |
        foreach-object {
            # $_ gets the current item of the foreach loop. 
            write-output "Sending $_..."
            $currentFile = "$($LocalDirectory)$($_)"
            upload-file -WinScpSession $session -LocalDirectory "$($LocalDirectory)$($_)" -RemoteDirectory "$RemoteDirectory"
        }
}

try
{
    # Upload files
    upload-files -WinScpSession $Session -LocalDirectory [PathToLocalDirectory] -FileType [FileType] -RemoteDirectory [PathToRemoteDirectoy]
}    
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    write-output $currentFile
    $errorMoveLocation = copy-item -path "$currentFile" -destination "$currentFile.err" -passthru
    Write-Host "Error File has been saved as $errorMoveLocation"
    $error = 1
}

Я удалил пути для удобства чтения и некоторые строки WinSCP, которые не имеют никакого отношения к проблеме.

При добавлении кода, нарушающего сценарий после функции загрузки файла, он перешел к оператору catch, где я ожидал, что переменная $currentFile будет $($LocalDirectory)$($_), поскольку она была перехвачена после повторной установки переменной. . Однако фактическое значение переменной — это исходное «тестовое» значение, с которым она была инициирована. Я попытался изменить область действия $currentFile как на скрипт, так и на глобальную, но та же проблема все еще возникает. Я все еще относительно новичок в PowerShell, и это выходит за рамки моих знаний. Любая помощь будет принята с благодарностью, спасибо!

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как написано, в вашем коде действительно существует проблема с областью видимости переменной :

  • (Немодульные) function (и файлы сценариев) запускаются в дочерней области вызывающего объекта, поэтому $currentFile = "$($LocalDirectory)$($_)" внутри вашего upload-files создается локальная $currentFile переменная, о которой вызывающий объект не знает и которая выходит за пределы области видимости при существовании функция.

Я попытался изменить область действия $currentFile на оба $script:, и $global: та же проблема все еще возникает.

Использование этих областей должно работать, учитывая, что вы явно указываете целевую область (а в области верхнего уровня вам даже не нужен спецификатор области $script:; кроме того, лучше избегать переменных $global:, поскольку они влияют на глобальный сеанс). state и задерживаться после завершения работы вашего скрипта; хотя $script: лучше, чем $global:, лучше вообще избегать изменения переменных в перекрестной области.)

Единственным объяснением того, что $script:currentFile = ... не изменяет значение в области скрипта/вызывающего объекта, может быть то, что get-childitem $LocalDirectory -filter $FileType не производит никаких выходных данных, и в этом случае блок скрипта ForEach-Object никогда не вводится.

Также обратите внимание, что, по крайней мере, в вашей функции upload-files нет кода, который генерировал бы завершающую ошибку, что является необходимым условием для использования оператора try {...} catch { ... }.


Обеспечение эффективности вашего try {...} catch { ... } заявления:

Обновлять:

  • Вы сообщаете, что вложенный вызов функции upload-file (который не показан в вопросе) может генерировать завершающую ошибку (оператор-) в результате исключения, вызванного вызовом метода (WinSCP) .NET. Такая ошибка завершения оператора перехватывается включающим оператором try { ... } catch { ... }, и управление мгновенно передается блоку catch.
    Обратите внимание, что без последнего такая ошибка завершения оператора по умолчанию не прерывает включающую функцию; дополнительную информацию см. в последней из двух ссылок внизу.
    И наоборот, если вы хотите продолжить выполнение, несмотря на использование try { ... } catch { ... }, заключите вызов метода .NET также в try {...} catch { ... } и преобразуйте исключение в непрерывную ошибку (try { ... } catch { $_ | Write-Error }) и объедините это с методом (c) ниже. (Конечно, если вы позаботились о том, чтобы вообще не возникало никаких завершающих ошибок, вы можете вообще отказаться от try { ... } catch { ... }.)

Ваша функция upload-files выдает только непрерывные ошибки, тогда как try/catch улавливает только завершающие ошибки.

  • Незавершающие ошибки встречаются гораздо чаще, чем завершающие.

Чтобы upload-files сообщал о завершающих ошибках, у вас есть два варианта:

  • (a) Перед вызовом (временно) установите привилегированную переменную $ErrorActionPreference в 'Stop', что преобразует непрерывные ошибки в завершающие.

    • Примечание. Если ваши функции были определены в модуле, этот метод не будет работать.
  • (б) Сделайте вашу функцию расширенной (подобной командлету), что обеспечит поддержку общего параметра -ErrorAction, что позволит вам передавать -ErrorAction Stop для каждого вызова для достижения того же эффекта.

    • Украсить блок param(...) функции атрибутом [CmdletBinding()] достаточно, чтобы сделать его расширенным, как и использование атрибута [Parameter()], индивидуального для параметра.

В качестве альтернативы (c) - если вы сделали свою функцию расширенной - вы можете использовать общий параметр -ErrorVariable, чтобы фиксировать все непрерывные ошибки в самостоятельно выбранной переменной (например, -ErrorVariable errs). Если вы обнаружите, что эта переменная не пуста, вы можете использовать throw для выдачи завершающей ошибки (script-), которую catch обработает (например, if ($errs) { throw $errs }

Обратите внимание, что:

  • (a) и (b) посредством обработки прерывания проектирования (и передачи управления блоку catch) после возникновения первой ошибки, тогда как

  • (c) потенциально запускает вызов функции до завершения, что потенциально допускает возникновение нескольких непрерывных ошибок.


Осуществление пункта (а):

$oldErrorActionPref = $ErrorActionPreference
try
{
    # Treat all errors as terminating
    $ErrorActionPreference = 'Stop'
    upload-files -WinScpSession $Session -LocalDirectory [PathToLocalDirectory] -FileType [FileType] -RemoteDirectory [PathToRemoteDirectoy]
}    
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    # ...
}
finally {
  $ErrorActionPreference = $oldErrorActionPref
}

Реализация пункта (b):

# ... 

function upload-files {
    [CmdletBinding()] # NOTE: This makes your function an *advanced* one.
    param(
        $WinScpSession,
        $LocalDirectory,
        $FileType,
        $RemoteDirectory
    )

    # ...
    
}

try
{
    # Pass -ErrorAction Stop to treat all errors as terminating
    upload-files -ErrorAction Stop -WinScpSession $Session -LocalDirectory [PathToLocalDirectory] -FileType [FileType] -RemoteDirectory [PathToRemoteDirectoy]
}    
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    # ...
}

Реализация пункта (с):

# ... 

function upload-files {
    [CmdletBinding()] # NOTE: This makes your function an *advanced* one.
    param(
        $WinScpSession,
        $LocalDirectory,
        $FileType,
        $RemoteDirectory
    )

    # ...
    
}

try
{
    # Pass -ErrorVariable errs to collect all non-terminating errors
    # that occur, if any.
    upload-files -ErrorVariable errs -WinScpSession $Session -LocalDirectory [PathToLocalDirectory] -FileType [FileType] -RemoteDirectory [PathToRemoteDirectoy]
    # If errors occurred, use `throw` to generate a (script-)terminating
    # error that triggers the `catch` block
    if ($errs) { throw $errs }
}    
catch
{
    Write-Host "Error: $($_.Exception.Message)"
    # ...
}

Смотрите также:

  • about_Try_Catch_Finally раздел справки.

  • Описание основных типов ошибок в контексте рекомендаций для авторов команд о том, когда выдавать завершающую, а не непрерывающую ошибку: этот ответ.

  • Полный обзор удивительно сложной обработки ошибок PowerShell: этот выпуск документации GitHub.

Спасибо за полезную информацию! Я попробую. Я должен был упомянуть об этом в сообщении OG, но строка загрузки файла запускает функцию, которая запускает только метод PutFileToDirectory() из сборки WinSCP .net. Этот метод генерирует исключение. Я намеренно оставил сценарий, чтобы продолжить работу в случае ошибки, поэтому он пропускает этот файл и переходит к следующему в каталоге. Надеюсь, это немного прояснит ситуацию. Еще раз спасибо за информацию!

Evan 26.06.2024 21:03

Рад слышать, что это помогло, @Evan, и спасибо за разъяснения (я обновил ответ). Однако обратите внимание, что при наличии заключающего try {...} catch { ... } функция, сообщающая об исключении метода .NET (что в терминах PowerShell является ошибкой завершения оператора), мгновенно прерывает выполнение и передает управление блоку catch. Другими словами: ваша функция не продолжит работу со следующим каталогом. Это происходит (по умолчанию), только если нет вложения try {...} catch { ... }.

mklement0 26.06.2024 21:29

Чтобы избежать этого, заключите вызов метода .NET в try {...} catch { ... }, преобразуйте исключение в непрерывную ошибку (... catch { $_ | Write-Error }) и объедините это с методом (c).

mklement0 26.06.2024 21:30

Другие вопросы по теме