PowerShell 2.0 can't redirect stdout - windows

Sometimes, the answer is "no". This turns out not to be a question, but more of a cri de coeur (OK, just complaining).
The Team Foundation Server 2010 PowerShell snap-in exports a cmdlet called Get-TfsPendingChange, which does what it sounds like.
If there are no pending changes, Get-TfsPendingChange echoes "There are no pending changes." on standard output. I never asked it to. The following attempts at suppressing that output all fail:
$pcs = $(Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r ) | out-null;
$($pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r ) | out-null;
$pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r | out-null;
I'm not getting errors, but the text output is not redirected. What IS redirected, is the normal successful output of the cmdlet, in the case where there are pending changes for it to return. The ONLY output you can suppress is the useful output. If there are zero pending changes, you get text garbage via stdout. If there are any actual pending changes, that stream (of objects, not text) can be redirected to the null device, but that's worse than useless.
So it appears that text on stdout is a side effect from this cmdlet, not the cmdlet's output, and it cannot be redirected in PowerShell. The PowerShell redirect operators > and |, without the fileno specifiers (2 in PS2, 2, 3, 4, 5, and * in PS3/PS4), work on the object stream, which is not stdout. This would be fine if stdout had been replaced by this new object stream concept (or if some genius on the TFS team hadn't decided that cmdlets should spew random text noise in all directions as a side effect).
The garbage output definitely is on stdout, not stderr. I can redirect the standard output of the whole script to nul when I run it from cmd.exe, and that works as expected, because duh:
c:\>powershell .\tfstest.ps1 > nul
However, I do not want to suppress all the output from the entire script. Just the output from one snapin cmdlet. It's not the end of the world, but it's irritating to have random stuff that I call echoing garbage I have no control over. The script otherwise works correctly (see UPDATE).
One workaround would be to prefix everything I want to echo on stdout with some arbitrary string (say "KLUDGEPREFIX"), and run the whole mess from a batch file which pipes the PowerShell script's output through find /v KLUDGEPREFIX and then through a PowerShell fragment that strips off the prefix from each line.
Instead, I'm going to write the rest of the script around the problem so it looks like what they get is what I intended, because, in fact, I was going to tell them they didn't have any pending changes anyhow. It's been a valuable exercise, however, in terms of getting my head inside PowerShell.
UPDATE: This script turned out to be fragile, unreliable, glacially slow, and impossible to run on any computer but the one where I originally wrote it.
I replaced it with a Perl script that took 1/10 the time to write, executed 10 times as fast, and does the same task better without any foolishness. PS and the TFS PS snap-in are great ideas but they both need a lot of work before they'll be ready for release.

I'm not familiar with Team Foundation Server, but most likely the cmdlet writes to the host rather than one of the output streams. Output to the host cannot be redirected in PowerShell, but if you run a PowerShell script in CMD the host output goes to STDOUT (as does the success output stream) and can be redirected there.
Demonstration:
PS C:\> cat .\test.ps1
Write-Host 'foo'
PS C:\> .\test.ps1 | Out-Null
foo
PS C:\> .\test.ps1 >$null
foo
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1
foo
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1 `>nul
PS C:\> & cmd.exe /c powershell.exe -File .\test.ps1 >$null
PS C:\> _

if you add 2 in front of the > it will only redirect error. likewise with 1 for non errors (stdout). So if you want all stdout, but not stderr to go away you can type
$pcs = Get-TfsPendingChange -Server $server "$/foo/bar" -User $env:USERNAME -r 1> $null
$null can be used as an equivalent to /dev/null. your code probably creates the file "nul" on disk.

Related

Is there way to turn off `ECHO` user input in `powershell`

Linux has have stty -echo which make off typing so it is not echoed back to user.
So basically what you're typing is not seen on the shell.
Is there way to do the same in powershell?
As it turns out, your intent was to suppress echoing of commands provided via stdin to the PowerShell CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell (Core) 7+):
To do so, use -Command - (-c -), which - like -File - (-f -, the default behavior) - accepts (independent) commands one by one via stdin, but - unlike -File - - neither prints a prompt string nor echoes the command provided:
With -Command -:
# OK: Just the piped command's *output* prints.
PS> 'Get-Date' | powershell.exe -Command -
Friday, September 3, 2021 10:08:07 AM
With -File -:
# Pseudo-interactive behavior:
# The piped command's output is:
# * *preceded by* the prompt string as well as the input command.
# * *followed by* another rendering of the prompt string.
PS> 'Get-Date' | powershell.exe -File -
PS C:\Users\jdoe> Get-Date
Friday, September 3, 2021 10:09:44 AM
PS C:\Users\jdoe>
Note: In the examples above, a single command is provided via stdin (via the pipeline), and since no more stdin input is provided, the PowerShell process exits. However, in scenarios where you keep stdin open, you'll be able to feed commands - one by one - to the PowerShell process indefinitely.
Caveat: Both -File - and -Command - exhibit problematic, pseudo-interactive behavior; notably, a command spanning multiple lines must be terminated with two newlines; see GitHub issue #3223 for details.
Note:
Providing neither -Command - nor -File - is essentially the same as -File -, except that a copyright message is also printed on startup (which can be suppressed separately with -NoLogo); in short: ... | powershell -NoLogo is the same as
... | powershell -File -
To provide a predictable execution environment and to avoid extra overhead, consider preceding -File / -Command with -NoProfile, which bypasses the loading of PowerShell's profile files.
As for a general stty -echo equivalent (the POSIX-mandated stty utility is available on Unix-like platforms, not on Windows):
This commands operates on the terminal, not the shell that happens to run in it.
A POSIX-compatible shell such as bash does not try to reset the terminal to its previous state after issuing this command, so input typed by the user indeed remains invisible through the end of the session or until the terminal is reset.
By contrast, PowerShell (the cross-platform v7+ edition that runs on Unix-like platforms) does reset the terminal after running each command, so running stty -echo is, in effect, a no-op.
In other words: you cannot achieve the same effect in PowerShell, on any platform (at least as of PowerShell 7.2)
On a loosely related note (prompted by a misreading of the question's intent), the following discusses:
How to hide or mask interactive user input solicited via the Read-Host cmdlet:
There is no built-in PowerShell feature (as of v7.2) that hides what the user types, but if the intent is simply to mask user input, so as to hide sensitive information being typed, such as a password, you have two options - both of which mask each character typed printing * instead:
Use Read-Host -AsSecureString:
$$valueEnteredSecure = Read-Host -AsSecureString -Prompt 'Enter your password'
Note that this outputs a System.Security.SecureString instance (which provides limited security on Windows, and virtually none on Unix-like platforms), which you can convert back to a regular string as follows:
$valueEntered = [System.Net.NetworkCredential]::new('', $valueEnteredSecure).Password
In PowerShell (Core) 7.0+, you can alternatively use (though note the simpler 7.1+ solution further below):
$valueEntered = ConvertFrom-SecureString $valueEnteredSecure -AsPlainText
PowerShell (Core) 7.1+ now supports a -MaskInput parameter that exhibits the same UI behavior, but directly returns a regular string:
# PowerShell 7.1+
$valueEntered = Read-Host -MaskInput -Prompt 'Enter your password'
If truly suppressing the display of all characters typed by the user is a must, you'll have to create a custom solution that uses $host.ui.RawUI.ReadKey('NoEcho') in a loop:
Here's a simple implementation, which you can call as
$valueEntered = Read-HostSilent -Prompt 'Enter your password', for instance:
function Read-HostSilent {
param([string] $Prompt)
if ($Prompt) { Write-Host -NoNewline "${Prompt}: " }
$entered = $null; $done = $false
do {
$key = $host.ui.RawUI.ReadKey('NoEcho')
switch ($key.VirtualKeyCode) {
# Backspace
8 { if ($entered.Length -gt 1) { $entered = $entered.Substring(0, $entered.Length-1) } }
# Enter
13 { $done = $true; break }
default {
if ($key.Character) { # printable?
$entered += $key.Character
}
}
}
} while (-not $done)
$entered # output
}

what is `powershell -c` or any execution parameters(e.g. powershell -nop -NonI)?

Apologize for the noob question but I did hours of research but still so confused...
The problem:
In powershell we can write this:
$i = 'hello'
echo $i # hello
easy. But:
powershell -c "$j = 'hello'; echo $j"
won't work and it throws error at our face.
The question: what is the error, and what is the correct grammar to use powershell -NoP -NonI -c "//..."? I see quite a few scripts written in this format. I even wonder if it is a linux thing...? but we are talking about powershell right?...
Any help would be appreciated.
It depends on where are you executing the command.
Inside cmd.exe this will work, because the commands don't render special meaning to cmd. But in powershell it will fail because of the special characters, use powershell -c '$j = ''hello''; echo $j' instead.
Also -c,-NoP etc. are parameters of powershell.exe:
PowerShell[.exe] [-PSConsoleFile <file> | -Version <version>]
[-NoLogo] [-NoExit] [-Sta] [-Mta] [-NoProfile] [-NonInteractive]
[-InputFormat {Text | XML}] [-OutputFormat {Text | XML}]
[-WindowStyle <style>] [-EncodedCommand <Base64EncodedCommand>]
[-ConfigurationName <string>]
[-File <filePath> <args>] [-ExecutionPolicy <ExecutionPolicy>]
[-Command { - | <script-block> [-args <arg-array>]
| <string> [<CommandParameters>] } ]
PowerShell[.exe] -Help | -? | /?
-PSConsoleFile
Loads the specified Windows PowerShell console file. To create a console
file, use Export-Console in Windows PowerShell.
-Version
Starts the specified version of Windows PowerShell.
Enter a version number with the parameter, such as "-version 2.0".
-NoLogo
Hides the copyright banner at startup.
-NoExit
Does not exit after running startup commands.
-Sta
Starts the shell using a single-threaded apartment.
Single-threaded apartment (STA) is the default.
-Mta
Start the shell using a multithreaded apartment.
-NoProfile
Does not load the Windows PowerShell profile.
-NonInteractive
Does not present an interactive prompt to the user.
-InputFormat
Describes the format of data sent to Windows PowerShell. Valid values are
"Text" (text strings) or "XML" (serialized CLIXML format).
-OutputFormat
Determines how output from Windows PowerShell is formatted. Valid values
are "Text" (text strings) or "XML" (serialized CLIXML format).
-WindowStyle
Sets the window style to Normal, Minimized, Maximized or Hidden.
-EncodedCommand
Accepts a base-64-encoded string version of a command. Use this parameter
to submit commands to Windows PowerShell that require complex quotation
marks or curly braces.
-ConfigurationName
Specifies a configuration endpoint in which Windows PowerShell is run.
This can be any endpoint registered on the local machine including the
default Windows PowerShell remoting endpoints or a custom endpoint having
specific user role capabilities.
-File
Runs the specified script in the local scope ("dot-sourced"), so that the
functions and variables that the script creates are available in the
current session. Enter the script file path and any parameters.
File must be the last parameter in the command, because all characters
typed after the File parameter name are interpreted
as the script file path followed by the script parameters.
-ExecutionPolicy
Sets the default execution policy for the current session and saves it
in the $env:PSExecutionPolicyPreference environment variable.
This parameter does not change the Windows PowerShell execution policy
that is set in the registry.
-Command
Executes the specified commands (and any parameters) as though they were
typed at the Windows PowerShell command prompt, and then exits, unless
NoExit is specified. The value of Command can be "-", a string. or a
script block.
If the value of Command is "-", the command text is read from standard
input.
If the value of Command is a script block, the script block must be enclosed
in braces ({}). You can specify a script block only when running PowerShell.exe
in Windows PowerShell. The results of the script block are returned to the
parent shell as deserialized XML objects, not live objects.
If the value of Command is a string, Command must be the last parameter
in the command , because any characters typed after the command are
interpreted as the command arguments.
To write a string that runs a Windows PowerShell command, use the format:
"& {<command>}"
where the quotation marks indicate a string and the invoke operator (&)
causes the command to be executed.
-Help, -?, /?
Shows this message. If you are typing a PowerShell.exe command in Windows
PowerShell, prepend the command parameters with a hyphen (-), not a forward
slash (/). You can use either a hyphen or forward slash in Cmd.exe.
EXAMPLES
PowerShell -PSConsoleFile SqlSnapIn.Psc1
PowerShell -version 2.0 -NoLogo -InputFormat text -OutputFormat XML
PowerShell -ConfigurationName AdminRoles
PowerShell -Command {Get-EventLog -LogName security}
PowerShell -Command "& {Get-EventLog -LogName security}"
# 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
-NoP is no profile means not load powershell profile.
-NonI is to run non-interactive session.
-c execute command/scriptblock and exit.

Powershell script strange behaviour when invoked from CMD/CLI

This scripts works fine when executed from Powershell console...
but does not work when executed with Powershell.exe from CMD.exe...
(powershell.exe -file script.ps1, using Powershell 5.1.17763.771)
# display Windows Shell Folder propertes
$App = New-Object -ComObject Shell.Application;
$AppNS = $App.NameSpace( "c:\windows" );
$AppNS.Self.InvokeVerb( "Properties" );
I tested other GUI objects (Winforms & WPF)
and they work fine...
?any ideas...
The problem is that the in-process COM object you're creating goes out of scope when the calling process exits, which in your case, when called from cmd.exe via PowerShell's CLI, means that the window typically never even gets a chance to display or is automatically closed after a very brief appearance.
In an interactive PowerShell session, the process lives on after exiting the script - that's why your code works there.
When you invoke a script via via PowerShell's CLI (powershell.exe for Windows PowerShell, pwsh for PowerShell Core, without the -NoExit switch to keep the process alive indefinitely), the PowerShell process exits when the script terminates.
Use of -NoExit would be a stopgap at best, because it would keep the PowerShell process around indefinitely, even though you presumably want it to live only for as long as the Properties dialog window is open - whenever the user chooses to close it.
Therefore, you need to synchronously wait for (a) the Properties dialog window to open and then (b) wait for it close before exiting the script.
You can do this with the help of the .NET UI Automation library as follows; note that the code uses PowerShell v5+ syntax:
using namespace System.Windows.Automation
# Load the UI Automation client assemblies.
# Requires Windows PowerShell or PowerShell Core v7+ (on Windows only).
Add-Type -AssemblyName UIAutomationClient; Add-Type -AssemblyName UIAutomationTypes
# Initiate display of the Windows folder's Properties dialog.
$App = New-Object -ComObject Shell.Application
$AppNS = $App.NameSpace('c:\windows')
$AppNS.Self.InvokeVerb('Properties')
# Comment out this line to suppress the verbose messages.
$VerbosePreference = 'Continue'
Write-Verbose 'Wating for the window''s creation...'
do {
# Search among the current process' top-level windows for a winow
# with class name '#32770', which is what the Properties dialog windows
# use (don't know why, but it has been stable over time).
$w = [AutomationElement]::RootElement.FindFirst([TreeScope]::Children,
[AndCondition]::new(
[PropertyCondition]::new([AutomationElement]::ClassNameProperty, '#32770'),
[PropertyCondition]::new([AutomationElement]::ProcessIdProperty, $PID)
)
)
Start-Sleep -Milliseconds 100
} while (-not $w)
Write-Verbose 'Window has appeared, waiting for it to close...'
while ($w.Current.ProcessId) {
Start-Sleep -Milliseconds 100
}
Write-Verbose 'Window is now closed, moving on.'
# At this point, if the script was invoked via PowerShell's CLI (powershell.exe -file ...)
# the PowerShell process terminates.
Now, invoking your PowerShell script as follows from your batch file will pop up the Properties dialog and wait for it to close before continuing:
#echo off
:: # ... your batch file
:: # Pop up the Properties dialog and *wait for it to close*.
powershell.exe -file script.ps1
:: # ...
If, by contrast, you simply want to launch the Properties dialog while continuing to run your batch file (be sure to disable the verbose messages first):
:: # Only *initiate* display of the Properties dialog and *continue execution*.
start /B powershell.exe -file script.ps1
Seems like it has to wait for the graphics to finish. "get-childitem | out-gridview" does a similar thing. Or add "sleep 120" to the end of the script, or find some other way to wait. Killing the script kills the window.
powershell -noexit .\explorer.ps1

Calling a command line application outtputing to stderr with piping

I'm looking to get both std-err and std-out streams from a binary I have called by powershell piped to a powershell cmdlet. I cant seem to get the syntax or find the right command.
I have a little Write-Smartly cmdlet that I want to process line-by-line of combined standard-error and standard output. It is itself a wrapper on Write-Host that adds some indenting.
My first attempt, I want to:
use -ErrorAction to indicate to powershell not to treat standard-error messages as exceptions
use 2>&1 to combine standard-error and standard-output streams into a pipelineable output
use ... | MyCmdlet to format the combined output & error streams to their ultimate output
giving me:
& $myApp $args --% -ErrorAction 'SilentlyContinue' 2>&1 | Write-Smartly
Unfortunatly this ended up passing -ErrorAction SilentlyContinue and 2>&1 into my application as arguments. Standard-error did show up as regular output, but it was written directly to host, rather than piped to WriteSmartly.
So I made a few modifications to try to correct this:
$ErrorActionPreference = 'SilentlyContinue'
&{ & $myApp $args } 2>&1 | Write-Smartly
This simply causes my error output to disappear.
I also played around with Start-Process, but it seems too heavyweight to allow me to get things out of it with a pipe. I think that Invoke-Command or Invoke-Expression might be helpful here but I'm not sure what their use cases are.
How can I capture both standard-error and standard-output as a pipelined value in powershell?

Powershell equivalent of Perl's $CHILD_ERROR

I essentially require a functionality in Powershell that executes the given string (it can be a CMD/Powershell command, a perl/python/powershell with arguments or an exe with arguments, etc) captures its exit value.
In perl I would pass the string to 'system()' and use the '$CHILD_ERROR' perlval and shift it to access the exit code.
In powershell I am clueless.
I tried using Invoke-Expression, but even if the expression passed to Invoke-Expression fails, the Invoke-Expression call itself will have succeeded.
You can use $LASTEXITCODE to get the exit code from an external program or the Boolean $? to check if the last operation succeeded or failed. Run Get-Help about_Automatic_Variables -ShowWindow from a PowerShell console to see more details.
You may want to check out the & (call) command as an alternative to Invoke-Expression when running external programs. Run Get-Help about_Automatic_Variables -ShowWindow from a PowerShell console for details.
Also remember you may be able to just call the external program without using one of the commands above. See the example below:
param($Hostname="127.0.0.1", $Tries=1, $Wait=1000)
$output = ping.exe $Hostname -n $Tries -w $Wait # captures anything written to stdout
$output|? {$_ -match 'Request timed out'}|Write-Warning
$LASTEXITCODE # returns the exit code from ping.exe
You can copy it to a test.ps1 file and run it from a PowerShell console window (.\test.ps1 8.8.8.8 for instance) to see how it works.

Resources