Running Invoke-Command on remote machine - invoke-command

I am trying to run any command I want on a remote machine. Example: gpupdate /force or copy file1 to file2 etc... so I have this code:
$ComputerName = Read-Host "Enter a remote computer name"
$RemoteCommand = Read-Host "Enter a remote command to run: Example gpupdate /force"
$s = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $s -ScriptBlock {$RemoteCommand}
Invoke-Command -Session $s -ScriptBlock { $? }
It runs without error and in fact it returns TRUE. But the file I have in c:\temp never gets copied to c:\temp\tmp
why not?

The problem is that you are passing a string variable to Invoke-Command in the scriptblock, which just evaluates to the content of the string. You are not passing it a scriptblock with actual commands.
To illustrate the difference see this code:
# Output is just the content of the string
$commandString = "Get-Service spooler"
Invoke-Command {$commandString}
# Output is the result of the commandlet
$scriptBlock = {Get-Service spooler}
Invoke-Command -ScriptBlock $scriptBlock
To get the result you want you can use the [scritpblock] accelerator, like this:
# Output is the result of the commandlet invocation defined in the string
$commandString = "Get-Service spooler"
$scriptBlock = [scriptblock]::Create($commandString)
Invoke-Command -ScriptBlock $scriptBlock

Try running the script like this:
Invoke-Command -Session $s -ScriptBlock { powershell.exe -Command "$RemoteCommand"}
If you get problems with escaping characters, there is also the -encodedCommand switch. From the powershell help:
# To use the -EncodedCommand parameter:
$command = 'dir "c:\program files" '
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
powershell.exe -encodedCommand $encodedCommand

Related

How to use multiple commands into Invoke-Command with multiple outputs

Currently I have the following Invoke-Command:
Invoke-Command -ComputerName $i -ScriptBlock ${Function:query} `
-Credential oracle -ArgumentList $metconexao,$dumpdir,$i > $arq
Realize that the output of the Invoke-Command is put into $arq variable.
Now I need to do something like this:
Invoke-Command -ComputerName $i -ScriptBlock ${Function:query, commandTwo} `
-Credential oracle -ArgumentList $metconexao,$dumpdir,$i > $arq > outputTwo
I need the output of the commandTwo in another variable/file.
Is there some way to do this?
Create a PowerShell session and run each command with a separate Invoke-Command statement in the same session:
$s = New-PSSession -Computer $i -Credential oracle
Invoke-Command -Session $s -ScriptBlock ${function:query} -ArgumentList $metconexao,$dumpdir,$i > $arq
Invoke-Command -Session $s -ScriptBlock { commandTwo } -ArgumentList $metconexao,$dumpdir,$i > outputTwo
Note that > $arq does not write the output into the variable $arq, but into a file named after the value of that variable.

Why my powershell script return wrong exit code?

I'm trying to return exit code from a powershell script that is executed on a remote machine. But, when I check ExitCode it has some random number.
What I'm doing wrong? In addition, is it possible to return the whole text?
my script
$proc = Start-Process -Filepath "$PSExec" -ArgumentList "\\$server -h -u $user -p $pass -d PowerShell $command" -PassThru -Wait
$proc.ExitCode
remote script
New-Item "c:\temp\1.txt" -type file -force
exit 123
UPDATE
$secureString = ConvertTo-SecureString $password -Force -AsPlainText #$password includes password in clear text
$cred = New-Object System.Management.Automation.PSCredential($usrName, $secureString)
$sess = New-PSSession -ComputerName $serverName -Credential $cred
$command = "`"C:\temp\1.ps1`""
$result = Invoke-Command -Session $sess -ScriptBlock {
Start-Process -Filepath "$PSExec" -ArgumentList "\\$server -h -u $usrName -p $password -d PowerShell $command" -PassThru -Wait
}
Can you use Invoke-Command as an alternative?
Example:
$session = New-PSSesson -ComputerName $serverName -Credential (Get-Credential)
$result = Invoke-Command -Session $session -ScriptBlock {
Start-Process ...
}
As an alternative to Get-Credential you can created a credential object and pass it via the -Credential paramter to Invoke-Command. Example:
$secureString = ConvertTo-SecureString $password -Force -AsPlainText #$password includes password in clear text
$cred = [System.Management.Automation.PSCredential]::new($usrName, $secureString)
$sess = New-PSSession -ComputerName $ComputerName -Credential $cred
Invoke-Command -Session $sess -ScriptBlock { ... }
$result should also include the ExitCode property, since Powershell Remoting serializes the remote object. I always suggest Powershell Remoting compared to the cmdlet specific ComputerName implementations. It uses a more standardized way (WsMan -> HTTP(S)). See this link for further details.
Hope that helps.
For your first approach, your issue is that when running psexec with the -d (don't wait) flag it returns the pid of the command that launched it, rather than waiting and returning the exitcode.
Altogether your process also could be optimized. First if you wanted to use psexec.exe, I don't see a reason for Start-Process since you are waiting and passing through. Just & $psexec ... would suffice.
However Moerwald's suggestion for using Invoke-Command is a great one. In your updated code, you are still running Start-Process and Psexec which are unnecessary. When you are invoking the command, you are already remotely running code, so just run the code:
$secureString = ConvertTo-SecureString $password -Force -AsPlainText
$cred = New-Object System.Management.Automation.PSCredential($usrName, $secureString)
$result = Invoke-Command -ComputerName $serverName -Credential $cred -ScriptBlock {
New-Item "c:\temp\1.txt" -type file -force
exit 123
}
Also, since it doesn't look like you are reusing the session, I dropped the saving the session to a variable. And it would also be better to replace all of the credential setup with a Get-Credential rather than passing plaintext passwords around (avoid the password ending up in a saved transcript). That would look like this:
$result = Invoke-Command -ComputerName $serverName -Credential (Get-Credential) -ScriptBlock {
New-Item "c:\temp\1.txt" -type file -force
exit 123
}

How to execute batch file using powershell on remote machine? [duplicate]

I need to connect to some remote servers from a client (same domain as the servers) once connected, I need to run a batch file:
I've done so with this code:
$Username = 'USER'
$Password = 'PASSWORD'
$pass = ConvertTo-SecureString -AsPlainText $Password -Force
$Cred = New-Object System.Management.Automation.PSCredential -ArgumentList $Username,$pass
try {
Invoke-Command -ComputerName "SERVER1" -Credential $Cred -ScriptBlock -ErrorAction Stop {
Start-Process "C:\Users\nithi.sundar\Desktop\Test.bat"
}
} catch {
Write-Host "error"
}
This script does not give any errors, but it doesn't seem to be executing the batch script.
any input on this would be greatly appreciated.
Try replacing
invoke-command -computername "SERVER1" -credential $Cred -ScriptBlock -ErrorAction stop { Start-Process "C:\Users\nithi.sundar\Desktop\Test.bat" }
with
Invoke-Command -ComputerName "Server1" -credential $cred -ErrorAction Stop -ScriptBlock {Invoke-Expression -Command:"cmd.exe /c 'C:\Users\nithi.sund
ar\Desktop\Test.bat'"}
It's not possible that the code you posted ran without errors, because you messed up the order of the argument to Invoke-Command. This:
Invoke-Command ... -ScriptBlock -ErrorAction Stop { ... }
should actually look like this:
Invoke-Command ... -ErrorAction Stop -ScriptBlock { ... }
Also, DO NOT use Invoke-Expression for this. It's practically always the wrong tool for whatever you need to accomplish. You also don't need Start-Process since PowerShell can run batch scripts directly:
Invoke-Command -ComputerName "SERVER1" -ScriptBlock {
C:\Users\nithi.sundar\Desktop\Test.bat
} -Credential $Cred -ErrorAction Stop
If the command is a string rather than a bare word you need to use the call operator, though:
Invoke-Command -ComputerName "SERVER1" -ScriptBlock {
& "C:\Users\nithi.sundar\Desktop\Test.bat"
} -Credential $Cred -ErrorAction Stop
You could also invoke the batch file with cmd.exe:
Invoke-Command -ComputerName "SERVER1" -ScriptBlock {
cmd /c "C:\Users\nithi.sundar\Desktop\Test.bat"
} -Credential $Cred -ErrorAction Stop
If for some reason you must use Start-Process you should add the parameters -NoNewWindow and -Wait.
Invoke-Command -ComputerName "SERVER1" -ScriptBlock {
Start-Process 'C:\Users\nithi.sundar\Desktop\Test.bat' -NoNewWindow -Wait
} -Credential $Cred -ErrorAction Stop
By default Start-Process runs the invoked process asynchronously (i.e. the call returns immediately) and in a separate window. That is most likely the reason why your code didn't work as intended.

How to pass array arguments and something else?

$cmd = {
param([System.Array]$filestocopy = $(throw "need files"),
[bool]$copyxml)
if($copy)
#do stuff
}
$files = #("one","two","three")
invoke-command -session $s -scriptblock $cmd -argumentlist (,$files) $copyxml
Error:
Invoke-Command : A positional parameter cannot be found that accepts argument 'True'.
I have searched high and low and cannot find how to pass in an array along with something in a argumentlist. I have tried: (,$files,$copyxml), (,$files),$copyxml, and (,$files) $copyxml
Is there a way to do this?
The argument to the parameter -ArgumentList must be an array, otherwise $copyxml will be interpreted as the next positional parameter to Invoke-Command. Also, passing the array in a subexpression ((,$files)) will cause it to be mangled. Simply passing the variable ($files) is sufficient. Change this:
invoke-command -session $s -scriptblock $cmd -argumentlist (,$files) $copyxml
into this:
invoke-command -session $s -scriptblock $cmd -argumentlist $files,$copyxml

How can I handle remote errors with Invoke-Command

I have the following code:
Invoke-Command -ComputerName $remoteComputerName -Credentials $cred {& c:/program.exe}
How can I return the rc from program.exe as the Invoke-Command return code, particularly when it is non-zero.?
By default Invoke-Command will pass back whatever the result of the script was. If you are not sending back any other data you can always do something like this:
Invoke-Command -ComputerName $remoteComputerName -Credentials $cred {& c:/program.exe;$lastexitcode}
That should return the exit code of whatever application you were trying to run.
as an extension for what TheMadTechnician says, you can insert what ever happend in the remote computer to a powershell object, you can even wrap it with try{} catch{} or send back only $? (same as $LASTEXITCODE) and pass it back to the script:
$rc_oporation = Invoke-Command -ComputerName $remoteComputerName -Credentials $cred {& c:/program.exe; $?}
$rc_other_option = Invoke-Command -ComputerName $remoteComputerName -Credentials $cred { try{& c:/program.exe} catch{"there was a problem"} }
now "$rc_oporation" will hold your answers as 0 refer to success with no errors
hope that helps :)
before the above anwsers I got the following working (thanks to themadtechnician):
$s = New-PSSession -Name autobuild -ComputerName <ip address> -Credential $cred
Invoke-Command -Session $s -ScriptBlock {& 'C:\program.exe'}
$rc = Invoke-Command -Session $s -ScriptBlock {$lastexitcode}
if ($rc -ne 0)
{
write-output "run failed ..."
Remove-PSSession -Name autobuild
exit 1
}
else
{
write-output "run complete ..."
Remove-PSSession -Name autobuild
exit 0
}

Resources