3

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.

skjcyber
  • 5,759
  • 12
  • 40
  • 60
  • One issue i see is that you have `-ComputerName (Get-Content Servers.txt)` which i think you want it to be `-ComputerName (Get-Content $ServerNameFilePath)` – Matt Oct 31 '14 at 15:48
  • yes.. that was a mistake..but it doesn't resolve the issue, – skjcyber Oct 31 '14 at 15:50
  • You dont have the `-ScriptBlock` parameter shown... i have to check but maybe its having a positional issue. Using the parameter name in from of `$ScrBlock` like `-ScriptBlock $ScrBlock` would help. In your parameter set it is Position 0 but it is looking for it in position 1. Try using the `-ScriptBlock` – Matt Oct 31 '14 at 15:51
  • Thanks for your suggestion. But I tried that too before. No luck. – skjcyber Oct 31 '14 at 15:54
  • Are you sure about the path? including the server name. And did you verify that the file is indeed at that location? – Micky Balladelli Nov 04 '14 at 12:25
  • @MickyB Yes, the path is correct...And the file exists over there. – skjcyber Nov 04 '14 at 13:03
  • Does it help if you change the `& $ExePath ..` to `Invoke-Expression $ExePath /q`? – arco444 Nov 05 '14 at 12:13

3 Answers3

3

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).
Community
  • 1
  • 1
briantist
  • 45,546
  • 6
  • 82
  • 127
  • CredSSP is not setup in our servers. And I am not allowed to do so. That's why I used -Authentication CredSSP, I got below error: "The WinRM client cannot process the request. A computer policy does not allow the delegation of the user credentials to the target computer." Is there any other way to solve this issue ? – skjcyber Nov 04 '14 at 10:37
  • There's no magic bullet. As I said, you could copy the files to the remote server and then refer to them with a local (to the server) path. If you don't have permissions to change policy or configuration, then your options are limited. – briantist Nov 04 '14 at 14:04
  • Edited may answer with some details. – briantist Nov 05 '14 at 15:49
3

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
)
  • DSC is if anything more prone to double hop issues by way of the fact that it always uses remoting, even in push mode. See my answer here: http://stackoverflow.com/questions/26304425/why-can-i-not-configure-tfs-build-service-in-unattend-mode-via-powershell-dsc – briantist Nov 04 '14 at 23:04
  • As I understand it, it's a service running on the target machine which actually makes the changes to the target machine (using a provider) and it will not use the credential of the user who created the configuration. A comment under your linked answer also mentions something to that effect. –  Nov 05 '14 at 20:38
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
Dino Padilla
  • 321
  • 1
  • 3
  • 11