Issues with Invoke-Command while installing softwa

2019-01-26 20:45发布

问题:

I need to install an application in several remote servers in quiet mode. I have created a script (Installer.ps1) like below using Powershell v3.0:

param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)

    $ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe";  & $ExePath /q;}
    Invoke-Command -ComputerName (Get-Content Servers.txt) -Credential $mycreds $ScrBlock -ArgumentList $InstallerFolderPath

}

InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password

Then I call the script like below (Installer folder path can have white spaces and the executable ServerReleaseManager.exe accepts argument):

.\Installer.ps1 -ServerNameFilePath Servers.txt -InstallerFolderPath "\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7" -UserName "Domain\User" -Password "Test123"

I am getting below CommandNotFoundException always:

The term '\\TestServer01\Public\Stable Applications\Server Release Manager Update 2\2.7\ServerReleaseManager.exe' is not recognized as the name of a cmdlet, function, script file, or 
operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

I have tried other options like using -FilePath with Invoke-Command but same error. I am really blocked here. Can you please let me know why this error has shown? How to resolve the error? Or are there any better ways to deal with this. Thanks for your help.

回答1:

This sounds like a double-hop authentication issue. Once you're remoted into the server, you can't access a file share on a third server because you can't pass your kerberos-based authentication to it.

You could try copying from the share to the remote server, first (this has to be done on the computer executing the script), and then in the scriptblock refer to the (now local) path.

You could set up CredSSP which isn't a great idea for this purpose.

Basically, you need to avoid connecting to one machine, then connecting to another through that connection.

Code that implements the workaround I'm describing:

param(
[String] $ServerNameFilePath = $(throw "Provide the path of text file which contains the server names"),
[String] $InstallerFolderPath = $(throw "Provide the Installer Folder Path. This should be a network location"),
[String] $UserName = $(throw "Provide the User Name"),
[String] $Password= $(throw "Provide the Password")
)
Function InstallApp
{
    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)


    $ScrBlock = {param($InstallerFolderPath) $ExePath = Join-Path $InstallerFolderPath "ServerReleaseManager.exe";  & $ExePath /q;}

    Get-Content Servers.txt | ForEach-Item {
        $remoteDest = "\\$_\c`$\some\temp\folder"
        $localDest = "C:\some\temp\folder" | Join-Path -ChildPath ($InstallerFolderPath | Split-Path -Leaf)

        try {
            Copy-Item -Path $InstallerFolderPath -Destination $dest -Force
            Invoke-Command -ComputerName $_ -Credential $mycreds $ScrBlock -ArgumentList $localDest
        finally {
            Remove-Item $remoteDest -Force -ErrorAction Ignore
        }
    }
}

InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFolderPath $InstallerFolderPath -UserName $UserName -Password $Password

Notes

  1. This is untested.
  2. As mentioned by Swonkie, you should set your parameters as mandatory if that's what you're looking to achieve (not addressed in my code).
  3. You shouldn't pass separate plain text user name and password parameters and then convert them to a credential object. Instead pass a single [PSCredential] parameter. You can use a default value that prompts, like [PSCredential] $Cred = (Get-Credential). (this is not addressed in my code either).


回答2:

Desired state configuration can be used to install software on target machines. I assume this can work around the double hop issue.

http://technet.microsoft.com/de-de/library/dn282132.aspx

http://technet.microsoft.com/de-de/library/dn282129.aspx

By the way - dont throw errors for missing mandatory arguments. Let PowerShell handle that - it's much more user friendly:

param(
    [parameter(Mandatory=$true)] [string] $ServerNameFilePath,
    [parameter(Mandatory=$true)] [string] $InstallerFolderPath,
    [parameter(Mandatory=$true)] [string] $UserName,
    [parameter(Mandatory=$true)] [string] $Password
)


回答3:

Here I created a new PSsession to each server in the list and used the invoke command to target that server's session. I've tested it in my environment and it successfully installs my exe application with a /q switch on my remote servers.

This method however does not tell if you the command ran successfully on the remote side, you would have to logon to the server or do a test-path to the expected location of the installed files for validation. Also, PSsessions are held open until the console that launched the command is closed. If a PSsession ends before the install completes, the install will fail.

Function InstallApp {

    param(
        [parameter(Mandatory=$true)] [String] $ServerNameFilePath,
        [parameter(Mandatory=$true)] [String] $InstallerFilePath,
        [parameter(Mandatory=$true)] [String] $CommandArgument,
        [parameter(Mandatory=$true)] [String] $UserName,
        [parameter(Mandatory=$true)] [String] $Password
    )

    $secpasswd = ConvertTo-SecureString $Password -AsPlainText -Force
    $mycreds = New-Object System.Management.Automation.PSCredential ($UserName, $secpasswd)

    Get-Content $ServerNameFilePath | ForEach-Object {
        $remoteSession = new-PSSession $_ -Credential $mycreds
        Invoke-command -Session $remoteSession -Scriptblock {& ($args[0]) @($args[1])} -ArgumentList $InstallerFilePath,$CommandArgument 
    }
}

InstallApp -ServerNameFilePath $ServerNameFilePath -InstallerFilePath $InstallerFilePath -CommandArgument $CommandArgument -UserName $UserName -Password $Password