Я пытаюсь вызвать блок сценария в Powershell из значения PSD1, импортированного через Import-PowerShellDataFile.
Давайте возьмем простейшее выражение (без файла данных) выполнения блока сценария в хеш-таблице. Это работает так, как ожидалось.
$Config = @{
test = {Write-output "Hello"}
}
$config.test.invoke()
#output: Hello
Теперь, если я добавлю следующий промежуточный шаг, он не удастся
$tmpConfig = Import-PowerShellDataFile -Path tmpconfig.psd1
$tmpconfig.test.Invoke()
#Output: Write-Output "Hello"
Я не могу найти объяснение разнице в поведении.
Есть идеи, что там происходит и как это исправить?
Я заметил, что вывод, импортированный из файла PowershellData .ToString(), заключен в дополнительную пару фигурных скобок, что по сути будет $t = {{Write-Output 'Hello'}}, и хотя мне нужно будет по существу вызвать дважды, я не понимаю контекст того, почему PowershellDataFile дважды оберните (а может быть, это что-то еще) блок сценария вот так.
Для справки, мой результат и самый простой сценарий для просмотра этого необычного сценария.
# Imported from PowershellData
ConfigType : System.Collections.Hashtable
Value : {Write-output "Hello"}
ValueType : System.Management.Automation.ScriptBlock
Result : Write-output "Hello"
ResultType : System.Management.Automation.ScriptBlock
# Not imported.
ConfigType : System.Collections.Hashtable
Value : Write-output "Hello"
ValueType : System.Management.Automation.ScriptBlock
Result : Hello
ResultType : System.String
$tmpconfigstring = @'
@{
test = { Write-output "Hello" }
}
'@
$tmpconfigstring | Out-File tmpconfig.psd1
$tmpConfig = Import-PowerShellDataFile -Path tmpconfig.psd1
$tmpconfig2 = ([scriptblock]::Create($tmpconfigstring)).InvokeReturnAsIs()
$Result = $tmpConfig.test.InvokeReturnAsIs()
$Result2 = $tmpconfig2.test.InvokeReturnAsIs()
[PSCustomObject]@{
'ConfigType' = $tmpConfig.GetType()
'Value' = $tmpConfig.test.ToString()
'ValueType' = $tmpconfig.test.GetType()
'Result' = $Result
'ResultType' = $Result.GetType()
} | fl
[PSCustomObject]@{
'ConfigType' = $tmpConfig2.GetType()
'Value' = $tmpConfig2.test.ToString()
'ValueType' = $tmpconfig2.test.GetType()
'Result' = $Result2
'ResultType' = $Result2.GetType()
} | fl





Возможно, они идут по этому пути из соображений безопасности, как указано в документации:
Командлет
Import-PowerShellDataFileбезопасно импортирует пары ключ-значение из хеш-таблиц, определенных в файле.PSD1. Значения можно импортировать, используяInvoke-Expressionв содержимом файла. ОднакоInvoke-Expressionзапускает любой код, содержащийся в файле.
Также возможно, что это была просто ошибка. В SafeValues.cs#L793-L796 они создают новый блок скрипта из экстента:
public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
{
return ScriptBlock.Create(scriptBlockExpressionAst.Extent.Text);
}
Вместо доступа к ScriptBlockAst и получения нового блока сценария с помощью GetScriptBlock():
public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
=> scriptBlockExpressionAst.ScriptBlock.GetScriptBlock();
Тогда результат будет похож на тот, который мы получаем, используя Invoke-Expression:
'@{ test = { 1 + 1 } }' | Out-File tmpconfig.psd1
$tmpConfig = Get-Content .\tmpconfig.psd1 -Raw | Invoke-Expression
$tmpConfig['test'].InvokeReturnAsIs() # 2
Если вы хотите копнуть глубже, они используют Parser , чтобы получить хеш-таблицу из psd1, см. ImportPowerShellDataFile.cs#L60-L70.
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
'tmpconfig.psd1',
[ref] $null,
[ref] $null)
$hashAst = $ast.Find(
{ $args[0] -is [System.Management.Automation.Language.HashtableAst] }, $false)
Отсюда они используют SafeGetValue(), чтобы получить хеш-таблицу из HashtableAst:
$hashtable = $hashAst.SafeGetValue()
$hashtable['test'].InvokeReturnAsIs().InvokeReturnAsIs() # 2
В этом пути кода также используется SafeGetValue() поверх ScriptBlockExpressionAst для получения блока сценария из значения записи словаря, что оборачивает блок сценария в другой блок сценария, как было показано ранее:
$sb = $hashAst.KeyValuePairs[0].Item2.PipelineElements[0].Expression.SafeGetValue()
$sb.InvokeReturnAsIs().InvokeReturnAsIs() # 2
Полезный ответ Сантьяго показывает результаты отличного исследования, которое выявило фактическое поведение Import-PowerShellDataFile, начиная с PowerShell 7.4.x.
Однако - если я ничего не упускаю - такое поведение не является оправданным ни по общим концептуальным соображениям, ни по соображениям безопасности.
Другими словами:
Тот факт, что запись хеш-таблицы, представляющая собой блок сценария ( [scriptblock] экземпляр, { ... }), хранящаяся в файле *.psd1, искусственно оборачивается в другой экземпляр при импорте через Import-PowerShellDataFile, следует считать ошибкой.
{ и }), вызов исходного блока скрипта, завернутого извне, фактически выводит исходный код исходного блока скрипта на терминал; например, & { {Write-Output "Hello"} } печатает дословно Write-Output "Hello"Насколько я могу судить, нет никакой угрозы безопасности, связанной с десериализацией сохраняемого литерала блока скрипта из файла *.psd1 как есть, поскольку это не приводит к выполнению указанного блока; исполнение требует явных действий со стороны импортера.
Фактически о рассматриваемой проблеме сообщалось в 2020 году в выпуске GitHub № 12789; к сожалению, этот вопрос так и не был рассмотрен.
Как отмечает создатель указанного выпуска в этом комментарии (выделено автором):
Я немного больше исследовал эту проблему – кажется, это могло быть сделано намеренно, хотя и неудачно. Использование блоков скриптов в .psd1, очевидно, официально не поддерживается, поскольку предполагается, что это статический формат, предназначенный только для данных. Тем не менее, я думаю, что это должно быть либо исправлено (желательно), либо явно отклонено с содержательным сообщением об ошибке при импорте.
Кстати, десериализация блоков сценариев также проблематична в инфраструктуре удаленного взаимодействия PowerShell (которая также используется в фоновых заданиях и внутрипроцессных вызовах CLI с использованием блоков сценариев), где возникает обратная проблема: то, что изначально было [scriptblock], необъяснимым образом десериализуется как строка ([string]); к сожалению, такое поведение было объявлено намеренным: см. выпуск GitHub № 11698.
Согласен, это была либо ошибка, либо намеренно по неизвестной нам причине. соответственно обновил мой ответ
В качестве отступления: лучше избегать использования метода
.Invoke()для выполнения блока сценария в коде PowerShell, поскольку он меняет семантику вызова в нескольких аспектах. Вместо этого используйте&, оператор вызова; например,& $tmpconfig.test [arg1 ...]. См. этот ответ для получения дополнительной информации.