带连字符参数的 Powershell 调用命令

Powershell Invoke-Command with hyphen arguments

我需要能够 运行 在远程计算机上执行以下 cmd 命令。

"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:package="\Server\Share\Package.zip" -dest:auto,computerName=Server1

Powershells Invoke-Command 看起来很有前途,但我不知道如何将参数传递给 msdeploy。

我试过了

Invoke-Command -ComputerName RemoteServer1 -ScriptBlock { "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" -verb:sync -source:package="\Server\Share\Package.zip" -dest:auto,computerName=Server1 }

但是它说 "you must provide a value expression on the right-hand side of the '-' operator."。 所以我猜问题是连字符,但我不知道如何转义它们以使命令正常工作。

第一部分:处理 PowerShell 命令行细节

使用&调用外部可执行文件。语法:& "[path] command" [arguments].

另请注意,从 PowerShell 调用时,msdeploy 支持 alternate way of specifying arguments

With a minor modification to its usual syntax, Web Deploy commands can be run from a Windows PowerShell prompt. To do this, change the colon character (:) after the verb, source, and dest arguments of the Web Deploy command to an equal sign (=). In the following example, compare the Web Deploy command with its PowerShell version.

Web Deploy command:

command: msdeploy -verb:sync -source:metakey=/lm/w3svc/1 -dest:metakey=/lm/w3svc/2 -verbose

PowerShell command:

.\msdeploy.exe -verb=sync -source=metakey=/lm/w3svc/1 -dest=metakey=/lm/w3svc/2 -verbose

请注意,参数包装在数组中:这会提示 PowerShell 如何将它们正确传递给目标应用程序。

示例:

Invoke-Command -ComputerName RemoteServer1 -ScriptBlock {&"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" @('-verb=sync', '-source=package="\Server\Share\Package.zip"', '-dest=auto,computerName=Server1')}

第二部分:处理multi-hop authentication

根据您的评论,我看到现在 PowerShell 在远程服务器上成功 运行s msdeploy,但 msdeploy 无法访问远程共享:

While the command executes, now msdeploy it saying:

"More Information: Object of type 'package' and path '\Server\Share\Package.zip' cannot be created. Learn more at: http://go.microsoft.com/fwlink/?LinkId=221672#ERROR_EXCEPTION_WHILE_CREATING_OBJECT.

Error: The Zip package '\Server\Share\Package.zip' could not be loaded. Error: Access to the path '\Server\Share\Package.zip' is denied. Error count: 1."

Even though the user has access to the share (read/write).

那是因为你实际上试图建立从计算机 A(客户端)到计算机 B(服务器)的远程会话,然后从计算机 B,你试图访问计算机 C 上共享中的文件(\Server\Share\Package.zip).

Invoke-Command 失败,因为远程会话尝试使用计算机凭据而不是用于调用远程会话的凭据来访问文件共享。 There is a way to pass or delegate credentials from the client 以便我们可以对文件共享进行身份验证。这就是所谓的多跳身份验证,PowerShell 远程处理使用 CredSSP 实现了这一点。

要启用 CredSSP,运行 来自提升提示符的那些命令:

  • 在您的电脑上:Enable-WSManCredSSP -Role Client -DelegateComputer "TargetServer.FQ.DN"

    DelegateComputer 参数用于指定从客户端接收委托凭据的服务器。 DelegateComputer 接受通配符 (*.FQ.DN)。您也可以指定*来指定网络中的所有计算机。

  • 在目标服务器上:Enable-WSManCredSSP -Role Server

现在,您应该能够 运行 Invoke-Command 使用 CredSSP 作为身份验证方法并传递凭据:

Invoke-Command -ComputerName RemoteServer1 -Authentication Credssp -Credential Domain\Username -ScriptBlock {&"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" @('-verb=sync', '-source=package="\Server\Share\Package.zip"', '-dest=auto,computerName=Server1')}

第三部分:使用 Invoke-Command 传递参数

How would I go about having the server parameterized. If I have a parameter $Server as the server name, how would I go about putting that in as a replacement for the -dest=auto,computerName=Server1 part?

要将参数传递给脚本块,请使用 ArgumentList 参数:

$Servers = @('Server1', 'Server2', 'Server3')
$Command = {
    Param($Srv)
    &"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" @('-verb=sync', '-source=package="\Server\Share\Package.zip"', "-dest=auto,computerName=$Srv")
}

$Servers |
    ForEach-Object {
        Invoke-Command -ComputerName RemoteServer1 -Authentication Credssp -Credential Domain\Username -ScriptBlock $Command -ArgumentList $_
    }

更进一步:

Нow would I go about adding extra parameters dynamically to the command. I need to add in -skip:objectName=dirPath,absolutePath="<folder>" to the arguments in the &"C:\Program Files\..." line, for each folder in an array of strings.

如果您要为所有服务器排除一组文件夹:

$Servers = @('Server1', 'Server2', 'Server3')
$SkipPaths = @('C:\folder\to\skip1', 'C:\folder\to\skip2', 'C:\folder\to\skip3')

$SkipCmd = $SkipPaths | ForEach-Object {"-skip=objectName=dirPath,absolutePath=$_"}

$Command = {
    Param($Srv, $Skp)
    &"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" $(@('-verb=sync', '-source=package="\Server\Share\Package.zip"', "-dest=auto,computerName=$Srv") + $Skp)
}

$Servers |
    ForEach-Object {
        Invoke-Command -ComputerName RemoteServer1 -Authentication Credssp -Credential Domain\Username -ScriptBlock $Command -ArgumentList ($_, $SkipCmd)
    }

如果您为每个服务器设置不同的文件夹:

$Servers = @{
    Server1 = @('C:\folder\to\skip_1', 'C:\folder\to\skip_2', 'C:\folder\to\skip_3')
    Server2 = @('C:\folder\to\skip_A', 'C:\folder\to\skip_B', 'C:\folder\to\skip_C')
    Server3 = @('C:\folder\to\skip_X', 'C:\folder\to\skip_Y', 'C:\folder\to\skip_Z')
}

$Command = {
    Param($Srv, $Skp)
    &"C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" $(@('-verb=sync', '-source=package="\Server\Share\Package.zip"', "-dest=auto,computerName=$Srv") + $Skp)
}

$Servers.GetEnumerator() |
    ForEach-Object {
        $SkipCmd = $_.Value | ForEach-Object {"-skip=objectName=dirPath,absolutePath=$_"}
        Invoke-Command -ComputerName RemoteServer1 -Authentication Credssp -Credential Domain\Username -ScriptBlock $Command -ArgumentList ($_.Key, $SkipCmd)
    }

注意:我错过了需要在远程计算机上执行的部分。下面的答案在那种情况下没有帮助,但它仍然与其他人相关,所以我将保留它。

有许多不同的方法可以在 PowerShell 中调用 .exe,每种方法都有其自身的缺点。从我尝试过的所有方法来看,以下方法最适合调用 msdeploy.exe.

您可以在 https://gist.github.com/sayedihashimi/1390cd6c97f25eefabdc 获取我的要点中的最新资源。我把它贴在下面了。

function Execute-CommandString{
    [cmdletbinding()]
    param(
        [Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]
        [string[]]$command,

        [switch]
        $ignoreExitCode
    )
    process{
        foreach($cmdToExec in $command){
            'Executing command [{0}]' -f $cmdToExec | Write-Verbose
            cmd.exe /D /C $cmdToExec

            if(-not $ignoreExitCode -and ($LASTEXITCODE -ne 0)){
                $msg = ('The command [{0}] exited with code [{1}]' -f $cmdToExec, $LASTEXITCODE)
                throw $msg
            }
        }
    }
}

# in this case there is no space in any argument and the call to msdeploy.exe succeeds
$msdeployExe = 'C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe'
$packOutput = 'C:\temp\publish'
$pubOut = 'c:\temp\publish-no-space\'

$publishArgs = @()
$publishArgs += ('-source:contentPath=''{0}''' -f "$packOutput")
$publishArgs += ('-dest:contentPath=''{0}''' -f "$pubOut")
$publishArgs += '-verb:sync'
$publishArgs += '-disablerule:BackupRule'

'Calling msdeploy to publish to file system with the command: [{0} {1}]' -f $msdeployExe,($publishArgs -join ' ') | Write-Output
$command = '"{0}" {1}' -f $msdeployExe,($publishArgs -join ' ')
Execute-CommandString -command $command

# in this case the call to msdeploy.exe fails with an argument error 
$pubOut2 = 'c:\temp\publish with space\'
$publishArgs2 = @()
$publishArgs2 += ('-source:contentPath=''{0}''' -f "$packOutput")
$publishArgs2 += ('-dest:contentPath=''{0}''' -f "$pubOut2")
$publishArgs2 += '-verb:sync'
$publishArgs2 += '-disablerule:BackupRule'

'Calling msdeploy to publish to file system with the command: [{0} {1}]' -f $msdeployExe,($publishArgs -join ' ') | Write-Output

$command = '"{0}" {1}' -f $msdeployExe,($publishArgs2 -join ' ')
Execute-CommandString -command $command

在这里,我用 " 引用 msdeploy.exe 的路径,并使用带有参数的 ' 转义任何路径。

我拥有 Visual Studio 网络发布经验,对于 ASP.NET 5,我们将从 PowerShell 调用 msdeploy.exe。这是我们目前为此使用的技术。

您可以使用 Powershell 的 stop parsing operator (--%):

Invoke-Command -ComputerName RemoteServer1 -ScriptBlock { 
    "C:\Program Files\IIS\Microsoft Web Deploy V3\msdeploy.exe" --% -verb:sync -source:package="\Server\Share\Package.zip" -dest:auto,computerName=Server1 
}

但是,您需要在其中添加新行,以停止停止解析并关闭脚本块。另一个缺点是,显然,您不能将变量放在运算符之后并期望它们扩展到它们的值。