bash exec redirection equivalent in PowerShell - powershell

What's PowerShell equivalent of exec redirection in bash like this?
exec 2>&1
I wanted to make a PowerShell script to redirect its stderr to stdout by itself, without any function or script block calls like this, or recursive script invocation.
The redirection should occur in the beginning of the script, then all stderr output from the rest of the script and sub-process should go to stdout.

You can't: Unlike Bash, PowerShell has no feature for script-wide redirection of streams (as of PowerShell v7).
You'll have to do one of the very things you're trying to avoid:
Enclose all code in your script in . { ... } 2>&1
Recursively invoke your script via such an enclosure (which requires some mechanism, such as a conceptually private parameter, to prevent infinite recursion).
Also note that while stdout and stderr are the conceptual equivalents of PowerShell's success output stream and error stream, respectively:
PowerShell has 6 output streams in total - see about_Redirection (which your question already links to).
Regrettably, all of these streams are by default mapped to stdout when PowerShell is called from the outside, via its CLI.
However, you can selectively redirect the error stream using 2> from the outside - but none of the other streams, aside from the success stream.

There's no "exec" in powershell (overlaying a process). But redirecting stderr to stdout is the same:
yourscript.ps1 2>&1
If you were running pwsh from some other unix shell, or powershell from cmd in windows, everything would go to stdout anyway:
pwsh yourscript.ps1 > yourscript.log
Don't worry about marking me down. Other people will mark me up later. I'm used to it.

Related

How can I write to file descriptor 3 via pwsh on linux

I'm using pwsh on linux to run some specific powershell modules.
I want to output the data received to file descriptor 3. I want data on file descriptor 3 as Powershell doesn't respect the convention that stdout is data and stderr is logging. I want file descriptor 3 to serve as our "data" file descriptor.
End goal is to be able to do something like this as we wrap this powershell call in Python and we'll redirect the file descriptor 3 data ourselves
pwsh -f script.ps1 3>data
PowerShell has no built-in way to output to streams other than stdout (1) and stderr (2) (more on that below.
Conceivably, you can roll your own output behavior with .NET API and/or P/Invoke calls, but that would be cumbersome.
However, this may not be required:
While it is true that - unfortunately - PowerShell by default sends output from all its output streams to stdout - see GitHub issue #7989 - you can redirect PowerShell error-stream to stderr, if you apply a 2> redirection on the caller's side.
The following call, e.g. from bash, demonstrates this:
# Prints just 'hi', because the error-stream output was
# redirected to stderr thanks to 2>, and due to targeting /dev/null, suppressed.
pwsh -noprofile -c '"hi"; Write-Error no!' 2>/dev/null
The downside is that if you want to print the stderr output too, you must capture it in a file and print it afterwards, which means that it (a) won't appear at the time it is being produced and (b) therefore won't be properly interleaved with any stdout output.
As an aside:
The current behavior of the PowerShell CLI (as of v7.2.x) is unfortunate not only with respect to how output streams are mapped, but also because it loads profile files by default.
There was talk about providing a separate CLI to address the latter problem, in the context of which the stream-mapping behavior could be fixed too, but nothing has happened so far: see GitHub issue #8072.

PowerShell Tee-Object not capturing debug lines in file

I have a PowerShell script that runs via automation, so I need to capture the output of the script to a file, but I'd also like to capture the commands that were run, to give the output some context (I'd use set -x in a Linux shell script). I can't figure out how to capture those commands into the output file in Windows though; any help is much appreciated!
# script.ps1
Set-PSDebug -Trace 1
Write-Host "Hello, World!"
How I'm calling the script (from cmd):
$ powershell -command "./script.ps1 *>&1 | Tee-Object -FilePath ./output.txt"
Terminal output:
DEBUG: 2+ >>>> Write-Host "Hello, World!"
Hello, World!
output.txt file contents:
Hello, World!
You can see that the output file is missing the debug line that was shown in the terminal. Ideally, I'd like the debug lines to only be in the output file (not the terminal output), but having the debug lines in both places would be fine too.
Note that PowerShell 5.1 is the version I have installed.
Unfortunately, PowerShell's tracing output (activated via Set-PSDebug) operates outside PowerShell's system of output streams, so such output cannot be captured from inside a PowerShell session.[1]
However, an outside caller of PowerShell's CLI[2], such as cmd.exe in your case, does receive tracing output via stdout (the standard output stream)[3], so you can use cmd.exe's redirection to capture everything in a file and print it to the console afterwards.
A simplified example (from cmd.exe):
C:\>powershell -c "Set-PSDebug -Trace 1; Write-Host 'Hello, World!'" > output.txt & type output.txt
DEBUG: 1+ Set-PSDebug -Trace 1; >>>> Write-Host 'Hello, World!'
Hello, World!
[1] Perhaps surprisingly, despite the word "Debug" in the cmdlet name, tracing output does not go through stream number 5, the debugging stream, the way that Write-Debug output does.
[2] Note that you can use this technique of calling the CLI, invariably as a child process, from inside an existing PowerShell session as well, in which case that existing session acts as the outside caller. However, note that passing the command to execute via a script block ({ ... }), which is normally preferable, would not work in this case, because a script block-based invocation uses PowerShell's remoting infrastructure behind the scenes, which preserves the PowerShell-specific output streams (and also type fidelity, with limitations), so that the tracing output again goes straight to the console.
[3] Perhaps surprisingly, all of PowerShell's output streams - as well as to-console-only output such as produced by tracing - are by default mapped to an outside caller's stdout stream - including errors. While you can at least separate out errors on demand via a stderr redirection - 2> - on the caller side, all remaining streams are invariably sent to stdout - see the bottom section of this answer and GitHub issue #7989 for more information.

Powershell executable isn't outputting to STDOUT

From a powershell script (with nuget installed and on the path), I'm trying to execute an executable (.net, if that matters) ... but for some reason, I can't get the STDOUT to display in the command window.
nuget install mdoc -OutputDirectory packages -ExcludeVersion
start-process "packages/mdoc/tools/mdoc.exe" "--version"
echo "done"
This should output mdoc 5.7.2 (at the time of this post, current version). But you'll see the nuget output, then done.
Any thoughts on why this is not showing up?
As Ansgar's comment implies: On Windows, Start-Process runs console programs in a new console window by default, asynchronously.
If that program completes quickly, you may see the new console window flash only briefly, as it opens and closes soon thereafter, or you may miss the flash altogether - either way, its output will not show in the caller's console window.
Adding -Wait to the Start-Process call would make the invocation synchronous, and adding -NoNewWindow would make it run in the same console, yet the calling PowerShell session wouldn't be able to capture or redirect the invoked program's output - see below.
Taking a step back: Do NOT use Start-Process[1] if you want to run a console program synchronously, with its standard streams connected to PowerShell's streams - just invoke such a program directly:
packages/mdoc/tools/mdoc.exe --version
If the external program's path / name must be quoted (because its path contains spaces) and/or it is stored in a variable, simply use &, the call operator, to invoke it:
# Via a quoted string:
& "packages/mdoc/tools/mdoc.exe" --version
# Via a variable:
$exePath = "packages/mdoc/tools/mdoc.exe"
& $exePath --version
Using the direct-invocation approach gives you:
synchronous execution by default
the ability to capture and/or redirect the invoked program's stdout and stderr streams.
access to the program's process exit code is via PowerShell's automatic $LASTEXITCODE variable.
To put it all together (based on your later comments):
nuget install mdoc -OutputDirectory packages -ExcludeVersion
$exePath = "packages/mdoc/tools/mdoc.exe"
& $exePath --version
"done"
This prints the version number - mdoc 5.7.2 as of this writing - just before printing done (verified on Windows PowerShell v5.1.17134.48 on Microsoft Windows 10 Pro (64-bit; Version 1709, OS Build: 16299.371)).
Optional reading: capturing stdout / stderr output from external programs:
To capture stdout output, simply assign the call to a variable:
$version = & $exePath --version # $version receives stdout output as an *array of lines*
$version receives either a string scalar (single string) if there was only 1 line of output, or an array of strings representing the output lines.
To also capture stderr output, use redirection 2>&1:
[string[]] $allOutput = & $exePath --version 2>&1
Note the cast to [string[]], which ensures that the stderr lines are captured as strings too.
By default, they are captured as [System.Management.Automation.ErrorRecord] instances, which in Windows PowerShell will somewhat confusingly print them as if they were PowerShell errors - this problem has been fixed in PowerShell Core.
Conversely, however, if you don't convert the type of the elements of the array that is returned to strings, you can examine each element with -is [System.Management.Automation.ErrorRecord] to determine whether it originated from stdout or stderr.
This answer uses this approach to separate stdout lines from stderr lines after merging with 2>&1, so that they can be saved to separate files (which with > in Windows PowerShell requires explicitly stringifying the [System.Management.Automation.ErrorRecord] instances).
Note: When PowerShell communicates with external programs, character-encoding issues come into play: see this answer for details.
[1] Or the underlying .NET API, System.Diagnostics.Process.
For guidance on when Start-Process is or isn't appropriate, see GitHub docs issue #6239

Powershell's Read-Host cmdlet causes hanging when script is ran from Cygwin

I'm trying to run a powershell script from within Cygwin (ultimately will be run over Cygwin SSH), and am finding that user input seems to be ignored. The script displays a menu and then uses Read-Host "Your selection:" to receive the input.
There is a blinking cursor, but the "Your selection" text doesn't appear, and anything I enter seems to just write to the console itself and is ignored by the script.
Does anyone know what could be wrong?
Thanks very much.
I'm guessing Cygwin console does not implement the APIs that the Powershell console's host (System.Management.Automation.Internal.Host.InternalHostUserInterface) depends on. Or doesn't implement them as expected. This will surely be the case if you attempt to run over SSH. MS has documentation on how to write a custom Host UI. So if you want to run PS over SSH seems like there are 4 possibilities:
Write your own PSHost implementation
Find someone else's PSHost implementation
Use your SSH's clients stdin and stdout as a two way pipe and write a REPL that takes input from the pipe (SSH stdin) and sends output to the pipe (SSH stdout). Unless you implement it yourself, this option means you lose line editing, history, tab completion, etc.
Not sure if this one will work, but it would might be the least amount of code to implement if it does. Create a child PS process. Redirect the child's stdout, stdin to the parent PS process. All input you get from SSH stdin you write to child PS's stdin and all output you read from child's stdout you write to SSH stdout. You'll probably want to use asynchronous I/O for the reads on SSH stdin and child's stdout, to prevent hangs (if script is waiting on a read from child's stdout, but the child PS has no more output, then the script is hung). In effect the SSH user is controlling the child PS process and the parent PS process is just the glue joining the two together.

How to force invoked command's STDOUT into invoking cmd.exe's STDOUT?

Context: Oracle Enterprise Manager has a feature to "execute host command." If into that feature I enter "dir c:\temp" then the output window echos the command and then shows a directory listing. If into that feature I enter "powershell dir c:\temp" the output window shows only the echo'd command. No directory listing. If on the target machine I enter those two commands in both cases I get the echo'd command followed by a directory listing.
I hypothesize that what I see in the cmd.exe window on the client blends two stdout streams: one from the cmd.exe itself and one from the invoked process (powershell dir c:\temp). The Oracle thing seems to recognize only the cmd.exe's stdout.
Is there some way I can force the stdout from the invoked process to be in the cmd.exe's stdout stream so that Oracle will recognize it and the thing I am trying to build will work?
I don't think you can directly pipe the output from one program back into STDOUT of a parent cmd.exe - assuming that is what Oracle is doing at some level.
That being said, you could try something clever like the following:
cmd /c "powershell -Command ""& echo Hello" > %TEMP%\a.txt & TYPE %TEMP\a.txt
Basically this is capturing the output from PowerShell, placing it in a temporary file, then dumping that file back onto STDOUT in cmd.exe. A nice touch would be cleaning up the temp file with a & DEL %TEMP%\a.txt on the end of the command.
You will probably need to toy around with the command line to account for any quirks in how Oracle is passing things along - my guess is that it is invoking cmd.exe /c directly so you can probably leave that part off.