Вызов простого блока сценария с другим поведением с помощью PowershellDataFile/без

Я пытаюсь вызвать блок сценария в 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 

В качестве отступления: лучше избегать использования метода .Invoke() для выполнения блока сценария в коде PowerShell, поскольку он меняет семантику вызова в нескольких аспектах. Вместо этого используйте &, оператор вызова; например, & $tmpconfig.test [arg1 ...]. См. этот ответ для получения дополнительной информации.

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

Ответы 2

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

Возможно, они идут по этому пути из соображений безопасности, как указано в документации:

Командлет 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.

Согласен, это была либо ошибка, либо намеренно по неизвестной нам причине. соответственно обновил мой ответ

Santiago Squarzon 24.06.2024 19:23

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