From remote PowerShell, I need to run a PowerShell "GUI" script under an interactive user session. The only solution I found so far is to use PsExec (with the -i parameter). That works (I see the PowerShell window pop up).
But I need to transmit the outputs of this "GUI" script to the host of my remote script. Ideally, that should happen asynchronously during that the "GUI" script is running (i.e. without waiting for it to finish).
Is there a way to redirect the script output/error streams from the new powershell process to the caller powershell process?
CODE:
Remote script returning outputs to C# host asynchrounously (via a Pipeline):
Write-Output "Starting remote script..." # this string is transmitted to host
# Run interactiveScript.ps1 in other session using PsExec:
$EncodedCommand = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes('& "interactiveScript.ps1" -myArg1 "abc"'))
$CommandString = "-accepteula -nobanner -i 1 -u admin -p admin -h \\localhost cmd /c `"echo . | powershell.exe -ExecutionPolicy Bypass -EncodedCommand $EncodedCommand`"
$Result = Start-Process -FilePath $PsExecExecutable -ArgumentList $CommandString -Wait -NoNewWindow -PassThru -RedirectStandardOutput $TempFileNameSTDOUT -RedirectStandardError $TempFileNameSTDERR -ErrorAction Continue"
Interactive script interactiveScript.ps1 that should redirect all its outputs/errors to calling script, which transmits them asynchronously to C# host:
param
(
[Parameter(Mandatory=$true)]
[string]$myArg1
)
Write-Output "Starting interactive script..." # this string must be transmitted to host
Write-Error "This is an error" # this error must be transmitted to host
Sleep 1 # Here the user interacts with the script
[System.Drawing.Bitmap]$img = ... # Here the script takes a screenshot of a window
Write-Output ([string](MyImageSerialization($img)) # this string must be transmitted to host
Note: I can't transmit directly an object such as [System.Drawing.Bitmap] to the host because it is a reference so I will serialize it to a string. Therefore it is acceptable if the solution works only for string outputs and errors.
POSSIBLE CLUE:
I am thinking about using named pipes to communicate between processes. But I don't know how to redirect the powershell streams through the pipes.
Related
I'm having a problem with using New-PSSession in combination with "Enter-PSSession -session". Namely, that there's no way to block the calling process until the user types "exit" in the interactive PSSession. Here's a code example:
$s = New-PSSession -computer "remotecomputer1"
Invoke-Command -Session $s -ScriptBlock {
dir | out-host
# Setup misc stuff needed for
# Enter-PSSesion
}
Enter-PSSesion -Session $s
#
# at this point there are 2 threads running...
# one thread for the main script, and
# the other for PSSession
#
# Wait-PSSession -Session $s <== THE MISSING
# COMMAND THAT IS NONE EXISTENT IN POWERSHELL
#
# Now the main thread falls-though and
# doesn't wait!!!!! because there's
# no command to stop this????
write-host "Shouldn't get here" `
+ "until user types 'exit'" `
+ "in `Enter-PSsession -Session`"
Why this matters? Because what happens if I call this from WSL:
$ powershell.exe -File MyRemoting.ps1
It kills the interactive session because it doesn't block the main thread until the interactive session remoting session finishes.
tl;dr
What you're seeing is a bug (see bottom section).
There is currently only a suboptimal workaround for your use case:
You can call Enter-PSSession via the Powershell CLI (powershell.exe) with -NoExit in order to force an interactive remote session to be entered synchronously from a script; since you won't be able to pass the session object directly to the CLI call[1], you'll have to retrieve the session via Get-PSSession, by computer name and self-chosen session name.
However, note that this CLI-based workaround has an unpleasant side effect: you need to submit exit twice in order to return control to the caller; first to exit the remote session, and again to exit the CLI call's enclosing local session.
# Create the remote session indirectly, in a *disconnected* state,
# by submitting the session-initialization commands via Invoke-Command:
$computerName = 'remotecomputer1' # remote target computer.
$sessionName = "example_$pid" # self-chosen session name.
$null = Invoke-Command -InDisconnectedSession -ComputerName $computerName -SessionName $sessionName -ScriptBlock {
# Set up misc. stuff.
$foo = 'bar' # Set sample variable.
}
# Work around the bug: force entering the remote session
# *synchronously* via the CLI.
# !! IMPORTANT: YOU'LL NEED to submit `exit` TWICE
# !! TO EXIT THE REMOTE SESSION.
powershell.exe -NoExit -NoProfile {
param($ComputerName, $SessionName)
# Retrieve the preconfigured session from the remote computer.
$session = Get-PSSession -ComputerName $ComputerName -Name $SessionName
# Synchronously enter the session interactively.
# Note:
# * In order for Enter-PSSession to succeed, the session must be
# in the *disconnected* state, which we've ensured above.
# * You may get the following warning, which you can ignore:
# "You have entered a session that is busy running a command or script and output will be displayed in the console.
# You can wait for the running command to finish or cancel
# it and get an input prompt by pressing Ctrl-C."
# If you submit $foo interactively, 'bar' should be reported,
# conforming use of the preconfigured session.
Enter-PSSession -Session $session
} -Args $computerName, $sessionName
# As desired, this command now executes only after the remote
# session (and its transient wrapper session) have exited.
Write-Host #'
Should only get here after the user has typed 'exit'
*twice* in the remote session.
'#
What you're seeing is a bug in Enter-PSSession - see GitHub issue #16350 - present up to at least PowerShell (Core) 7.2.1:
Enter-PSSession should always block, in that it should enter an interactive remote session that shouldn't exit and therefore shouldn't return control to the caller until the user interactively terminates the remote session, typically by submitting exit or the equivalent Exit-PSSession.
This currently only works when Enter-PSSession is called directly directly from the prompt of a (local) interactive PowerShell session.
In other words: Enter-PSSession is currently broken when you call it from a script and/or via the CLI (pwsh for PowerShell (Core) 7+, powershell.exe for Windows PowerShell):
Entering the remote session is then unexpectedly deferred, and local execution of the remaining commands in the script file / passed to the -Command (-c) CLI parameter continues right away. In the case of the CLI, the deferred remote session is effectively never entered:
If you call a script file containing Enter-PSSession from inside a (local) PowerShell session, the remote session is then unexpectedly not entered until after the script has finished.
You can simulate this scenario via script block as follows; note: requires running from an elevated session, and requires your machine to be set up for PowerShell remoting:
# !! With the bug present, 'ConsoleHost' prints *first*
# !! - implying *local* execution - and only *then*
# !! is the interactive remote session entered.
& { Enter-PSSession -ComputerName .; $Host.Name }
If you call via the CLI, a remote interactive session is never even entered:
If you call powershell.exe - whether or not the last command is exit - or pwsh.exe with exit as the last command, the entire PowerShell process exits after all commands have executed locally and returns control to the caller; in other words: the interactive remote session is never entered.
If you call pwsh.exe without an explicit exit statement, the CLI call hangs after local command execution, and even Ctrl-C doesn't help.
Example (again, run from an elevated session, and make sure that your machine is set up for PowerShell remoting):
# !! With the bug present, 'ConsoleHost' prints *right away*
# !! - implying *local* execution - and then *returns*, i.e.
# !! the interactive *remote* session is never entered.
powershell -c 'Enter-PSSession -ComputerName .; $Host.Name; exit'
As discussed and shown in the code in the top section, a suboptimal workaround is to use -NoExit to ensure that the session is entered, but at the expense of having exit twice.
[1] Even though calling the PowerShell CLI from inside PowerShell with a script block { ... } tries to pass objects and not just strings via the -Args parameter, the objects undergo serialization and deserialization, which can result in loss of type fidelity. Indeed, in the case at hand, the deserialized form of what originally was a [System.Management.Automation.Runspaces.PSSession] instance (created by New-PSSession) is not recognized by Enter-PSSession's -Session parameter.
One option is to invoke Enter-PSSession in a separate process and wait for said process to exit:
# Do your setup/preparation like previously
$s = New-PSSession -computer "remotecomputer1"
Invoke-Command -Session $s -ScriptBlock {
dir | out-host
# Setup misc stuff needed for
# Enter-PSSesion
}
# Launch a new process that invokes Enter-PSSession, wait for process to exit
$p = Start-Process powershell.exe '-NoExit','-Command','Enter-PSSession -Session (New-PSSession remotecomputer1)' -Wait -Verb Runas
# We won't reach this point until the child process has exited
write-host "Shouldn't get here" `
+ "until user types 'exit'" `
+ "in `Enter-PSsession -Session`"
We have received 20 jmeter test plans, each testing one endpoint, which we need to run. On tests we need to pass parameters and others we don't.
My idea was to create a powershell script that loops through the directories and runs a test, waits until finished and then runs the next test. When we develop a new endpoint we just create a new test plan and save it in the appropriate folder and the powershell script will include it next time we loop through tests.
I need the tests to finish before starting the next plan, so I'm looking at something like:
Write-Output "Running Test 1"
$proc = Start-Process -FilePath "C:\JmeterLoadTests\apache-jmeter-5.2.1\bin\jmeter" -ArgumentList "-n -t C:\JmeterLoadTests\test\enpointsType1\test-1-1.jmx -Jduration=10"
$proc.WaitForExit()
Write-Output "Proc 1 Done"
Write-Output "Running Proc 2"
$proc2 = Start-Process -FilePath "C:\JmeterLoadTests\apache-jmeter-5.2.1\bin\jmeter" -ArgumentList "-n -t C:\JmeterLoadTests\test\enpointsType1\test-1-1.jmx -Jduration=10"
$proc2.WaitForExit()
This just launches both tests simultaneously.
My question is then how to make Powershell wait for the previous test to finish.
Your immediate problem is that your Start-Process call is missing the -PassThru switch, which is required for the call to return a System.Diagnostics.Process instance representing the newly launched process.
# ...
# Note the use of -PassThru
$proc = Start-Process -PassThru -FilePath "C:\JmeterLoadTests\apache-jmeter-5.2.1\bin\jmeter" -ArgumentList "-n -t C:\JmeterLoadTests\test\enpointsType1\test-1-1.jmx -Jduration=10"
$proc.WaitForExit()
# ...
Alternatively, if you don't need to examine the process exit code (which $proc.ExitCode in the above command would give you), you can simply use the -Wait switch, which makes Start-Process itself wait for the process to terminate:
# ...
# Note the use of -Wait
Start-Process -Wait -FilePath "C:\JmeterLoadTests\apache-jmeter-5.2.1\bin\jmeter" -ArgumentList "-n -t C:\JmeterLoadTests\test\enpointsType1\test-1-1.jmx -Jduration=10"
# ...
Taking a step back:
To synchronously execute console applications or batch files in the current console window, call them directly, do not use Start-Process (or the System.Diagnostics.Process API it is based on).
Aside from being syntactically easier and less verbose, this has two key advantages:
You can directly capture their output.
It allows you to examine the process exit code via the automatic $LASTEXITCODE variable afterwards.
Assuming that jmeter is a console application (the docs suggests it runs as one when invoked with arguments):
# ...
# Direct invocation in the current window.
# Stdout and stderr output will print to the console by default,
# but can be captured or redirected.
# Note: &, the call operator, isn't strictly needed here,
# but would be if your executable path were quoted
# or contained variable references.
& C:\JmeterLoadTests\apache-jmeter-5.2.1\bin\jmeter -n -t C:\JmeterLoadTests\test\enpointsType1\test-1-1.jmx -Jduration=10
# Use $LASTEXITCODE to examine the process exit code.
# ...
See this answer for more information.
It might be the case you're suffering from Out-Default cmdlet execution, the easiest is separating the commands with the semicolon like:
cmd1;cmd2;cmd3;etc;
this way Powershell will wait for the previous command to complete before starting the next one
Demo:
It might be a better idea considering switching to Maven JMeter Plugin which by default executes all tests it finds under src/test/jmeter folder relative to the pom.xml file
We have Alteryx on several servers and want to be able to run jobs remotely from either command line or powershell, not through Alteryx API.
I have run alteryx flows locally from command line and powershell locally for years but now with multiple servers we have need to execute certain flows this way but to trigger from a react web app. We tried the API and it works but jobs often queue for too long and we need near realtime response and these jobs run in seconds. Our solution is to trigger outside the Alteryx API for immediate execution on a worker node with excess resources.
Our problem is that when testing the script which works well if run locally it runs all other commands fine except the execution of the alteryxenginecmd.exe program.
if I use Start-Process with passthru it generates a process but no output from the alteryx job and no errors detected.
If I use Call operator & if fails saying it can;t find the workflow, so the alteryxenginecmd.exe is firing but not reading or receiving the full path of the flow to run as the parameter. but it works fine locally.
I have used creds to force a sign on to clear any credential issue with no change in result.
I know Powershell has some oddities and maybe there is some tweak to how a command must be specified if the script is run from a remote server?
This is the script I want to run from remote server which does work fine in any of the 4 scenarios shown
$rundate = (Get-Date -format 'u') -replace "-","" -replace ":","" -replace " ","-"
$Pgm2Run = "\<somepath>\RunScriptRemote\RunRemoteTest2 - NoDB.yxmd"
$Bat2Run = "\\<somepath>\RunScriptRemote\RunRemoteTestJob.bat"
$JobLogFile = "\<somepath>\RunScriptRemote\LogsJob\JobTranscript $rundate.txt"
$ScriptLogFile = "\<somepath>\RunScriptRemote\LogsScript\RunJobTranscript $rundate.txt"
Start-Transcript -Path $ScriptLogFile
echo " "
echo "$rundate Start --> "
Echo "$rundate Before Start-Process"
#1 Start-Process -filepath "AlteryxEngineCmd.exe" -ArgumentList `"$Pgm2Run`" -RedirectStandardOutput $JobLogFile -Wait -PassThru
#2 Start-Process -filepath $Bat2Run -RedirectStandardOutput $JobLogFile -Wait -PassThru
#3 & AlteryxEngineCmd.exe "$Pgm2Run"
AlteryxEngineCmd.exe "$Pgm2Run"
echo "$rundate After Start-Process"
echo "$rundate End --> "
Stop-Transcript
This command run on Server1 to execute the script above on server2
Invoke-Command -ComputerName server2 -FilePath "\\<somepath>\RunScriptRemote\RunAlteryxAndLogIt.ps1"
And this is the result:
20200831-094309Z Before Start-Process
AlteryxEngineCmd.exe Version 2019.4.8.22007 © Alteryx, Inc. - All Rights Reserved.
Started running \\<somepath>\RunRemoteTest2 - NoDB.yxmd
Error - Alteryx Engine: Can't read the file "
Invoke-Command -ComputerName server2 -FilePath "\\<somepath>\RunScriptRemote\RunRemoteTest2 - NoDB.yxmd"
Finished in 0.020 seconds with 1 error
20200831-094309Z After Start-Process
I am not sure it is a powershell thing, it may well be an Alteryx thing because the issue seems to be executing that one exe properly. the script triggers properly from remote server and I tested other commands and all worked, I just can't seem to get the one executing this exe to work when run remotely though it works fine locally when same script is run locally. its like a barrier betweek a script running another script.
Any powershell experts familiar with running exe's remotely from another server?
I want to make a PowerShell script that can be used to connect computers to various client's SonicWall VPNs (specifically through Global VPN and NetExtender). I would like to have it be like a user interface to prompt the user (which will set the variables) and then use that information to pass through to command lines in the command prompt.
I want to be able to have information entered in be applied in the cmd line in the script.
I have tried using the MobileConnect connection through (Using the the app from the app store) and connecting with the Microsoft VPN client, but that does not grab all the network information; specifically DNS servers.
The best way is to install either Global VPN or NetExtender and connect through cmd line; that way will grab the entire network information.
This is the basic command to run it:
Function Connect-VPN {
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
cmd /c "NECLI connect -s address:4433 -u Uname -p Password -d Domain -A"
Set-Location -Path C:\
}
Basically, you change the directory and execute the commands with those arguments.
I would like to prompt in POSH, create the variables with the user input, then have those arguments passed down.
What I have right now is:
param(
[string]$Testadd ,
[string]$Testun ,
[string]$TestPW ,
[string]$TestDom
)
If ($Testadd -eq "")
{$Testadd = (Read-Host "test")
}
If ($Testun -eq "")
{$Testun = (Read-Host "test")
}
If ($TestPW -eq "")
{$TestPW = (Read-Host "test")
}
If ($TestDom -eq "")
{$TestDom = (Read-Host "test")
}
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
cmd /c "NECLI connect -s "$($Testadd)" -u "$($Testun)" -p "$($TestPW)" -d "$($TestDom)" -A"
Set-Location -Path C:\
The problem is that the all the arguments come out null. I do not know if it is possible, but I wanted to see.
You can try to build the string before running the cmd
param (
[string]$Testadd,
[string]$Testun,
[string]$TestPW,
[string]$TestDom
)
If ($Testadd -eq "")
{
$Testadd = (Read-Host "testadd")
}
If ($Testun -eq "")
{
$Testun = (Read-Host "testun")
}
If ($TestPW -eq "")
{
$TestPW = (Read-Host "testpw")
}
If ($TestDom -eq "")
{
$TestDom = (Read-Host "testdom")
}
Set-Location -Path "C:\Program Files (x86)\SonicWALL\SSL-VPN\NetExtender"
#build the string before
$cmd = "NECLI connect -s " + $($Testadd) + " -u " + $($Testun) + " -p " + $($TestPW) + " -d " + $($TestDom) + " -A"
# Or even like this
$cmd = "NECLI connect -s $Testadd -u $Testun -p $TestPW -d $TestDom -A"
# exec command
cmd /c $cmd
Set-Location -Path C:\
To add to #Desinternauta, I suspect it is how the command is interpreting the quotes and the variables. i.e. when you write out the string as you have it, it adds spaces:
$b = "b"
Write-Host "a"$($b)"c"
Outputs:
a b c
The good news is that double quoted strings allow you to embed the variables into the string:
cmd /c "NECLI connect -s $Testadd -u $Testun -p $TestPW -d $TestDom -A"
Calling external exe / commands that use cmd.exe, require special consideration and outing specifics. You also do not need to call cmd.exe directly, as that will just happen. This is a well documented this. For example:
PowerShell: Running Executables
The Call Operator &
Why: Used to treat a string as a SINGLE command. Useful for dealing
with spaces.
In PowerShell V2.0, if you are running 7z.exe (7-Zip.exe) or another
command that starts with a number, you have to use the command
invocation operator &.
The PowerShell V3.0 parser do it now smarter, in this case you don’t
need the & anymore .
Details: Runs a command, script, or script block. The call operator,
also known as the "invocation operator," lets you run commands that
are stored in variables and represented by strings. Because the call
operator does not parse the command,it cannot interpret command
parameters
# Example:
& 'C:\Program Files\Windows Media Player\wmplayer.exe' "c:\videos\my home video.avi" /fullscreen
Start-Process (start/saps)
Why: Starts a process and returns the .Net process object Jump if
-PassThru is provided. It also allows you to control the environment in which the process is started (user profile, output redirection
etc). You can also use the Verb parameter (right click on a file, that
list of actions) so thatyou can, for example, play a wav file.
Details: Executes a program returning the process object of the
application. Allows you to control the action on a file (verb
mentioned above) and control the environment in which the app is run.
You also have the ability to wait on the processto end. You can also
subscribe to the processes Exited event.
#Example:
#starts a process, waits for it to finish and then checks the exit code.
$p = Start-Process ping -ArgumentList "invalidhost" -wait -NoNewWindow -PassThru
$p.HasExited
$p.ExitCode
10. Stop-Parsing Symbol --%
Why: Its a quick way to handle program arguments that are not standard. Also its the new cool way to do it.
Details: The stop-parsing symbol (--%), introduced in Windows PowerShell 3.0, directs Windows PowerShell to refrain from interpreting input as Windows PowerShell commands or expressions. When calling an executable program in Windows PowerShell, placethe stop-parsing symbol before the program arguments.
After the stop-parsing symbol --% , the arguments up to the end of the line (or pipe, if you are piping) are passed as is.
#Examples:
# icacls in V2
# You must use escape characters to prevent PowerShell from misinterpreting the parentheses.
icacls X:\VMS /grant Dom\HVAdmin:`(CI`)`(OI`)F
# In V3 you can use the stop-parsing symbol.
icacls X:\VMS --% /grant Dom\HVAdmin:(CI)(OI)F
See also:
Using Windows PowerShell to run old command line tools (and their weirdest parameters)
Solve Problems with External Command Lines in PowerShell
Quoting
About Quoting Rules
A Story of PowerShell Quoting Rules
I have a dynamically generated batch file that I push out to a remote PC and then execute it using PsExec. The issue I am facing is that as soon as that line is called, the PowerShell script moves on and doesn't wait for it to finish. Here is what I have:
psexec -accepteula \\$Server -u Username -p Password-d -i 2 cmd /c C:\Call.bat
Call.bat calls an executable on the remote machine with a few parameters passed in. This file is dynamically generated and is different every time, but can look like this:
cd C:\20161212-175524
C:\20161212-175524\RE.exe /pa1 /pa2 /pa3 /pa4 /pa5 /pa6 /pa7 /pa8 /pa9 /pa10
The batch file needs to run as an interactive script as that specific user, but I need it to at least wait for the spawned process to finish. I have tried adding 2>&1 and | Out-Null
Ideally, I would like to retrieve the exit code returned by the spawned process, but that may be too much.
Previously i have used something like this to achieve the waiting you are after:
Start-Process -FilePath 'c:\tools\PSexec.exe' -ArgumentList "-u MyUserName -p $password \\$Computer .\Run.bat $Var >> C:\Temp\$Computer.log" -Wait -Passthru -WindowStyle Hidden
What you need to focus on the line above is by using the Start-Process cmdlet we can use the -Wait parameter.
Hope this answers your question
private static string ExecuteAndGetOutput(string command)
{
string resultFile = Path.GetTempFileName();
string commandFile = Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(resultFile) + ".bat");
command += #" >""{0}""";
command = string.Format(command, resultFile);
File.WriteAllText(commandFile, command);
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = commandFile;
psi.WindowStyle = ProcessWindowStyle.Hidden;
Process p = Process.Start(psi);
p.WaitForExit();
int exitCode = p.ExitCode;
return File.ReadAllText(resultFile);
}