I have an app that prints text to stderr, so to save to a file, I do this:
.\NGPQUERY.exe -spoof -stages -diag 2> c:\foo.txt
None of the text is special characters, other than crlf at the end of lines.
In DOS, the output is fine.
In powershell the output is almost fine.
The first line of text is this:
RTE Routings for BRI to LED - 00:
I get this error message at the top of the output:
NGPQUERY.exe : RTE Routings for BRI to LED - 00:
At line:1 char:15
+ .\ngpquery.exe <<<< -spoof -stages -diag 2> e:\foo
+ CategoryInfo : NotSpecified: (RTE Routings for BRI to LED - 00: :String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Then throughout the output, line feeds are added at seemingly random locations. So my question is how do I get rid of the error and the random line feeds.
Also powershell outputs file that is twice as large as the dos file, I'm guessing its unicode. So I would like to know the best way get an ansi output too.
If you only need STDERR, this should be enough:
$oPsi = New-Object -TypeName System.Diagnostics.ProcessStartInfo
$oPsi.FileName = "NGPQUERY.exe"
$cArgs = #("-spoof", "-stages", "-diag")
$oPsi.Arguments = $cArgs
$oPsi.CreateNoWindow = $true
$oPsi.UseShellExecute = $false
$oPsi.RedirectStandardError = $true
$oProcess = New-Object -TypeName System.Diagnostics.Process
$oProcess.StartInfo = $oPsi
[Void]$oProcess.Start()
$sStdErr = $oProcess.StandardError.ReadToEnd()
[Void]$oProcess.WaitForExit()
$sStdErr | Out-File -Encoding "ASCII" -FilePath "C:\foo.txt"
This is an old question, but I got here because I had a similar problem and found the easiest solution to be to call cmd.exe and surround the command in quotes, to prevent Powershell from interpreting it. So in the above case that would be:
cmd.exe /c ".\NGPQUERY.exe -spoof -stages -diag 2> c:\foo.txt"
Related
I have been doing a lot of reading on invoke-expression (also known as iex) and I'm having trouble getting it to work for me.
My understanding is, it will run any powershell code you give to it. However, when I run my tests on it, it does not run the code.
Example:
## testcode.ps1
$myvar = "i am here"
if ($myvar -ne $null) {
"($myvar) variable is Full"
} else {
"($myvar) variable is Empty"
}
Now, if I cat(gc) this file and I pass it to iex, it outputs a bunch of errors. Same thing happens when I save the code into a variable and then feed the variable to iex. Neither works.
Despite the fact that I've tried numerous examples, I feel there's something minor I'm doing wrong that I'm hoping someone can point out for me.
I'm new to Windows scripting, so please bear with me. These are the results of the tests I performed:
First Test:
PS C:\Users\J> gc C:\Users\J\testcode.ps1 | iex
Invoke-Expression : Cannot bind argument to parameter 'Command' because it is an empty string.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : InvalidData: (:PSObject) [Invoke-Expression], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.InvokeExpressionCommand
iex : At line:1 char:23
+ if ($myvar -ne $null) {
+ ~
Missing closing '}' in statement block or type definition.
At line:1 char:31
+ cat C:\Users\J\testcode.ps1 | iex
+ ~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : MissingEndCurlyBrace,Microsoft.PowerShell.Commands.InvokeExpressionCommand
Second Test:
PS C:\Users\J> $scriptBlock = gc C:\Users\J\testcode.ps1
PS C:\Users\J>
PS C:\Users\J> iex -Command "$scriptBlock"
iex : At line:1 char:23
+ $myvar = "i am here" if ($myvar -ne $null) { "($myvar) variable ...
+ ~~
Unexpected token 'if' in expression or statement.
At line:1 char:1
+ iex -Command "$scriptBlock"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ParserError: (:) [Invoke-Expression], ParseException
+ FullyQualifiedErrorId : UnexpectedToken,Microsoft.PowerShell.Commands.InvokeExpressionCommand
PS C:\Users\J>
I'm aware that I can just run the file containing the code. However, I need help figuring out how iex works and what it is I'm doing wrong.
Please kindly advise.
First things first:
Invoke-Expression should generally be avoided and used only as a last resort, due to its security risks. In short: avoid it, if possible, given that superior alternatives are usually available. If there truly is no alternative, only ever use it on input you either provided yourself or fully trust - see this answer.
For the record: in the case at hand, the superior alternative is to directly invoke the script file:
# Prepend `& `, if the script file path is quoted or references a variable.
C:\Users\J\testcode.ps1
Invoke-Expression (iex) accepts multiple strings via the pipeline, and evaluates each individually, as a self-contained script.
Therefore, you must provide the contents of your script as a whole, as a single string, which is what Get-Content's (gc's) -Raw switch does[1]:
Get-Content -Raw C:\Users\J\testcode.ps1 | Invoke-Expression
Alternatively, pass the script-file contents as an argument:
Invoke-Expression (Get-Content -Raw C:\Users\J\testcode.ps1)
Note that passing the string to evaluate as an argument truly only accepts a single string, so the command would fail without -Raw.
[1] By default, the Get-Content cmdlet reads a file line by line, passing each line through the pipeline as it is being read.
$myvar = "I'm Here"
#Using Invoke-Expression - Accepts a STRING as Input
$SBCode = 'if ($Null -ne $myvar) {"($myvar) variable is Full"}' +
'else {"`$myvar variable is Empty"}'
Clear-Host
"Before Invoke-Expression `$myvar = $myvar"
$Result = Invoke-Expression $SBCode
"Invoke-Expression Returns: $Result"
#Using Invoke-Command - Accepts Script Block as Input
$SBCode = {
if ($myvar -ne $null) {
"($myvar) variable is Full"
}
else {
"`$myvar variable is Empty"
}
} #End $SBCode Script Block
"Before Invoke-Command `$myvar = $myvar"
$Result = Invoke-Command -ScriptBlock $SBCode
"Invoke-Command Returns: $Result"
Results:
Before Invoke-Expression $myvar = I'm Here
Invoke-Expression Returns: (I'm Here) variable is Full
Before Invoke-Command $myvar = I'm Here
Invoke-Command Returns: (I'm Here) variable is Full
# After changing $MyVar = $Null
Before Invoke-Expression $myvar =
Invoke-Expression Returns: $myvar variable is Empty
Before Invoke-Command $myvar =
Invoke-Command Returns: $myvar variable is Empty
HTH
You can use out-string to convert output into string.
cat C:\Users\J\testcode.ps1 | out-string | Invoke-Expression
I am still quite new to Powershell, but I would like to add my favourite editor into an Alias in Powershell.
I edited the profile.ps1 in C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 which will run automatically when PowerShells starts.
I tried enter New-Alias np notepad.exe which works perfectly everytime I launch PowerShell.
However, I would like to use Sublime Text 3 as my editor. I followed the instructions in this SO page: How can I write a PowerShell alias with arguments in the middle?
The command line I need for Sublime Text is "C:\Program Files\Sublime Text 3\sublime_text.exe" -n [FirstArg]
Which I come out something like this: function sublime { 'C:\Program Files\Sublime Text 3\sublime_text.exe' -n $args }
It does not work and I got the error like this:
At C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1:5 char:72
+ ... lime { 'C:\Program Files\Sublime Text 3\sublime_text.exe' -n $args }
+ ~~
Unexpected token '-n' in expression or statement.
At C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1:5 char:75
+ ... lime { 'C:\Program Files\Sublime Text 3\sublime_text.exe' -n $args }
+ ~~~~~
Unexpected token '$args' in expression or statement.
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnexpectedToken
Any helps would be appreciated. Thanks!
Without being a Sublime user, I suspect this should work:
function Start-Sublime {
param([string]$args)
$limeArgs = [string]::Empty
if ($args -ne $null) {
$limeArgs = $args
}
Start-Process "$env:ProgramFiles\Sublime Text 3\sublime_text.exe" -n $limeArgs
}
Set-Alias lime Start-Sublime
Not the prettiest PowerShell code, but I imagine it will do what you're after. It's a little easier to understand than the cryptic & operator is.
I am trying to create a script to create an ini file via powershell to disable windows UAC.
$functionText = #"`[Options`]
UpdateKey=04/28/2015 12:50:27 AM
WINDOW_LEFT=258
WINDOW_TOP=149
WINDOW_WIDTH=666
WINDOW_HEIGHT=519
WINDOW_MAX=0
BackupDir=C:\Windows\System32
UpdateCheck=1
Language=1033
(App)Sun Java=False
NewVersion=5.05.5176
SkipUAC=1
FinderInclude1=PATH|C:\|*.*|RECURSE
FinderInclude2=PATH|D:\|*.*|RECURSE
FinderIncludeStates=1|1
I see SkipUAC=1
ShowCleanWarning=False
ShowFirefoxCleanWarning=False
WipeFreeSpaceDrives=C:\
RunICS=0
CookiesToSave=*.piriform.com|google.com
"#
New-Item c:\Program Files\Ccleaner\Ccleaner.ini -type file -force -value $functionText
I keep getting Unrecognized token in source text.
At C:\PROGRA~3\BEANYW~1\Scripts\2480_C~1\~SC52F~1.PS1:1 char:17
+ $functionText = <<<< #"[Options]
+ CategoryInfo : ParserError: (:) [], ParseException
+ FullyQualifiedErrorId : UnrecognizedToken
I tried adding the escape character around options to see if that would do it - I think the issue is around the word [options]
If you want to use a here-string, put the #" on a line by itself.
$functionText = #"
[Options]
UpdateKey=04/28/2015 12:50:27 AM
WINDOW_LEFT=258
WINDOW_TOP=149
WINDOW_WIDTH=666
WINDOW_HEIGHT=519
WINDOW_MAX=0
BackupDir=C:\Windows\System32
UpdateCheck=1
Language=1033
(App)Sun Java=False
NewVersion=5.05.5176
SkipUAC=1
FinderInclude1=PATH|C:\|*.*|RECURSE
FinderInclude2=PATH|D:\|*.*|RECURSE
FinderIncludeStates=1|1
I see SkipUAC=1
ShowCleanWarning=False
ShowFirefoxCleanWarning=False
WipeFreeSpaceDrives=C:\
RunICS=0
CookiesToSave=*.piriform.com|google.com
"#
New-Item "C:\Program Files\Ccleaner\Ccleaner.ini" -type file -force -value $functionText
The advantage of a here-string is that you don't have to escape anything inside the string. So if there were single or double quotes it wouldn't matter. As long as the literal string '"#' doesn't exist, on a line by itself, inside the ini file code you're safe.
Read more about here-strings.
Also, as shown in the sample above, you need to put quotes around the file path.
I try to figure how to determine if a command throw with Invoke-Expression fail.
Even the variable $?, $LASTEXITCODE or the -ErrorVariable don't help me.
For example :
PS C:\> $cmd="cat c:\xxx.txt"
Call $cmd with Invoke-Expression
PS C:\> Invoke-Expression $cmd -ErrorVariable err
Get-Content : Cannot find path 'C:\xxx.txt' because it does not exist.
At line:1 char:4
+ cat <<<< c:\xxx.txt
+ CategoryInfo : ObjectNotFound: (C:\xxx.txt:String) [Get-Content], ItemNotFoundExcep
tion
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
The $? is True
PS C:\> $?
True
The $LASTEXITCODE is 0
PS C:\> $LASTEXITCODE
0
And the $err is empty
PS C:\> $err
PS C:\>
The only way I found is to redirect STD_ERR in a file and test if this file is empty
PS C:\> Invoke-Expression $cmd 2>err.txt
PS C:\> cat err.txt
Get-Content : Cannot find path 'C:\xxx.txt' because it does not exist.
At line:1 char:4
+ cat <<<< c:\xxx.txt
+ CategoryInfo : ObjectNotFound: (C:\xxx.txt:String) [Get-Content], ItemNotFoundExcep
tion
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Is it the only and best way to do this ?
I was going crazy trying to make capturing the STDERR stream to a variable work. I finally solved it. There is a quirk in the invoke-expression command that makes the whole 2&>1 redirect fail, but if you omit the 1 it does the right thing.
function runDOScmd($cmd, $cmdargs)
{
# record the current ErrorActionPreference
$ep_restore = $ErrorActionPreference
# set the ErrorActionPreference
$ErrorActionPreference="SilentlyContinue"
# initialize the output vars
$errout = $stdout = ""
# After hours of tweak and run I stumbled on this solution
$null = iex "& $cmd $cmdargs 2>''" -ErrorVariable errout -OutVariable stdout
<# these are two apostrophes after the >
From what I can tell, in order to catch the stderr stream you need to try to redirect it,
the -ErrorVariable param won't get anything unless you do. It seems that powershell
intercepts the redirected stream, but it must be redirected first.
#>
# restore the ErrorActionPreference
$ErrorActionPreference=$ep_restore
# I do this because I am only interested in the message portion
# $errout is actually a full ErrorRecord object
$errrpt = ""
if($errout)
{
$errrpt = $errout[0].Exception
}
# return a 3 member arraylist with the results.
$LASTEXITCODE, $stdout, $errrpt
}
It sounds like you're trying to capture the error output of a native in a variable without also capturing stdout. If capturing stdout was acceptable, you'd use 2>&1.
Redirecting to a file might be the simplest. Using Invoke-Expression for it's -ErrorVariable parameter almost seems like a good idea, but Invoke-Expression has many problems and I usually discourage it.
Another option will look a little cumbersome, but it can be factored into a function. The idea is to merge output streams using 2>&1, but then split them again based on the type of the object. It might look like this:
function Split-Streams
{
param([Parameter(ValueFromPipeline=$true)]$InputObject)
begin
{
$stdOut = #()
$stdErr = #()
}
process
{
if ($InputObject -is [System.Management.Automation.ErrorRecord])
{
# This works well with native commands but maybe not as well
# for other commands that might write non-strings
$stdErr += $InputObject.TargetObject
}
else
{
$stdOut += $InputObject
}
}
end
{
,$stdOut
,$stdErr
}
}
$o, $e = cat.exe c:\xxx.txt 2>&1 | Split-Streams
I have a PowerShell script that writes to the error output. The script can be as simple as the following:
Write-Error 'foo'
Start-Sleep -s 5
Write-Error 'bar'
The script I actually call spawns an external process that takes a while to process and writes to standard error.
Now when I call the script like this:
. myScript.ps1
I get the error message with PowerShell's usual behaviour (i.e. red text and lots of debugging information). As that text has no relation to PowerShell in my actual application, I don't need those debugging information and it only makes the result less readable (and impossible to process).
Is there a way to redirect that output directly into standard output, so that I just get the text?
I tried something like this:
Write-Host ( . myScript.ps1 2>&1 )
But that delays the output until everything is completed.
About the “debugging information”, when I run the script right now, the output looks like this (in red):
C:\path\to\myScript.ps1 : foo
Bei Zeile:1 Zeichen:2
+ . <<<< 'C:\path\to\myScript.ps1'
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,myScript.ps1
C:\path\to\myScript.ps1 : bar
Bei Zeile:1 Zeichen:2
+ . <<<< 'C:\path\to\myScript.ps1'
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,myScript.ps1
When I run the script with Write-Host ( . myScript.ps1 2>&1 ), where the error output is written to the standard output, I get a result like this:
foo bar
That is exactly what I would like the output to be, except that the Write-Host (..) makes the output only appear after the script has terminated, so I cannot receive any information on the progress of said script.
To come actually closer to my actual problem (because it’s hard to explain that with pure PowerShell); I’ve got the following Python script that resembles approximately what the command line program I use does, i.e. it does some processing and prints out the progress to the standard error:
#!/usr/bin/python
import sys, time
sys.stderr.write( 'Progressing... ' )
sys.stderr.flush()
time.sleep( 5 )
sys.stderr.write( 'done.\n' )
sys.stderr.flush()
Now, when I call it with python script.py, it works correctly and prints out the “progressing” line, waits 5 seconds and then prints the “done” to PowerShell (as normal text). The problem is now that I want to run this in a job, like this: Start-Job { python script.py }.
The job gets started correctly, and also works fine in the background, but when I want to check its progress via Receive-Job <id>, I get no output at all at first, and after the script/program finished (i.e. after the 5 seconds), I get the following output (in red again):
Progressing... done.
+ CategoryInfo : NotSpecified: (Progressing... done.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Obviously that is not what I am trying to get. Instead I want to get the actual output that was printed to the standard error, both live (i.e. when it happens in the script) and without that PowerShell related debugging text.
According to the edit section of the question, this should be suitable:
. MyScript.ps1 2>&1 | %{ Write-Host $_ }
It writes just "foo" and "bar" and they appear as soon as they happen.
EDIT
Actually this is even simpler and works fine, too:
. MyScript.ps1 2>&1 | Write-Host
But I keep the original answer. That code allows to process the output ($_) dynamically and do something else (i.e. not just write it to the host).
EDIT 2
External program that writes to STDERR:
using System;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 100; ++i)
{
Console.Error.WriteLine("Step " + i);
System.Threading.Thread.Sleep(2000);
}
}
}
}
The script that starts this application as a job and receives its output periodically.
$ErrorActionPreference = 'continue'
$job = Start-Job { C:\TEMP\Test\ConsoleApplication1.exe 2>&1 }
for(;;) {
Receive-Job $job | Write-Host
Start-Sleep 2
}
The script does exactly what you need (for your edit 2 section): it shows the output as soon as it is available and it does not show unwanted extra error information.
P.S. This version works, too:
$job = Start-Job { C:\TEMP\Test\ConsoleApplication1.exe }
for(;;) {
Receive-Job $job 2>&1 | Write-Host
Start-Sleep 2
}
Try:
$erroractionpreference = "silentlycontinue"
.\myscript.ps1