如何在序列化之前扩展脚本块内的变量

时间:2018-01-05 08:12:30

标签: c# powershell serialization

我有一个接受[scriptblock]参数的自定义函数,在将脚本块发送到远程进程以进行反序列化和调用之前,使用[System.Management.Automation.PSSerializer]::Serialize()对其进行了serailzed。远程进程无权访问本地变量。我想允许在函数调用中将变量放入scriptblock中,但是我会使用第二个参数$ArgumentList来将参数\参数传递给scriptblock。我在System.Management.Automation.dll浏览了Microsoft.PowerShell.Commands.InvokeCommandCommand以查看我是否可以确定Invoke-command如何添加此功能,但我是C#的初学者并且无法弄明白。

如何在脚本块发送之前扩展它们中的变量?

本地序列化函数的一个示例:

Function Send-Command
{
    param(
    [scriptblock]$Scriptblock
    )
    [String]$Serialized = [System.Management.Automation.PSSerializer]::Serialize($Scriptblock)
    [byte[]]$MessageBytes = [System.Text.Encoding]::UTF8.GetBytes($Serialized)
    return $MessageBytes
}

另一方面:

[byte[]]$Serialized = [System.Text.Encoding]::UTF8.GetString($MessageBytes)
[String]$Deserialized = [System.Management.Automation.PSSerializer]::Deserialize($Serialized.ToString())
return $Deserialized

我用局部变量调用函数:

$String = "This is a Test"

Send-Command -Scriptblock {Write-Output $String }

另一方面:

Command: Write-Output $String

应输出:

Command: Write-Output "This is a Test"

3 个答案:

答案 0 :(得分:1)

有一种反序列化ScriptBlock的方法。

您必须首先从字符串创建一个脚本块。 然后,您必须序列化绑定到作用域的所有变量。 之后,我们使用正则表达式将变量替换为有效载荷以反序列化其值。

反序列化代码时,它将在ScriptBlock中具有注入的代码以反序列化变量的值,因此它们保留了内容。 然后,您需要调用GetNewClosure()来从反序列化器范围中解除绑定变量(例如任何参数),然后在下一次调用中将其自由绑定。

此解决方案基于powershell DSC基础结构的作用。


Function Serialize-Command
{
    param(
        [scriptblock]$Scriptblock
    )
    $rxp = '\$using:(?<var>\w+)'
    $ssb = $Scriptblock.ToString()
    $cb = {
        $v = (Get-Variable -Name $args[0].Groups['var'] -ValueOnly)
        $ser = [System.Management.Automation.PSSerializer]::Serialize($v)
        "`$([System.Management.Automation.PSSerializer]::Deserialize('{0}'))" -f $ser
    }
    $sb = [RegEx]::Replace($ssb, $rxp, $cb, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase)
    $Serialized = [System.Management.Automation.PSSerializer]::Serialize($sb)
    [System.Text.Encoding]::UTF8.GetBytes($Serialized)
}

Function Deserialize-Command
{
    param(
        [byte[]]$ScriptblockString
    )
    $Serialized = [System.Text.Encoding]::UTF8.GetString($ScriptblockString)
    $sb = [System.Management.Automation.PSSerializer]::Deserialize($Serialized.ToString())
    [Scriptblock]::Create($sb).GetNewClosure()
}

$a = "is this what you wanted?"
$b = Serialize-Command { "test $using:a" }
$a = "okay"
$c = Deserialize-Command $b
$a = "right"


PS> & $c
is this what you wanted?

来源:

https://www.briantist.com/how-to/use-duplicate-dsc-script-resources-in-loop/

https://docs.microsoft.com/en-us/dotnet/api/system.management.automation.scriptblock.getnewclosure?view=powershellsdk-1.1.0#System_Management_Automation_ScriptBlock_GetNewClosure

https://devblogs.microsoft.com/powershell/get-closure-with-getnewclosure/

答案 1 :(得分:0)

扩展变量的两种常用方法如下:

$VariableName
"$VariableName"
$($VariableName)

...但是还有其他考虑因素取决于你在做什么。

然而,你所问的,似乎与这篇文章非常相似:

Expand variable with scriptblock inside variable in a loop with runspaces

答案 2 :(得分:0)

使用PowerShell,我建议您执行以下操作:

为什么不将 Invoke-Command 与脚本块一起使用来在远程计算机上运行脚本块?您可以将两者与$ 使用 LocalVariableName 语句结合使用,以从远程主机访问局部变量,或者如上所述进行局部变量扩展,甚至将两者结合使用。

我想应该是这样的:

    $ScriptBlock = {
        Write-Output $using:Text
    }
    $Text = 'This is a test'

    function Send-Command {
        param (
            [scriptblock] $ScriptBlock,
            [string] $ComputerName,
            [System.Management.Automation.PSCredential] $Credential
        )

        Invoke-Command -ComputerName $Computername -ScriptBlock $ScriptBlock -Credential $Credential
    }

    $ComputerName = 'Server0123'
    $Credential = Get-Credential -Message "Enter credential for computer $($ComputerName)"
    Send-Command $ScriptBlock $ComputerName $Credential

PowerShell: Invoke-Command

PowerShell: about_remote_variables