Я пытался написать сценарий в 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, и это выходит за рамки моих знаний. Любая помощь будет принята с благодарностью, спасибо!
Как написано, в вашем коде действительно существует проблема с областью видимости переменной :
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.
Рад слышать, что это помогло, @Evan, и спасибо за разъяснения (я обновил ответ). Однако обратите внимание, что при наличии заключающего try {...} catch { ... }
функция, сообщающая об исключении метода .NET (что в терминах PowerShell является ошибкой завершения оператора), мгновенно прерывает выполнение и передает управление блоку catch
. Другими словами: ваша функция не продолжит работу со следующим каталогом. Это происходит (по умолчанию), только если нет вложения try {...} catch { ... }
.
Чтобы избежать этого, заключите вызов метода .NET в try {...} catch { ... }
, преобразуйте исключение в непрерывную ошибку (... catch { $_ | Write-Error }
) и объедините это с методом (c).
Спасибо за полезную информацию! Я попробую. Я должен был упомянуть об этом в сообщении OG, но строка загрузки файла запускает функцию, которая запускает только метод PutFileToDirectory() из сборки WinSCP .net. Этот метод генерирует исключение. Я намеренно оставил сценарий, чтобы продолжить работу в случае ошибки, поэтому он пропускает этот файл и переходит к следующему в каталоге. Надеюсь, это немного прояснит ситуацию. Еще раз спасибо за информацию!