1

I am new to PowerShell scripting. So please pardon me if I am asking a silly question. I have a task to connect to the command prompt of a remote desktop and run a series of commands on the command line.

I am using winrm to access the remote desktop machine. I have written the following code in a test.ps1 file:

powershell.exe -c "winrm set winrm/config/client '@{TrustedHosts=remoteHostServer}'"
powershell.exe -c "winrs /r:remoteHostServer /username:admin /password:xxxxxx cmd.exe"
powershell.exe -c "C:\test\"
powershell.exe -c "java test.java"

When I am executing the above code, I am reaching the following line:

C:\Users\admin>

But then it is not changing the directory to the path "C:\test" and execute the java file. Can anyone say what else should I do to execute the java files present in the path "C:\test" ?

RDX
  • 145
  • 3
  • 12

1 Answers1

1

Note: Instead of using winrs calls via powershell.exe to execute remote commands, consider using PowerShell's remoting, which is equally WinRM-based.


  • You don't need to make your winrm and winrs calls via powershell.exe.
  • To execute a command remotely, you must submit it as part of the winrs command line.
  • Separate powershell.exe calls create independent sessions, so you cannot use one call to create state for another; use a single call with multiple commands.

Therefore:

winrm set winrm/config/client "@{TrustedHosts=remoteHostServer}"
winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c "Set-Location C:\test; java test.java"

Note that you do not need PowerShell to execute your specific remote command either. Here's the version without PowerShell, using the -d option to set the remote working directory:

winrm set winrm/config/client "@{TrustedHosts=remoteHostServer}"
winrs /r:remoteHostServer /username:admin /password:xxxxxx -dC:\test java test.java

Note that the remote cmd.exe session that winrs implicitly creates automatically ends when a given command finishes executing (the powershell ... call in this case).
That is, the remote session ends and returns control to the caller once the remote command terminates.

An interactive remote session can only be entered if you specify a shell executable as the command to execute, such as cmd.exe and powershell.exe without arguments (or with /K / -NoExit).
However, note that the commands you submit remotely will be echoed before their output prints; while there is a -noecho option to suppress that, it unfortunately also hides commands as they're being typed.


Sending many commands to execute remotely via winrs.exe and PowerShell:

The above shows that you can simply pass multiple commands, separated with ;, inside the "..." string passed to the -Command (-c) parameter of powershell.exe, the Windows PowerShell CLI.
Given that the command-line length limit is close to 32,767 characters, this makes it possible to send many commands.
However, the single-line requirement and the need to escape embedded " chars. make this approach awkward.

The alternatives are:

  • (Untested) Place the commands in a *.ps1 script file on a file share that is accessible from the remote machine and invoke it from there, using powershell -ExecutionPolicy Bypass -File ...; doing so also requires passing the /allowdelegate option to winrs; e.g.:

    winrs /r:remoteHostServer /allowdelegate /username:admin /password:xxxxxx powershell -ExecutionPolicy Bypass -File "\\Server1\share1\script.ps1"
    
    • Note: If the script to execute is on the remote machine's local drive, simply use the local path - no need for a file share or /allowdelegate

    • Caveat: If the execution policy on the remote machine is GPO-controlled, -ExecutionPolicy Bypass will have no effect.

  • Pipe the contents of a script file to powershell -c -, i.e. make the remote PowerShell instance read the commands to execute from stdin:

    type script.ps1 | winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c -
    
    • Note: Since you're sending code via stdin in this case, you cannot also send data that way; if you need the latter (e.g. to automate Read-Host responses), you'll have to use the script-file-based approach or the approach where you pass the code directly to
      -c

    • Caveat: Using -Command - (-c -) has quirks, notably:

      • Make sure that script.ps1 has two newlines at the end of the file - otherwise, a multi-line statement (anywhere in the script) that isn't followed by an empty line won't get processed, along with any subsequent statements.

      • Each statement received via stdin executes in a separate pipeline (this slows down execution and can affect for-display output formatting)

      • See GitHub issue #3223 for details.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Thanks mklement0, but I need to run plenty of commands after logging into the remote machine. So the following line of code will continue to grow like anything: winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c "Set-Location C:\test; java test.java" So can you please advise how can we include all the commands? Can't we send them in different lines? – RDX Jun 24 '23 at 06:11
  • Hi mklement0, Thanks for the further clarifications. Now consider the following command: winrs /r:remoteHostServer /allowdelegate /username:admin /password:xxxxxx powershell -ExecutionPolicy Bypass -File "\\Server1\share1\script.ps1" Here can I place the file script.ps1 in any location (say C:\script.ps1) of the remote machine? And in the script.ps1 I just need to write just the list of all the commands or should I write them in specific syntax like we do in a typical powershell script? Please clarify. – RDX Jun 25 '23 at 16:07
  • @RDX, If the script to execute is on the remote machine's _local drive_, simply *use the local path* - no need for a file share or `/allowdelegate`. You only need the latter if you don't want to copy the script to the remote machine first. Either way, your `*.ps1` script must be a regular PowerShell script (which in the simplest case is a list of commands, each on its own line). – mklement0 Jun 25 '23 at 16:11
  • @ mklement0, Thanks for those info. Now I am able to execute multiple lines of code. But still one place I am getting stuck. Some of the commands asks users to give the inputs for the prompts. For ex consider the following prompt - + Enter user: When I send Joe, it says "The term 'Joe' is not recognized as the name of a cmdlet, function,..." Can you please say how can we send the inputs for the prompt? Remember the user input should be via automation, not manual. – RDX Jun 26 '23 at 07:51
  • @RDX: If you have control over the script, I suggest modifying it to accept _arguments_ instead of using `Read-Host` calls. If that is not an option, you can pipe the automated responses the `Read-Host` responses to the `winrs` call, e.g.: From PowerShell ``'jdoe' | winrs -r:localhost powershell -c '$user = Read-Host; \"You entered: $user\"'`` From `cmd.exe`: ``echo jdoe | winrs -r:localhost powershell -c "$user = Read-Host; \"You entered: $user\""`` – mklement0 Jun 26 '23 at 09:19
  • Suppose I run this : `type script.ps1 | winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c -` where script.ps1 has the command which will give a prompt for username. Then how can I accept the prompt because I think `'jdoe' | winrs -r:localhost powershell -c '$user = Read-Host; \"You entered: $user\"'` which you gave can be run separately. In my case I need to run just script.ps1 and that should automatically handle the prompt. Please advise what to do here. – RDX Jun 26 '23 at 11:10
  • Suppose I am executing this command `winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c "Set-Location C:\test; java test.java" ` and the file test.java asks for a prompt "Enter name:". So in this case how can I pass the value? `winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c "Set-Location C:\test; java test.java; echo 'Joe'` - will this command work? For me it is not entering the value 'Joe' in the prompt. How should I pass it? – RDX Jun 26 '23 at 11:36
  • @RDX, as the examples in my previous comment shows, you must provide the responses _via stdin_, i.e. via a _pipeline_. If you need to provide _multiple_ responses, provide them as separate strings; e.g., from PowerShell: `'response1', 'response2' | winrs ...` For more information, including about limitations, see [this answer](https://stackoverflow.com/a/72191533/45375). – mklement0 Jun 26 '23 at 13:31
  • Ok. I tried this - `'Joe' | winrs /r:remoteHostServer /username:admin /password:xxxxxx powershell -c "Set-Location C:\test; java test.java;`$user1 = Read-Host;"` . For this I am getting the following error: `Enter the prefix for users:java.util.NoSuchElementException: No line found Enter the Name:` See the prompt is coming after the exception but the user input is not getting accepted also... So how can I send that? – RDX Jun 26 '23 at 15:02
  • @RDX, Id don't know what your Java program does, but if it reads from stdin, it should see the `Joe` string. Once that is consumed, the `Read-Host` call would then receive _no_ input. Given that this answer addresses your question as asked and that you're now asking about a follow-up problem, I suggest creating a new question post focused on just your follow-up problem, ideally using a [mcve]. – mklement0 Jun 26 '23 at 21:34
  • 1
    Ok thanks a lot @mklement0. I will create a new question... – RDX Jun 27 '23 at 04:16