Capturing verbose output from -Verbose switch in PowerShell v2 - powershell

I ran into an interesting issue today that has me puzzled. I need to capture output from the verbose stream and write it to stdout.
This can be accomplished with this:
# Create a PowerShell Command
$pscmd = [PowerShell]::Create()
# Add a Script to change the verbose preference.
# Since I want to make sure this change stays around after I run the command I set UseLocalScope to $false.
# Also note that since AddScript returns the PowerShell command, I can simply call Invoke on what came back.
# I set the return value to $null to suppress any output
$null = $psCmd.AddScript({$VerbosePreference = "Continue"},$false).Invoke()
# If I added more commands, I'd be adding them to the pipeline, so I want to clear the pipeline
$psCmd.Commands.Clear()
# Now that I've cleared the pipeline, I'll add another script that writes out 100 messages to the Verbose stream
$null = $psCmd.AddScript({Write-verbose "hello World" }).Invoke()
# Finally, I'll output the stream
$psCmd.Streams.Verbose
Now the interesting part is if I were to create a function called Hello-World and use [CmdletBinding()] to inherit the -verbose switch, I can no longer capture output:
Function Hello-World {
[CmdletBinding()]
Param()
Write-Verbose "hello world"
}
...
$null = $psCmd.AddScript({Hello-World -Verbose }).Invoke()
...
I am assuming that the function is given its own verbose stream and that visibility to the stream is lost, but I am not positive. Does this have to do with [CmdletBinding()]?
Avoiding transcripts, is there a way to accomplish this?
Thanks!

Thank you #JasonMorgan, below is the solution that appears to be working. I needed to declare the function in the pscmd I made:
$pscmd = [PowerShell]::Create()
$null = $psCmd.AddScript({$VerbosePreference = "Continue"},$false).Invoke()
$null = $psCmd.AddScript({
function Hello-World {
[CmdletBinding()]
Param()
Write-Verbose "hello world"
}
}, $false).Invoke()
$psCmd.Commands.Clear()
$null = $psCmd.AddScript({Hello-World -Verbose }).Invoke()
$psCmd.Streams.Verbose

Related

Powershell returning boolean fails

Writing a powershell script with a function that invokes an executable, and then attempts to return a bool. Two errors occur:
The output from the .exe doesn't show in the console window while running
the returned value is contains the output + the return value..
I'm doing something stupid, but what?
Script is
function HelloWorld () {
$cmd = "./HelloWorld.exe"
& $cmd
return $true}
$ret = HelloWorld
The $ret = "Hello World
True"
UPDATED:
Ok, I found a script online to create the helloworld.exe
Add-Type -outputtype consoleapplication -outputassembly helloworld.exe 'public class helloworld{public static void Main(){System.Console.WriteLine("hello world");}}'
Now We can run this:
function HelloWorld {
$cmd = ".\HelloWorld.exe"
& $cmd | Write-Host
return $true
}
$ret = HelloWorld
After run:
hello world
PS> $ret
True
PS> $ret | gm
TypeName: System.Boolean
Seems to work here at least.
HTH
The output is being captured by the assignment to $ret. Important to understand PowerShell functions return all output. when you ran & $cmd the output is returned, then you ran an explicit albeit unnecessary Return $true. Both pieces of data get returned and you see no screen output because it was consumed by the assignment.
In order to get the output of HelloWorld.exe to the console while only returning the Boolean from the function you can use either Write-Host or Out-Host. The difference being the latter traverses PowerShell's for-display formatting system, which may not be necessary in this case.
function HelloWorld ()
{
$cmd = "./HelloWorld.exe"
& $cmd | Write-Host
return $true
}
$return = HelloWorld
In this case the screen should show the output from HellowWorld.exe, but $return should only contain $true.
Note: Because of the aforementioned behavior Return isn't technically necessary. The only necessary use case for Return is to explicitly exit a function usually early.
Also note: this assumes HelloWorld.exe is a typical console application for which PowerShell will capture output into the success stream.
So in the function, there is a call to an executable which had a console.writeline statement (console.writelne("Hello world"))
calling the function works fine, but there no output that appears. Instead it got returned to the caller along with the return true/false. How can I get just the bool return without disrupting the output from the exe which we need to see on the console real time...

echo in while loop get's added to my return value

I wasn't sure how to describe this problem in the title so here goes.
I call a function from a script in another script. In that function i have a while loop that basically keeps looping through a set of ip's and looks up their hostname. when the while loop times out or we have all the host names.
it returns the hostnames.
My problem is that the return value contains every single Write-Host i'm doing in that function.
i know it's because Write-Host puts stuff on the pipeline and the return just returns whatever it has.
How do i go about fixing this?
The entire script i run get's logged in a log file which is why i want to have some verbose logging.
| out-null on write-host fixes the issue but it doesn't print the write-host values in the script.
in main.psm1 i have a function like so:
$nodes = #("ip1", "ip2", "ip3", "ip4")
$nodesnames = DoStuff -nodes $nodes
then in functions.psm1 i have functions like:
Function DoStuff
{
param($nodes)
$timeout = 300
$timetaken = 0
$sleepseconds = 5
$nodenames = #("$env:COMPUTERNAME")
while(($nodenames.count -lt $nodes.count) -and ($timetaken -lt $timeout))
{
try
{
Write-Host "Stuff"
foreach($node in $nodes)
{
$nodename = SuperawesomeFunction $node
Write-Host "$nodename"
if($nodenames -notcontains $nodename)
{
$nodenames += #($nodename)
}
}
}
catch
{
Write-Host "DoStuff Failed because $_"
}
Start-Sleep $sleepseconds
$timetaken += $sleepseconds
}
return $nodenames
}
Function SuperawesomeFunction
{
param($node)
$nodename = [System.Net.Dns]::GetHostEntry("$node")
return $nodename
}
Thanks.
So the answer is, your function is working like it is by design. In PowerShell a function will return output in general to the pipeline, unless specifically directed otherwise.
You used Echo before, which is an alias of Write-Output, and output is passed down the pipe as I mentioned before. As such it would be collected along with the returned $nodenames array.
Replacing Echo with Write-Host changes everything because Write-Host specifically tells PowerShell to send the information to the host application (usually the PowerShell Console or PowerShell ISE).
How do you avoid this? You could add a parameter specifying a path for a logfile, and have your function update the logfile directly, and only output the relevant data.
Or you can make an object with a pair of properties that gets passed back down the pipe which has the DNS results in one property, and the errors in another.
You could use Write-Error in the function, and set it up as an advanced function to support -errorvariable and capture the errors in a separate variable. To be honest, I'm not sure how to do that, I've never done it, but I'm 90% sure that it can be done.

Appropriate logging in Powershell

If I have a powershell script say called caller.ps1 which looks like this
.\Lib\library.ps1
$output = SomeLibraryFunction
where library.ps1 looks like the following
function SomeLibraryFunction()
{
Write-Output "Some meaningful function logging text"
Write-Output "return value"
}
What I'd like to achieve is a way in which the library function can return it's value but also add some logging messages that allow the caller to process those internal messages as they see fit. The best I can think of is to write both to the pipeline and then the caller will have an array with the actual return value plus the internal messages which may be of use to a logger the calling script has.
Am I going about this problem the right way? Is there a better way to achieve this?
It's usually not a good idea to mix logging messages with actual output. Consumers of your function then have to do a lot of filtering to find the object they really want.
Your function should write logging messages to the verbose output stream. If the caller wants to see those messages, it can by specifying the -Verbose switch to your function.
function SomeLibraryFunction()
{
[CmdletBinding()]
param(
)
Write-Verbose "Some meaningful function logging text"
Write-Output "return value"
}
In PowerShell 3+, consumers can redirect your verbose messages to the output stream or a file:
# Show verbose logging messages on the console
SomeLibraryFunction -Verbose
# Send verbose logging messages to a file
SomeLibraryFunction -Verbose 4> verbose.txt
# redirect verbose message to the output stream
SomeLibraryFunction -Verbose 4>&1
Other options include:
Writing to a well-known file
Writing to the Event Log
Use Start-Transcript to create a log of the session.
Something like Tee-Object might be helpful to you
function SomeLibraryFunction()
{
$filepath = "C:\temp\log.txt"
write-output "Some meaningful function logging text" | Tee-Object -FilePath $filepath -Append
write-output "return value" | Tee-Object -FilePath $filepath -Append
}
For more information on Tee-Object look here
You could use an If statement based on a variable like $logging=$true but i could see that getting messy.
Another Approach
If you are looking for more of an optional solution then maybe you could use something like this Start-Transcript and Stop-Transcript which creates a record of all or part of a Windows PowerShell session in a text file.
function SomeLibraryFunction()
{
write-output "Some meaningful function logging text"
write-output "return value"
}
$logging = $True
If ($logging){
Start-Transcript -Path C:\temp\log.txt -Append
}
SomeLibraryFunction
If ($logging){
Stop-Transcript
}
This would just show that you could toggle the Start and Stop. You could even set the switch with a paramater passed to the script if you chose.
NOTE The output might be more that you are looking for but at least give it a try. Also, this will not work in the Powershell ISE as you will get an error Start-Transcript : This host does not support transcription.
Another way to do this would be to return a compound object that includes the results and the log information. This is then easy to pick apart.
function MyFunc
{
# initialize
$result = $null
$log = #()
# write to the log
$log += "This will be written to the log"
$log += "So will this"
# record the result
$result = $true
# return result and log combined into object
return New-Object -TypeName PSObject -Property #{ result = $result; log = $log -join "`r`n" }
}
# Call the function and get the results
$MyFuncResult = MyFunc
# Display the returned result value
Write-Host ( "MyFunc Result = {0}" -f $MyFuncResult.Result )
# Display the log
write-host ( "MyFunc Log = {0}" -f $MyFuncResult.Log )
Alternatively, if you want to avoid the object, pass in a log variable by reference. The function can write to the log variable and the changes will be visible in the calling scope. To do this, you need to add the [ref] prefix to the function definition AND the function call. When you write to the variable in the function you need to refer to the .value property.
function MyFunc2 ([ref]$log)
{
# initialize
$result = $null
# write to the log
$log.value += "`r`nThis will be written to the log"
$log.value += "`r`nSo will this"
# record the result
$result = $true
# return result and log combined into object
return $result
}
# Call the function and get the results
$log = "before MyFunc2"
$MyFuncResult = MyFunc2([ref]$log)
$log += "`nafter MyFunc2"
# Display the returned result value
write-host ( "MyFunc2 result = {0}" -f $MyFuncResult )
# Display the log
write-host ( "MyFunc2 Log = {0}" -f $Log )

Recursive -Verbose in Powershell

Is there an easy way to make the -Verbose switch "passthrough" to other function calls in Powershell?
I know I can probably search $PSBoundParameters for the flag and do an if statement:
[CmdletBinding()]
Function Invoke-CustomCommandA {
Write-Verbose "Invoking Custom Command A..."
if ($PSBoundParameters.ContainsKey("Verbose")) {
Invoke-CustomCommandB -Verbose
} else {
Invoke-CustomCommandB
}
}
Invoke-CustomCommandA -Verbose
It seems rather messy and redundant to do it this way however... Thoughts?
One way is to use $PSDefaultParameters at the top of your advanced function:
$PSDefaultParameterValues = #{"*:Verbose"=($VerbosePreference -eq 'Continue')}
Then every command you invoke with a -Verbose parameter will have it set depending on whether or not you used -Verbose when you invoked your advanced function.
If you have just a few commands the do this:
$verbose = [bool]$PSBoundParameters["Verbose"]
Invoke-CustomCommandB -Verbose:$verbose
I began using KeithHill's $PSDefaultParameterValues technique in some powershell modules. I ran into some pretty surprising behavior which I'm pretty sure resulted from the effect of scope and $PSDefaultParameterValues being a sort-of global variable. I ended up writing a cmdlet called Get-CommonParameters (alias gcp) and using splat parameters to achieve explicit and terse cascading of -Verbose (and the other common parameters). Here is an example of how that looks:
function f1 {
[CmdletBinding()]
param()
process
{
$cp = &(gcp)
f2 #cp
# ... some other code ...
f2 #cp
}
}
function f2 {
[CmdletBinding()]
param()
process
{
Write-Verbose 'This gets output to the Verbose stream.'
}
}
f1 -Verbose
The source for cmdlet Get-CommonParameters (alias gcp) is in this github repository.
How about:
$vb = $PSBoundParameters.ContainsKey('Verbose')
Invoke-CustomCommandB -Verbose:$vb

Redirecting output to $null in PowerShell, but ensuring the variable remains set

I have some code:
$foo = someFunction
This outputs a warning message which I want to redirect to $null:
$foo = someFunction > $null
The problem is that when I do this, while successfully supressing the warning message, it also has the negative side-effect of NOT populating $foo with the result of the function.
How do I redirect the warning to $null, but still keep $foo populated?
Also, how do you redirect both standard output and standard error to null? (In Linux, it's 2>&1.)
I'd prefer this way to redirect standard output (native PowerShell)...
($foo = someFunction) | out-null
But this works too:
($foo = someFunction) > $null
To redirect just standard error after defining $foo with result of "someFunction", do
($foo = someFunction) 2> $null
This is effectively the same as mentioned above.
Or to redirect any standard error messages from "someFunction" and then defining $foo with the result:
$foo = (someFunction 2> $null)
To redirect both you have a few options:
2>&1>$null
2>&1 | out-null
ADDENDUM:
Please note that (Windows) powershell has many more streams than a linux based OS. Here's the list from MS docs:
Thus you can redirect all streams using the wildcard with *>$null, and you can also use a file instead of $null.
This should work.
$foo = someFunction 2>$null
If it's errors you want to hide you can do it like this
$ErrorActionPreference = "SilentlyContinue"; #This will hide errors
$someObject.SomeFunction();
$ErrorActionPreference = "Continue"; #Turning errors back on
Warning messages should be written using the Write-Warning cmdlet, which allows the warning messages to be suppressed with the -WarningAction parameter or the $WarningPreference automatic variable. A function needs to use CmdletBinding to implement this feature.
function WarningTest {
[CmdletBinding()]
param($n)
Write-Warning "This is a warning message for: $n."
"Parameter n = $n"
}
$a = WarningTest 'test one' -WarningAction SilentlyContinue
# To turn off warnings for multiple commads,
# use the WarningPreference variable
$WarningPreference = 'SilentlyContinue'
$b = WarningTest 'test two'
$c = WarningTest 'test three'
# Turn messages back on.
$WarningPreference = 'Continue'
$c = WarningTest 'test four'
To make it shorter at the command prompt, you can use -wa 0:
PS> WarningTest 'parameter alias test' -wa 0
Write-Error, Write-Verbose and Write-Debug offer similar functionality for their corresponding types of messages.
using a function:
function run_command ($command)
{
invoke-expression "$command *>$null"
return $_
}
if (!(run_command "dir *.txt"))
{
if (!(run_command "dir *.doc"))
{
run_command "dir *.*"
}
}
or if you like one-liners:
function run_command ($command) { invoke-expression "$command "|out-null; return $_ }
if (!(run_command "dir *.txt")) { if (!(run_command "dir *.doc")) { run_command "dir *.*" } }
Recently, I had to shut up powershell on a Linux host, this wasn't that obvious to figure out. After back and forth I found out that wrapping a command in $( ) and adding a explicit redirection after the wrapper works.
Anything else I tried, wouldn't - I still don't know why since the PowerShell Docs are of desirable quality (and full of inconsistency...)
To import all modules on startup, I added the following. This produced some stderr output by powershell that couldnt be put to rest by ErrorAction or redirection without using the wrapping...
If anyone could elaborate on why's that would be very appreciated.
# import installed modules on launch
$PsMods = $(Get-InstalledModule);
$($PsMods.forEach({ Import-Module -Name $_.Name -ErrorAction Ignore })) *> /dev/null