Search code examples
powershellmsdeployinvoke-command

Powershell Invoke-Command with hyphen arguments


I need to be able to run the following cmd command on a remote computer.

"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 looks promising but I cannot figure out how to pass the arguments to msdeploy.

I've tried

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 }

But it says "you must provide a value expression on the right-hand side of the '-' operator.". So I'm guessing the issue is the hyphens but I don't know how to escape them to make the command work.


Solution

  • Part one: dealing with PowerShell command-line specifics

    Use & to call external executable. Syntax: & "[path] command" [arguments].

    Also note, that msdeploy supports alternate way of specifying arguments when called from PowerShell:

    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

    Note, that arguments are wrapped in the array: this hints PowerShell how to pass them correctly to the target application.

    Example:

    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')}
    

    Part two: dealing with multi-hop authentication

    From your comments I'm seeing that now PowerShell successfully runs msdeploy on remote server, but msdeploy can't access the remote share:

    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).

    That's because you actually trying to establish a remoting session from computer A (client) to computer B (server) and then from computer B, you trying to access the file in a share on computer C (\\Server\Share\Package.zip).

    CredSSP example

    Invoke-Command fails because remote session tries to access the file share using the machine credentials instead of the credentials used to invoke the remote session. There is a way to pass or delegate credentials from the client so that we can authenticate to the file share. This is what is called multi-hop authentication and PowerShell remoting enables this using CredSSP.

    To enable CredSSP, run those commands from elevated prompt:

    • On your PC: Enable-WSManCredSSP -Role Client -DelegateComputer "TargetServer.FQ.DN"

      DelegateComputer parameter is used to specify the server or servers that receive the delegated credentials from the client. The DelegateComputer accepts wildcards (*.FQ.DN). You can also specify * to specify all computers in the network.

    • On target server: Enable-WSManCredSSP -Role Server

    Now, you should be able to run Invoke-Command with CredSSP as the authentication method and pass the credentials:

    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')}
    

    Part three: passing parameters with 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?

    To pass arguments to a scriptblock, use ArgumentList parameter:

    $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 $_
        }
    

    And going further:

    Н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.

    If you have one set of folders to exclude for all servers:

    $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)
        }
    

    If you have different set of folders for each of servers:

    $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)
        }