Use Tee-Object with redirecting - powershell

I want to run this command and redirect all the output to Windows as well as a log file.
powershell "C:\backup\backup.bat *>&1 | tee log.txt"
so when I run the command I can see the output and also save it in a file, but I am getting this error:
Ampersand not allowed. The & operator is reserved for future use; use "&" to pa
ss ampersand as a string.
At line:1 char:25
+ C:\backup\backup.bat *>& <<<< 1 | tee log.txt
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordEx
ception
+ FullyQualifiedErrorId : AmpersandNotAllowed

To get the output of .bat file execution to the console as well as to file, use:
powershell "& 'C:\backup\backup.bat' *>&1 | Tee-Object -FilePath 'log.txt'"
There's a good post, PowerShell and external commands done right, which explains how to start external command. After that simply apply redirection as in the article you linked.

Redirection of streams other than Success and Error (AKA STDOUT and STDERR) isn't supported prior to PowerShell v3, as #CB. mentioned in comments. In PowerShell v2 you can only merge the Error stream:
powershell "C:\backup\backup.bat 2>&1 | tee log.txt"

Related

Pipe into executable through .lnk file

I have an executable at C:\Very\Long\Path\StreamToClipboard.exe
The file C:\InPath\StreamToClipboard.lnk points to that executable.
The directory C:\InPath is in my PATH variable, and .lnk is in my PATHEXT variable.
In regular cmd, I can execute any of these commands:
echo Hello | C:\Very\Long\Path\StreamToClipboard.exe
echo Hello | C:\InPath\StreamToClipboard.lnk
echo Hello | StreamToClipboard.lnk
echo Hello | StreamToClipboard
and the the executable is started, the text "Hello" is correctly piped into that process.
In PowerShell, I can execute echo Hello | C:\Very\Long\Path\StreamToClipboard.exe and it works too. But all of the other commands don't work:
Fehler beim Ausführen des Programms "StreamToClipboard.lnk": Die angegebene ausführbare Datei ist keine gültige
Anwendung für diese Betriebssystemplattform.In Zeile:1 Zeichen:12
+ echo foo | C:\InPath\StreamToClipboard.lnk
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~.
In Zeile:1 Zeichen:1
+ echo foo | C:\InPath\StreamToClipboard.lnk
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (:) [], ApplicationFailedException
+ FullyQualifiedErrorId : NativeCommandFailed
(and respective paths)
Which roughly translates to Error while executing "StreamToClipboard.lnk": The specified executable file is not a valid application for this operating system plattform.
Note that echo Hello | "C:\Very\Long\Path\StreamToClipboard.exe" also does not work, with a different error message:
In Zeile:1 Zeichen:14
+ ... cho Hello | "C:\Very\Long\Path\StreamToClipboard.exe"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Ausdrücke sind nur als erstes Element einer Pipeline zulässig.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ExpressionsMustBeFirstInPipeline
Roughly translates to Expressions are only valid as the first element in a pipeline.
If I, instead of creating the .lnk file, copy the entire executable to C:\InPath\StreamToClipboard.exe, then these commands:
echo Hello | C:\InPath\StreamToClipboard.exe
echo Hello | StreamToClipboard.exe
echo Hello | StreamToClipboard
work fine.
How can I get PowerShell to accept echo Hello | StreamToClipboard (where it's an .lnk file), or at least echo Hello | StreamToClipboard.lnk?
Unlike cmd.exe, PowerShell does not support invoking shortcut files (.lnk) like console applications; instead:
A new console window opens, asynchronously.
The process in the new console window does NOT receive stdin input (via the pipeline):
If a command is used - such as echo hello, using the built-in echo alias for PowerShell's (rarely needed) Write-Output cmdlet - the following error occurs, as of Windows PowerShell v5.1 / PowerShell (Core) 7.2.4:
Cannot run a document in the middle of a pipeline
If an expression - such as 'hello' - is used, no error occurs, but the input is effectively ignored.
In other words: PowerShell considers .lnk files documents, not executables, and defers to Windows (GUI) shell for opening them;[1] in effect, invoking a .lnk file is like passing it to Invoke-Item or Start-Process; adding -Wait to the latter makes the invocation synchronous, but still runs in a separate window and doesn't support stdin input; attempting to use -NoNewWindow and/or -RedirectStandardInput (to provide stdin input via a file) results in an error, similar to the one you saw:[2]
This command cannot be run due to the error: %1 is not a valid Win32 application.
Workarounds:
Call the target .exe file directly in your pipeline, which - if your path is quoted and/or contains variable references or expressions - requires use of &, the call operator:
# & is required, because the executable path is quoted.
# Note: 'Hello' is the PowerShell-idiomatic equivalent of
# echo Hello
'Hello' | & "C:\Very\Long\Path\StreamToClipboard.exe"
Note: For simplicity, you may choose to always use & when invoking external programs, but it is only required in the cases mentioned above, discussed in more detail in this answer.
Invoke the shortcut (.lnk) file via cmd /c:
'Hello' | cmd /c C:\InPath\StreamToClipboard.lnk
Note that, in both workarounds, character encoding issues may arise, given that data is being sent to an external program:
The $OutputEncoding preference variable controls what encoding is used to send data to an external program.
The encoding stored in [Console]::OutputEncoding determines how PowerShell decodes data received from external programs.
See this answer for more information.
[1] Note that (temporarily) appending ";.LNK" to the value of $env:PATHEXT, the environment variable that lists all filename extensions that belong to executables, does not help.
[2] The reason is that these parameters cause Start-Process to switch from the ShellExecute WinAPI function to CreateProcess, and the latter only works with actual executables.

Get the exact command error output displayed in console into a Powershell variable

I'm writing some scripts to backup registry and I encountered 2 small issues. The 1st one is that I can't intercept the error output of the reg export command.
Example:
reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg
Writes this to console because the key is not available:
ERROR: The system was unable to find the specified registry key or value.
I would love to get this exact string.
Assigning the command to a variable results in an empty string, even with Out-String:
$regerror = reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg | Out-String
The closest solution I've found is this kind of redirection:
$regerror = & reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg 2>&1
But that displays:
reg.exe : ERROR: The system was unable to find the specified registry key or value.
At line:1 char:9
+ $test = & reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (ERROR: The syst...y key or value.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
I'm wondering if there's a simple way to get the mentioned exact output without attempting to process the text above.
Simply convert the stderr lines to strings:
$regerror =
reg export HKEY_CURRENT_USER\Software\OpenVPN-GUI test.reg 2>&1 |
ForEach-Object ToString
While stderr are are strings to begin with, PowerShell wraps them in [System.Management.Automation.ErrorRecord] instances when you use a 2> redirection, which is what causes them to render like PowerShell errors when printed to the display.
(In PowerShell (Core) 7+, they no longer render as such, but they're still wrapped in [System.Management.Automation.ErrorRecord] instances).

Why a powershell command is valid in the terminal but invalid from lnk file?

For example, this command works perfectly from a PowerShell prompt :
powershell.exe -NoExit -c Set-Variable -Name "CA_HOME" -Value "$(Get-Location).Path\intermed-ca"
but fails with an error if used from an lnk file.
Set-Variable : A positional parameter cannot be found that accepts argument '\intermed-ca'.
At line:1 char:1
+ Set-Variable -Name CA_HOME -Value $(Get-Location).Path'\intermed-ca'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Set-Variable], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.SetVariableCommand
Why ?
The error you supplied is not from that particular command. Notice PowerShell is showing us the command it is erroring on.
That particular error is simply a syntax error; that is not how you concatenate strings. Running just $(Get-Location).Path'\intermed-ca' will give you an error: "Unexpected token ''\intermed-ca'' in expression or statement."
Going back to the command you posted at the top of the OP, that is proper syntax, but won't be the Path you're looking for because the logic is flawed. So, what you posted as your command will not generate an error, but will return a path that likely doesn't exist. Assuming your working directory is your user profile, you will get something like this back for $CA_HOME: C:\Users\Zulgrib.Path\intermed-ca. The reason is that you're basically doing this:
$path = (Get-Location).Path
'{0}.Path\intermed-ca' -f $path
The .Path is part of the string, not the string execution. That's why you should add parenthesis to ensure that this property is returned as part of the string execution:
powershell.exe -NoExit -c Set-Variable -Name "CA_HOME" -Value "$((Get-Location).Path)\intermed-ca"
While this is fine and will work without issues, quote translations can be tricky from the command line. So, this really isn't a perfect solution for all scenarios. For better compatibility, I would get in the habit of wrapping the whole command in double quotes and keep single quotes inside the command:
powershell.exe -NoExit -c "Set-Variable -Name 'CA_HOME' -Value ('{0}\intermed-ca' -f (Get-Location).Path)"
For the best compatibility, consider base64 encoding your command and running it with -EncodedCommand. See the very bottom of powershell.exe /? for an example. Be weary that some situations will run into issues with overly long CLI commands.
Note: '{0}\intermed-ca' -f (Get-Location).Path and '{0}\intermed-ca' -f (Get-Location) will give the same result. When PowerShell knows it's injecting a PSOobject into a string, it'll give you the string form of what you likely want.

Passing Powershell from Cmd with variable evaluation

I want to use the following command
powershell -command "get-content %CONFIG_FILE% | %{$_ -replace \"APP_PATH=.+\",\"APP_PATH=%APP_DIR%\"}"
but with the evaluation of the %CONFIG_FILE% and the %APP_DIR% which are defined in the batch script using
set CONFIG_FILE=C:\B0_GW.cfg
set APP_DIR=C:\dbg\kernel
when i do so, i currently get the following issue:
The string is missing the terminator: ".
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
any ideas?
aschipfl has provided the crucial pointer in a comment on the question:
From within a batch file[1], you must escape % characters that you want to pass through to the target application as %%.
Otherwise, cmd interprets % as the start or end of an environment-variable reference (which in part happens by design in your command; e.g., %CONFIG_FILE%).
(You've already correctly escaped embedded " chars. in your command as \" for PowerShell).
Specifically, the % in the %{...} part of your command needs escaping (% is PowerShell's built-in alias for the ForEach-Object cmdlet):
powershell -command "get-content %CONFIG_FILE% | %% {$_ -replace \"APP_PATH=.+\",\"APP_PATH=%APP_DIR%\"}"
Alternatively, simply use the cmdlet's actual name, ForEach-Object, which obviates the need for escaping:
powershell -command "get-content %CONFIG_FILE% | ForEach-Object {$_ -replace \"APP_PATH=.+\",\"APP_PATH=%APP_DIR%\"}"
[1] Sadly, the behavior at cmd.exe's command prompt (in an interactive session) differs - see this answer.

Environment variable, PowerShell session vs. started from CMD

I am trying to run a PowerShell command from within a command prompt window (run as Administrator), but it fails. Whereas when I run the same command from within a PowerShell window it runs fine.
Here is the command without error in a PowerShell window:
Powershell [Environment]::SetEnvironmentVariable("HostIPv4", "192.168.255.14:", "Machine")
In the command prompt window it fails:
C:\test>powershell [Environment]::SetEnvironmentVariable("HostIPv4", "192.168.255.14:", "Machine")
At line:1 char:39
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~
Missing ')' in method call.
At line:1 char:39
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~~~~~~~~
Unexpected token 'HostIPv4' in expression or statement.
At line:1 char:47
+ [Environment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Mach ...
+ ~
Missing argument in parameter list.
At line:1 char:73
+ ... ironment]::SetEnvironmentVariable(HostIPv4, 192.168.255.14:, Machine)
+ ~
Unexpected token ')' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : MissingEndParenthesisInMethodCall
What could be the problem?
PowerShell's command-line parsing removes the double quotes, try using single quotes:
powershell [Environment]::SetEnvironmentVariable('HostIPv4', '192.168.255.14:', 'Machine')
Also note that you have to reopen an new process window to see the results (this is a known behavior for the command shell, see also: C# set environment variable)
iRon's helpful answer explains the problem and works, but I suggest adopting a generally more robust approach to invoking PowerShell commands from cmd.exe:
Use -Command explicitly, because in PSv6 the default will change to -File, expecting a script filename rather than a command.
Use -NoProfile, to avoid unnecessary loading of the PowerShell profiles and for a more predictable execution environment.
Double-quote your entire PowerShell command to protect it from potentially unwanted up-front interpretation by cmd.exe.
powershell -NoProfile -Command "[Environment]::SetEnvironmentVariable('HostIPv4', '192.168.255.14:', 'Machine')"
The use of ' instead of " inside the command string is an easy way to avoid having to escape embedded " characters, which works fine here, but in case you do need embedded " (for interpolating strings), escape them either as \" (sic) or """ (sic).