如何在PowerShell 4.0 Invoke-Command -ScriptBlock中正确传递字符串数组作为参数

时间:2017-10-24 19:49:07

标签: powershell powershell-v4.0 powershell-remoting invoke-command

我正在使用PowerShell 4.0,我正在尝试将字符串数组作为Invoke-Command -ScriptBlock的参数之一传递,其中我在远程服务器上调用另一个PowerShell脚本。当我这样做时,字符串数组似乎变平,因此它显示为单个字符串值,而不是字符串数组。

下面列出的是第一个脚本,由提供初始参数的Bamboo部署服务器调用。

在Debug部分中,$ SupportFolders字符串数组由FlowerBoxArrayText函数迭代,它正确地将两个文件夹路径写入控制台,如预期的那样。

24-Oct-2017 14:59:33    *****************************************************************************
24-Oct-2017 14:59:33    **** E:\SRSFiles\SRSOutput
24-Oct-2017 14:59:33    **** E:\SRSFiles\SRSBad
24-Oct-2017 14:59:33    *****************************************************************************

这是第一个脚本文件的初始部分,显示输入参数,字符串数组创建以及我通过Invoke-Command调用远程脚本的位置;

 [CmdletBinding(DefaultParametersetName='None')]
 param (
    # Allows you to specify Install, Delete or Check.
    [ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
    # Allows you to specify the remote server name.
    [string] $ComputerName = "None",
    # Allows you to specify the username to use for installing the service.
    [string] $Username = "None",
    # Allows you to specify the password to use for installing the service.
    [string] $Password = "None",
    # Allows you to specify the location of the support folders for the service, if used. 
    [string] $SupportFoldersRoot = "None"    
)

Function CreateCredential() 
{
    $Pass = $Password | ConvertTo-SecureString -AsPlainText -Force
    $Cred = New-Object System.Management.Automation.PSCredential($Username, $Pass) 
    Return $Cred
}

Function FlowerBoxArrayText($TextArray, $TextColor="Yellow")
{
    Write-Host "*****************************************************************************" -ForegroundColor $TextColor
    foreach($TextLine in $TextArray) 
    {
        IndentedText $TextLine $TextColor
    }
    Write-Host "*****************************************************************************" -ForegroundColor $TextColor
}


Function IndentedText($TextToInsert, $TextColor="Yellow")
{
    Write-Host "**** $TextToInsert" -ForegroundColor $TextColor
}



$Credential = CreateCredential
[string[]] $ResultMessage = @()
[string] $Root = $SupportFoldersRoot.TrimEnd("/", "\")
[string[]] $SupportFolders = @("$Root\SRSOutput", "$Root\SRSBad")

#Debug
Write-Host "**** Starting debug in ManageAutoSignatureProcessorService ****"
FlowerBoxArrayText $SupportFolders -TextColor "Green"
Write-Host "**** Ending debug in ManageAutoSignatureProcessorService ****"
#End Debug

$ResultMessage = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {
    param($_action,$_username,$_password,$_supportFolders) &"C:\Services\ManageService.ps1" `
    -Action $_action `
    -ComputerName DEV `
    -Name DevProcessor `
    -DisplayName 'DevProcessor' `
    -Description 'DevProcessor' `
    -BinaryPathName C:\Services\DevProcessor.exe `
    -StartupType Manual `
    -Username $_username `
    -Password $_password `
    -ServicePathName C:\Services `
    -SupportFolders $_supportFolders `
    -NonInteractive } -ArgumentList $Action,$Username,$Password,(,$SupportFolders)

if ($ResultMessage -like '*[ERROR]*') 
{
    FlowerBoxArrayText $ResultMessage -textColor "Red"
} 
else 
{
    FlowerBoxArrayText $ResultMessage -textColor "Green"
}

然后,在远程服务器上的ManageService.ps1脚本文件中,我有以下内容;

[CmdletBinding(DefaultParametersetName='None')]
    param (
    # Allows you to specify Install, Delete or Check.
    [ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
    # Allows you to specify the name of the remote computer.
    [string] $ComputerName = "None",
    # Allows you to specify the service name.
    [string] $Name = "None",
    # Allows you to specify the service display name.
    [string] $DisplayName = "None",
    # Allows you to specify the service description.
    [string] $Description = "None",
    # Allows you to specify the path to the binary service executable file.
    [string] $BinaryPathName = "None",
    # Allows you to specify how the service will start, either manual or automatic.
    [ValidateSet("Manual", "Automatic")][string] $StartupType = "Manual",
    # Allows you to specify the domain username that the service will run under.
    [string] $Username = "None",
    # Allows you to specify the password for the domain username that the service will run under.
    [string] $Password = "None",
    # Allows you to specify the path to the service install scripts and service files on the remote server.
    [string] $ServicePathName = "None",  
    # Allows you to specify the location of the support folders for the service, if used. The default value is an empty array
    [string[]] $SupportFolders = @(),    
    # Disables human interaction, and allows all tests to be run even if they 'fail'.
    [switch] $NonInteractive
)


Function CreateCredential() 
{
    $Pass = $Password | ConvertTo-SecureString -AsPlainText -Force
    $Cred = New-Object System.Management.Automation.PSCredential($Username, $Pass) 
    Return $Cred
}


[bool] $OkToInstall = $False
[string[]] $ResultMessage = @()

#Debug
$ResultMessage = $ResultMessage += "[DEBUG] ***************************************"
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders: [$SupportFolders] ."

foreach ($Folder in $SupportFolders) 
{
    $ResultMessage = $ResultMessage += "[DEBUG] SupportFolders Item: $Folder."
}
$Count = @($SupportFolders).Count
$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders Count: $Count ."
$ResultMessage = $ResultMessage += "[DEBUG] ***************************************"
#End Debug

这一行,

$ResultMessage = $ResultMessage += "[DEBUG] SupportFolders: [$SupportFolders] ."

显示返回到调用脚本的$ ResultMessage值的以下结果;

**** [DEBUG] SupportFolders: [E:\SRSFiles\SRSOutput E:\SRSFiles\SRSBad] .

请注意,数组已展平。

后面的foreach循环也只打印出一个值而不是两个;

"E:\SRSFiles\SRSOutput E:\SRSFiles\SRSBad"

我花了很多时间研究解决方案,但尚未找到答案。

有什么想法吗?

编辑1使用@Bacon Bits建议;

$Options = @{'Action' = $Action
        'ComputerName' = 'DEV'
        'Name' = 'DevProcessor'
        'DisplayName' = 'DevProcessor'
        'Description' = 'Generate daily processes'
        'BinaryPathName' = 'C:\Services\DevProcessor\DevProcessor.exe'
        'StartupType' = 'Manual'
        'Username' = $Username
        'Password' = $Password
        'ServicePathName' = 'C:\Services\DevProcessor'
        'SupportFolders' = $SupportFolders
}

$ScriptBlock = {
param($Options)
& {
    param(
        $Action,
        $ComputerName,
        $Name,
        $DisplayName,
        $Description,
        $BinaryPathName,
        $StartupType,
        $Username,
        $Password,
        $ServicePathName,
        $SupportFolders,
        $NonInteractive
    )
    &powershell "C:\Services\DevProcessor\ManageService.ps1 $Action $ComputerName $Name $DisplayName $Description $BinaryPathName $StartupType $Username $Password $ServicePathName $SupportFolders"
} @Options;
}

$ResultMessage = Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock $ScriptBlock -ArgumentList $Options

如果我运行上面列出的修改代码,我仍然会获得$ SuppportFolders的扁平数组,而ManageService.ps1脚本会跳过具有空格的参数,即使它们在我分配它们时也会被引用。

完全包装ManageService.ps1中的代码的选项,而不是简单地调用远程脚本是不可行的,因为ManagedService.ps1脚本相当广泛且通用,因此我可以从我的30多个自动化脚本中调用它部署服务器。

我相信如果包装ManageService脚本是可行的,那么@Bacon Bits建议会起作用。

1 个答案:

答案 0 :(得分:1)

要传递单个数组,您可以执行以下操作:

Invoke-Command -Session $Session -ScriptBlock $ScriptBlock -ArgumentList (,$Array);

但是,只有在您只需要传递单个数组时才有效。一旦开始传递多个数组或多个复杂对象,它们就会崩溃。

有时,这会奏效:

Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList (, $Array1), (, $Array2), (, $Array3);

然而,根据我的经验,这可能是不一致的。有时它会再次使阵列变平。

您可以做的与this answer类似。

{param($Options)& <# Original script block (including {} braces)#> @options }

基本上我们做的是:

  1. 将脚本包装在一个脚本块中,该脚本块接受一个哈希表作为参数。
  2. 将所有参数放入哈希表。
  3. 使用传递的哈希表作为splat变量。
  4. 所以它会是这样的:

    $Options = @{
        Action = 'Check';
        ComputerName = 'XYZ123456';
        Name = 'MyName';
        .
        .
        .
    }
    
    $ScriptBlock = {
        param($Options) 
        & {
            [CmdletBinding(DefaultParametersetName='None')]
            param (
            # Allows you to specify Install, Delete or Check.
            [ValidateSet("Install", "Delete", "Check")][string] $Action = "Check",
            # Allows you to specify the name of the remote computer.
            [string] $ComputerName = "None",
            # Allows you to specify the service name.
            [string] $Name = "None",
            .
            .
            .
            .
            #End Debug
        } @Options;
    }
    
    Invoke-Command -ComputerName RemoteServer -ScriptBlock $ScriptBlock -ArgumentList $Options;
    

    这是一个简单的工作示例:

    $Options = @{
        List1 = 'Ed', 'Frank';
        List2 = 5;
        List3 = 'Alice', 'Bob', 'Cathy', 'David'
    }
    
    $ScriptBlock = {
        param($Options) 
        & {
            param(
                $List1,
                $List2,
                $List3
            )
            "List1"
            $List1
            ''
            "List2"
            $List2
            ''
            "List3"
            $List3
        } @Options;
    }
    
    Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $Options;
    

    输出:

    List1
    Ed
    Frank
    
    List2
    5
    
    List3
    Alice
    Bob
    Cathy
    David
    

    请注意,我在PowerShell v5上进行了测试。我不再拥有使用PowerShell v4进行测试的系统。