Store output of command with error - powershell

I want to store the output of this command
git checkout master
the output of this command has two states.
success - the command outputs:
Switched to branch 'X'
failure - the command outputs
Your local changes to the following files would be overwritten by checkout <list files> : Please commit your changes or stash them before you switch branches. Aborting
I want to store the output and check if it succeeds by checking for the string abort.
This is what I've tried.
$output = git checkout master 2>&1
if ($output.Contains("Aborting")) {
Write-Host $output -BackgroundColor Red
} else {
Write-Host $output
}
but $output produces something like this, when in the second state:
It seems like the command completed, but it was the wrong syntax. How do I fix this? I would like to to ouput

Make sure that your command's stderr output is converted to regular strings:
$output = [string[]] (git checkout master 2>&1)
Without the [string[]] cast, stderr output lines are stored in $output as [System.Management.Automation.ErrorRecord] instances, and outputting such instances later somewhat misleadingly prints them as if they were PowerShell errors.
(Note that PowerShell Core no longer exhibits this behavior: while stderr lines are still captured as [System.Management.Automation.ErrorRecord] instances, they now print as regular strings.)

Related

Powershell : How do I capture Success and Failure of call to command? [duplicate]

I'm using some GIT commands in my PowerShell scripts. Most of the time I'm calling the GIT commands via Invoke-Expression so that I, e.g.
can parse the output, or/and
forward the out to a logging method.
At some GIT commands I recognized that not all output is returned via Invoke-Expression though the documentation states:
Outputs
PSObject
Returns the output that is generated by the invoked command (the value of the Command parameter).
Here is an example:
> $x = iex "git fetch --all"
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 3), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (4/4), done.
Content of $x:
> $x
Fetching origin
Fetching upstream
So the main information is not returned to $x. I can't imagine that git fetch --all is returning the main information via stderr (wouldn't make sense ...).
I also found this PowerShell question, which is unanswered and the used PowerShell version is 2.
Used PowerShell version:
> $PSVersionTable
Name Value
---- -----
PSVersion 6.2.0
PSEdition Core
GitCommitId 6.2.0
OS Microsoft Windows 10.0.18362
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
How can I force Invoke-Expression to return the whole output?
Thx
As I mentioned in "PowerShell Capture Git Output", with Git 2.16 (Q1 2018), you can try and set first:
set GIT_REDIRECT_STDERR=2>&1
Then in your Powershell script, you should get both stdout and stderr outputs,
See also dahlbyk/posh-git issue 109 for a more Powershell-like example:
$env:GIT_REDIRECT_STDERR = '2>&1'
VonC's answer works well with git, specifically, but it's worth discussing a generic solution:
Note: Invoke-Expression should generally be avoided and there is no reason to use it for invocation of external programs: just invoke them directly and assign to a variable:
$capturedStdout = git ... # capture git's stdout output as an array of lines
As has been noted, git outputs status information to stderr, whereas data goes to stdout; a PowerShell variable assignment only captures stdout output.[1]
To capture a combination of stdout and stderr, interleaved, as it would print to the terminal, you can use redirection 2>&1, as in other shells, to merge the error stream / stderr (2) into (>&) the data output stream (stdout equivalent, 1 - see about_Redirection):
$combinedOutput = git fetch --all 2>&1
Caveat: In PowerShell versions up to v7.1, if $ErrorActionPreference = 'Stop' happens to be in effect, the use of 2> unexpectedly triggers a terminating error; this problematic behavior is discussed in GitHub issue #4002.
There are non-obvious differences to the behavior of other shells, however:
The output will be an array of lines, not a single, multi-line string,
Note: As of PowerShell 7.2 - external-program output is invariably interpreted as text (strings) - there is no support for raw binary output; see this answer.
Lines that originated from stdout are represented as strings, as expected, but lines originating from stderr are actually [System.Management.Automation.ErrorRecord] instances, though they print like strings and on conversion to strings do reproduce the original line, such as when sending the result to an external program.
This answer shows how to separate the captured lines by stream of origin (assuming stdout and stderr were merged).
Being able to capture stderr output separately in a variable is desirable, which isn't supported as of PowerShell 7.2.x, however. Adding future support, along the lines of 2>variable:errs, is the subject of GitHub issue #4332.
The array-based result can be advantageous for parsing; e.g., to find a line that contains the word unpacking:
PS> $combinedOutput -match 'unpacking'
Unpacking objects: 100% (4/4), done.
Note: If there's a chance that only one line was output, use #($combinedOutput) -match 'unpacking'
If you prefer to receive a single, multi-line string instead:
$combinedOutput = (git fetch --all 2>&1) -join "`n" # \n (LF); or: [Environment]::NewLine
If you don't mind a trailing newline as part of the string, you can more simply use Out-String:[2]
$combinedOutput = git fetch --all 2>&1 | Out-String
Caveat: In Windows PowerShell this won't work as expected if stderr lines are present, as they are rendered like PowerShell error records (this problem has been fixed in PowerShell (Core) 6+); run cmd /c 'echo data & echo err >&2' 2>&1 | Out-String to see the problem. Use the -join "`n" solution to avoid the problem.
Note:
As usual, irrespective of what redirections you use, determining whether an external-program call succeeded or failed should be based only on its exit code, reflected in PowerShell's automatic $LASTEXITCODE variable: By convention (which most, but not all programs observe), 0 indicates success and and any nonzero value failure (a notable exception is robocopy which uses several nonzero exit codes to communicate additional information in the success case).
[1] For comprehensive information on capturing output from external programs in PowerShell, see this answer.
[2] This problematic Out-String behavior is discussed in GitHub issue #14444.
try this (without iex)
$x=git fetch --all

How can I store the output of a command as a variable in PowerShell?

Say I execute the command:
git pshu
which will give me:
git: 'pshu' is not a git command. See 'git --help'.
Did you mean this?
push
If this was the last command I executed, I want retrieve the command and store its outputs in a variable for further use.
So far I have tried things like:
$var = echo (iex (h)[-1].CommandLine)
$var = iex (h)[-1].CommandLine | Out-String
etc.
How can I save such output to a variable?
Lets say you are running multiple commands, and you want to store store a run command based on its input.
Lets say you want to store non existing git commands, as git pshu. My proposal will be to store the command and search for the desired pattern on the output as "Did you mean...". In this case, you can follow as:
$output = git pshu *>&1
if ( "$output".indexOf("Did you mean") -ge 0 ) {
write-host "Command '$($(get-history)[$(get-history).count-1].CommandLine)' didnt exist"
where $(get-history)[$(get-history).count-1].CommandLine will contain the latest run command, in this example, `git pshu'

How to get PowerShell to display the commands being called

I've got a simple PowerShell script that I'm calling from Jenkins:
function PerformRepoActions($localRepo)
{
$startDir = $pwd.path
cd $localRepo
hg recover
hg revert --all
hg pull -r $branch --rebase
hg update -r $branch
cd $startDir
}
$branch = $env:NAMEDBRANCH
PerformRepoActions $pwd.path
When I run this, it does not show any of the mercurial commands that I'm making. Here's what it shows in the Jenkins output:
no interrupted transaction available
pulling from [repo_location]
no changes found
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
It's giving me the the output of the hg commands, but not showing the commands themselves.
So, I'm looking for something that will make those commands print out, equivalent to the cmd.exe "echo on".
A search tells me that the equivalent in PowerShell is "Set-PSDebug -Trace 1". So I add that to the beginning of the script, and it changes the output to:
DEBUG: 15+ >>>> $branch = $env:NAMEDBRANCH
DEBUG: 16+ >>>> PerformRepoActions $pwd.path
DEBUG: 5+ >>>> {
DEBUG: 6+ >>>> $startDir = $pwd.path
DEBUG: 7+ >>>> cd $localRepo
no interrupted transaction available
pulling from ssh://hg#mercurial.wilcoxassoc.com/PcDmis/QA
no changes found
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
As you can see, while it does give me more output, but it doesn't actually show me the commands that I'm calling.
"Set-PSDebug -Trace 2" gives even more output, but still doesn't show what mercurial commands are being called.
The work-around that I've come up with is to create a function which echoes and the executes a string. It works, but it feels awful kludgey:
function execute($cmd)
{
echo $cmd
iex $cmd
}
function PerformRepoActions($localRepo)
{
$startDir = $pwd.path
execute "cd $localRepo"
execute "hg recover"
execute "hg revert --all"
execute "hg pull -r $branch --rebase"
execute "hg update -r $branch"
execute "cd $startDir"
}
$branch = $env:NAMEDBRANCH
PerformRepoActions $pwd.path
Is there some better way to do this?
It seems that there must be, but I've been surprised before.
edit: This is not a duplicate of PowerShell "echo on". Re-directing the output to a file, the answer to that question, isn't feasible. I need it to display in the Jenkins output, not a file.
there's a way that you can capture the contents of the current script that you're in, and with a little work you could probably marry that up with the relevant output. (whether that's a need I'm not sure)
As of Powershell v3 you have access to the abstract syntax trees (that's the only link I could find that said anything about it!)
Basically the Ast has access all properties of the current script (or any script you send to it) including its complete source.
running this script (I included notepad to show it running external exe)
Write-Host "before"
$MyInvocation.MyCommand.ScriptBlock.Ast
Write-Host "after"
notepad
Will produce this output
before
Attributes : {}
UsingStatements : {}
ParamBlock :
BeginBlock :
ProcessBlock :
EndBlock : Write-Host "before"
$MyInvocation.MyCommand.ScriptBlock.Ast
Write-Host "after"
notepad
DynamicParamBlock :
ScriptRequirements :
ImplementingAssembly :
Extent :
Write-Host "before"
$MyInvocation.MyCommand.ScriptBlock.Ast
Write-Host "after"
notepad
Parent :
after
I imagine if you play around with
$MyInvocation.MyCommand.ScriptBlock.Ast
You should be able to make it produce something like what you want.
edited to add
You could potentially take this a step further by using the Ast to select each Powershell cmdlet and attach a breakpoint to it, then you should have access to the cmdlet names (I don't think that would work with executables - and its only a theory at the moment!) You'd need to run the whole thing through an external script that managed the logging and continuing from the breakpoints. But I think it could work.

Output message with variable part to TeamCity build log from PowerShell

I know that one can output messages to TeamCity build logs via specially decorated Write-Host:
write-host "##teamcity[message text='Prepearing backup folder']"
What if I need the message to contain a value of a variable as well.
I've tried the following:
$myFullMessage = "Perpearing backup folder at: " + $path
write-host "##teamcity[message text=$myFullMessage]"
But I get an error in an output stating that the message parameter provided should start with ' character.
Please let me know if I can output messages with variable value part in message body.
The easiest way is a string formatter. Otherwise you get in escape-character hell. Note how in Powershell that you must place two consecutive single-quote characters to put a literal one in the string.
$myFullMessage = "Perpearing backup folder at: " + $path
write-host $( '##teamcity[message text=''{0}'']' -f $myFullMessage )

Use console output of command-line tool in Powershell pipeline

In my PowerShell script, I'd like to use the output of a tool like git.
For example, the command line
git status
returns
# On branch master
nothing to commit (working directory clean)
Now I tried to use this output in the following pipeline command:
git status | $_.Contains("nothing to commit")
But I get the error
Expressions are only allowed as the first element of a pipeline.
What am I doing wrong?
$msg = [string](git status) | where { $_.Contains("nothing to commit") }
You can use select-string:
git status | select-string "nothing to commit"