How to expand member-variables in Write-Host or double quotes? - powershell

I have written a PS script and for diagnostic purpose am echoing messages to screen using Write-Host. This is fine as long as I have to expand normal variable like
Write-Host "Hello World, $name"
Problem starts when i try to echo some member variable as below
Write-Host "Hello World, $Person.Name"
This does not expand as expected. The work around that am following is, to use temp variable
as below
$personName = $Person.Name
Write-Host "Hello World, $personName"
Is there an elegant way of doing this with out the use of temp variable?

If you want to use property access within double-quoted strings, you need a subexpression:
"Foo $($bar.Property)"

Try this:
$dir = ls
Write-Host "Dir elements:" $dir.Length

Related

Powershell 'mystring' vs Write-Host 'mystring' [duplicate]

I am having a little confusion about the various ways to print (echo) to the console. I have seen that there are multiple ways to write output to the console, such as:
Write-Host "Hello world1"
"Hello World2"
Out-Host -InputObject "Hello World3"
All three ways will print to the console. The middle one is somehow simpler and less verbose and easier to use. I also find that when you write a function such as:
function GetValues()
{
"1"
"2"
}
It still returns two strings in the pipeline:
And I'm still able to print out the values:
foreach ($s in GetValues)
{
Write-Host "s: " $s
}
The thing that I have found was that using just the quoted string doesn't always appear on custom hosts, and that I have had to use Write-Host to get values to print on custom hosts.
Somehow I find this confusing. Is "Print something" supposed to be an alias to Write-Host or what is the intent?
Default behaviour of PowerShell is just to dump everything that falls out of a pipeline without being picked up by another pipeline element or being assigned to a variable (or redirected) into Out-Host. What Out-Host does is obviously host-dependent.
Just letting things fall out of the pipeline is not a substitute for Write-Host which exists for the sole reason of outputting text in the host application.
If you want output, then use the Write-* cmdlets. If you want return values from a function, then just dump the objects there without any cmdlet.
The middle one writes to the pipeline. Write-Host and Out-Host writes to the console. 'echo' is an alias for Write-Output which writes to the pipeline as well. The best way to write to the console would be using the Write-Host cmdlet.
When an object is written to the pipeline it can be consumed by other commands in the chain. For example:
"hello world" | Do-Something
but this won't work since Write-Host writes to the console, not to the pipeline (Do-Something will not get the string):
Write-Host "hello world" | Do-Something

Unnecessary space in output when using Write-Host

When I use Write-Host within a Foreach-Object, I get an unnecessary space in the output.
write-host "http://contoso.com/personal/"$_.ADUserName
Output:
http://contoso.com/personal/ john.doe
How can I remove the space before john? Trim does not work because there is no space in $_.ADUserName
This is happening because Write-Host is considering your constant string and your object to be two separate parameters -- you aren't actually joining the strings together the way you're calling it. Instead of calling it this way, actually concatenate the strings:
write-host "http://contoso.com/personal/$($_.ADUserName)"
or
write-host ("http://contoso.com/personal/" + $_.ADUserName)
or
write-host ("http://contoso.com/personal/{0}" -f $_.ADUserName)
Just do it without write-host:
"http://contoso.com/personal/{0}" -f $_.ADUserName

How to write to the console in PowerShell?

I am having a little confusion about the various ways to print (echo) to the console. I have seen that there are multiple ways to write output to the console, such as:
Write-Host "Hello world1"
"Hello World2"
Out-Host -InputObject "Hello World3"
All three ways will print to the console. The middle one is somehow simpler and less verbose and easier to use. I also find that when you write a function such as:
function GetValues()
{
"1"
"2"
}
It still returns two strings in the pipeline:
And I'm still able to print out the values:
foreach ($s in GetValues)
{
Write-Host "s: " $s
}
The thing that I have found was that using just the quoted string doesn't always appear on custom hosts, and that I have had to use Write-Host to get values to print on custom hosts.
Somehow I find this confusing. Is "Print something" supposed to be an alias to Write-Host or what is the intent?
Default behaviour of PowerShell is just to dump everything that falls out of a pipeline without being picked up by another pipeline element or being assigned to a variable (or redirected) into Out-Host. What Out-Host does is obviously host-dependent.
Just letting things fall out of the pipeline is not a substitute for Write-Host which exists for the sole reason of outputting text in the host application.
If you want output, then use the Write-* cmdlets. If you want return values from a function, then just dump the objects there without any cmdlet.
The middle one writes to the pipeline. Write-Host and Out-Host writes to the console. 'echo' is an alias for Write-Output which writes to the pipeline as well. The best way to write to the console would be using the Write-Host cmdlet.
When an object is written to the pipeline it can be consumed by other commands in the chain. For example:
"hello world" | Do-Something
but this won't work since Write-Host writes to the console, not to the pipeline (Do-Something will not get the string):
Write-Host "hello world" | Do-Something

What's the difference between "Write-Host", "Write-Output", or "[console]::WriteLine"?

There are a number of different ways to output messages. What is the effective difference between outputting something via Write-Host, Write-Output, or [console]::WriteLine?
I also notice that if I use:
write-host "count=" + $count
The + gets included in the output. Why's that? Shouldn't the expression be evaluated to produce a single concatenated string before it gets written out?
Write-Output should be used when you want to send data on in the pipe line, but not necessarily want to display it on screen. The pipeline will eventually write it to out-default if nothing else uses it first.
Write-Host should be used when you want to do the opposite.
[console]::WriteLine is essentially what Write-Host is doing behind the scenes.
Run this demonstration code and examine the result.
function Test-Output {
Write-Output "Hello World"
}
function Test-Output2 {
Write-Host "Hello World" -foreground Green
}
function Receive-Output {
process { Write-Host $_ -foreground Yellow }
}
#Output piped to another function, not displayed in first.
Test-Output | Receive-Output
#Output not piped to 2nd function, only displayed in first.
Test-Output2 | Receive-Output
#Pipeline sends to Out-Default at the end.
Test-Output
You'll need to enclose the concatenation operation in parentheses, so that PowerShell processes the concatenation before tokenizing the parameter list for Write-Host, or use string interpolation
write-host ("count=" + $count)
# or
write-host "count=$count"
BTW - Watch this video of Jeffrey Snover explaining how the pipeline works. Back when I started learning PowerShell I found this to be the most useful explanation of how the pipeline works.
Apart from what Andy mentioned, there is another difference which could be important - write-host directly writes to the host and return nothing, meaning that you can't redirect the output, e.g., to a file.
---- script a.ps1 ----
write-host "hello"
Now run in PowerShell:
PS> .\a.ps1 > someFile.txt
hello
PS> type someFile.txt
PS>
As seen, you can't redirect them into a file. This maybe surprising for someone who are not careful.
But if switched to use write-output instead, you'll get redirection working as expected.
Here's another way to accomplish the equivalent of Write-Output. Just put your string in quotes:
"count=$count"
You can make sure this works the same as Write-Output by running this experiment:
"blah blah" > out.txt
Write-Output "blah blah" > out.txt
Write-Host "blah blah" > out.txt
The first two will output "blah blah" to out.txt, but the third one won't.
"help Write-Output" gives a hint of this behavior:
This cmdlet is typically used in scripts to display strings and other
objects on the console. However, because the default behavior is to
display the objects at the end of a pipeline, it is generally not
necessary to use the cmdlet.
In this case, the string itself "count=$count" is the object at the end of a pipeline, and is displayed.
For usages of Write-Host, PSScriptAnalyzer produces the following diagnostic:
Avoid using Write-Host because it might not work in all hosts, does not work when there is no host, and (prior to PS 5.0) cannot be suppressed, captured, or redirected. Instead, use Write-Output, Write-Verbose, or Write-Information.
See the documentation behind that rule for more information. Excerpts for posterity:
The use of Write-Host is greatly discouraged unless in the use of commands with the Show verb. The Show verb explicitly means "show on the screen, with no other possibilities".
Commands with the Show verb do not have this check applied.
Jeffrey Snover has a blog post Write-Host Considered Harmful in which he claims Write-Host is almost always the wrong thing to do because it interferes with automation and provides more explanation behind the diagnostic, however the above is a good summary.
From my testing Write-Output and [Console]::WriteLine() perform much better than Write-Host.
Depending on how much text you need to write out this may be important.
Below if the result of 5 tests each for Write-Host, Write-Output and [Console]::WriteLine().
In my limited experience, I've found when working with any sort of real world data I need to abandon the cmdlets and go straight for the lower level commands to get any decent performance out of my scripts.
measure-command {$count = 0; while ($count -lt 1000) { Write-Host "hello"; $count++ }}
1312ms
1651ms
1909ms
1685ms
1788ms
measure-command { $count = 0; while ($count -lt 1000) { Write-Output "hello"; $count++ }}
97ms
105ms
94ms
105ms
98ms
measure-command { $count = 0; while ($count -lt 1000) { [console]::WriteLine("hello"); $count++ }}
158ms
105ms
124ms
99ms
95ms
Regarding [Console]::WriteLine() - you should use it if you are going to use pipelines in CMD (not in powershell). Say you want your ps1 to stream a lot of data to stdout, and some other utility to consume/transform it. If you use Write-Host in the script it will be much slower.

how to output a string to variable and console at the same time

is there an easy way in powershell to output a string to variable and console at the same time?
i want to capture the output of my script to a variable so i can analyze it in the end of the script, save it to a log file and also email to an operator.
my intent is to have a variable $output and add any output strings to it and also output to console immediately something like
$output="Process started"
$output=+"Doing step 1"
"Doing step 1"
$output=+"Doing step 2"
"Doing step 2"
so in the end I can save $output to a log file, email it and parse it.
I played with tee-object that might work for that purpose but unfortunately it would rewrite my $output variable instead of appending a string to it.
UPDATE
This is the final solution I decided to go with - thanks to manojlds!
$script:output = ""
filter mylog {
$script:output+= $_+"`n"
return $_
}
"doing step {0}" -f 1 | mylog
"doing step {0}" -f 2 | mylog
"doing step {0}" -f 3 | mylog
#in the end of the script
$script:output
There are so many ways to get your end goal:
In your script just have something like this:
"Process started"
<step1>
"Doing step 1"
<step2>
"Doing step 2"
...
Then run the script as
.\test.ps1 | Tee-Object -file log.txt
Note that the output is available to Tee-Object and hence the console as and when it occurs. You don't get the output only after the entire script runs. This is how pipeline works in Powershell. Objects are passed along downstream as and when they occur. Insert a sleep 10 in between as steps and see that the output comes as soon as it is available.
Also, you don't necessarily have to have another script ( the launcher.ps1 ) you are talking about. You can use functions, scriptblock etc. within your script.
Some other ways:
function test {
"Process started"
sleep 5
"Doing step 1"
sleep 5
"Doing step 2"
}
test | %{$output+=$_;$_}
#use output
write-host -fore blue $output
You can create a filter:
$script:output = ""
filter appendOutput {
$script:output+= $_
return $_
}
"Process started" | appendOutput
sleep 5
"Doing step 1" | appendOutput
sleep 5
"Doing step 2" | appendOutput
#use output
write-host -fore blue $script:output
There are probably many more ways of doing this.
Here's a nice trick, enclose your variable assignment in parenthesis. You can find more on this in the PowerShell language specification (section 7.1.1 Grouping parentheses) available to download here or view it online here:
PS > ($var=1)
1
PS >
I haven't messed with powershell enough to give a concrete answer, but if I were to do this in C I would exploit side effects.
//Psuedo-C
string con (oldString, newString) {
print(newString);
return oldString + newString;
}
Use function like so:
myString = con(myString, "Process started");
It would have the desired effect. (leaving aside correct C syntax and pedantry such as dealing with newlines) I don't know how to translate this to powershell.
What you want to do may be considered messy however. It might be clearer if you just explicitly output and log output and log one after the other in your code. Side effects inevitably come back to bite you. Keep things modular.
I was looking at something similar to this, with the exception I did not need to analyze it afterwards, just collect the output.
Something someone else might look at since it seems you have your answer is using PowerShell transcripts (Start-Transcript and Stop-Transcript).I found from this site that it does have some issues when you hit a server error, but he shows how he handled it.
To expand a bit on #manojlds answer, you can simply use the Tee-Object cmdlet, but rather than specifying a file for it to write to, you can tell it to write directly to a variable, like so:
.\test.ps1 | Tee-Object -Variable output
All of the output from the script/cmdlet/function/etc will be written to the console screen in real-time, as well as stored in the $output variable when the script finishes running. So you could then analyze it or write it to a file using that variable. This avoids you having to read the file contents into a variable after the operation completed.
Script will write data on output screen as well as store the same data in variable
get-process | Tee-Object -Variable test
$test can use further
("Process started" | out-host) | Set-Variable x ; $x