代码之家  ›  专栏  ›  技术社区  ›  Sage Pourpre

使用PowershellDataFile/不使用Powershell DataFile/在具有不同行为的简单脚本块上调用

  •  2
  • Sage Pourpre  · 技术社区  · 1 年前

    我正试图从通过导入的PSD1中的值调用Powershell中的脚本块 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 
    
    2 回复  |  直到 1 年前
        1
  •  2
  •   Santiago Squarzon    1 年前

    他们走这条路很可能是出于安全考虑,他们甚至在 documentation :

    这个 Import-PowerShellDataFile cmdlet从中定义的哈希表中安全地导入键值对 .PSD1 文件可以使用导入值 Invoke-Expression 关于文件的内容。然而 调用表达式 运行文件中包含的任何代码。

    如果你想潜水更深,他们会使用 Parser 从中获取哈希表 psd1 看见 ImportPowerShellDataFile.cs#L60-L70 .

    $tmpconfigstring = @'
    @{
        test = { 1 + 1 }
    }
    '@
    
    $tmpconfigstring | Out-File tmpconfig.psd1
    $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
    

    与使用非常不同 调用表达式 :

    $tmpConfig = Get-Content .\tmpconfig.psd1 -Raw | Invoke-Expression
    $tmpConfig['test'].InvokeReturnAsIs() # 2
    

    它很可能使用不同的代码路径从字典条目值中获取脚本块,也可能访问 ScriptBlockAst 从那里开始 GetScriptBlock() :

    $sb = $hashAst.KeyValuePairs[0].Item2.PipelineElements[0].Expression.ScriptBlock.GetScriptBlock()
    $sb.InvokeReturnAsIs() # 2
    
        2
  •  1
  •   mklement0    1 年前

    Santiago's helpful answer 显示了出色的侦查结果,发现了 事实上的 的行为 Import-PowerShellDataFile ,自PowerShell 7.4.x起。

    然而,除非我遗漏了什么,否则这种行为是 合理的,既不在一般概念上,也不在 安全 理由。

    换句话说:

    • 事实 该哈希表条目是 script block ( [scriptblock] 例子 { ... } )存储在 *.psd1 文件是 人工包裹 另一个 例子 什么时候 通过导入 导入PowerShellDataFile 应该被视为 程序错误 .

      • 至于 症状 :因为脚本阻止字符串作为 逐字内容 (不带外壳 { } ),调用外部包装的原始脚本块实际上输出 源代码 向终端发送原始脚本块的;例如。, & { {Write-Output "Hello"} } 逐字打印 Write-Output "Hello"
    • 据我所知 与从反序列化持久化脚本块文字相关的安全风险 *.psd1 文件 照原样 ,就像这样做 导致 处决 所述块的;执行要求 显式作用 由进口商提供。

      • 事实上 2020年报告了手头的问题 GitHub issue #12789 ; 不幸的是,它从未得到处理。

        • 正如上述问题的创建者在中所指出的那样 this comment (增加强调):

          我围绕这个问题做了更多的探索——这似乎是故意的,尽管很不幸。 在.psd1中使用脚本块显然不受官方支持,因为它应该是一种静态的、仅限数据的格式。尽管如此,我认为这应该被修复(最好),或者在导入时通过一条有意义的错误消息明确表示不赞成。

      • 与此相关的是,脚本块反序列化在PowerShell的 远程处理 基础结构(也用于后台作业和使用脚本块的进程内CLI调用),其中 相反的 问题出现了:最初是什么 [脚本块] 莫名其妙地反序列化为 一串 ( [string] ); 不幸的是,此行为被声明为 按设计 看见 GitHub issue #11698 .