PowerShell Forms. How come in my Form Closing event If I can't call a Function or use Write-output, but it works if I use Write-Host - forms

I have a small form I'm working on but I have something I'm confused about. I have a closing event
$Form.Add_Closing({}) In there I'm wanting to stop a custom logging module but it doesn't reflect the output to the console, same if I use write-output. If I use Write-Host though, that reflects to the console. Does the Closing event just have any real output capability?
$Form.Add_Closing({
# my logging function - doesn't work
Write-Log -Stop
# Write-Output - doesn't work
Write-Output 'Test message'
# Write-Host - does work
Write-Host 'Another Test message'
})

The problem applies to all events, not just Closing:
Inside a PowerShell script block serving as a .NET event delegate:
You can call arbitrary commands...
... but their success-stream (pipeline) output is discarded.
However, output to any of PowerShell's other output streams does surface in the caller's console, as you've experienced with Write-Host.
Therefore, if you simply want to print the called commands' success output to the caller's display, you can pipe them to Out-Host:
$Form.Add_Closing({
Write-Log -Stop | Out-Host
})
Note:
Out-Host's output - unlike Write-Host's - can fundamentally neither be captured nor suppressed.
Output from Write-Host, which since v5 writes via the information stream, can be suppressed with 6>$null, and in principle be captured via the common -InformationVariable parameter, if your script is an advanced script and it is invoked with, say, ./yourScript -InformationVariable capturedInfoStream.
However, this does not work with Write-Host calls made inside event-delegate script blocks.
If you want to collect success output emitted from event-delegate script blocks for later use in the script (which also allows you to control if the collected output is sent to the script's caller or not), create a list in the script scope, to which you can append from the event-delegate script blocks:
# Initialize a list to collect event-delegate output in.
$outputFromEventDelegates = [Collections.Generic.List[object]] #()
# ...
$Form.Add_Closing({
# Call the function of interest and add its output to the
# script-scope list.
$outputFromEventDelegates.AddRange(
#(Write-Log -Stop)
)
})
# ... after the .ShowDialog() call
# Now you can access all collected output.
Write-Verbose -Verbose "Output collected from event delegates:"
$outputFromEventDelegates

Related

While working in Powershell, how do I pause between list items?

I have been working on this for a while and I cannot find this utilization anywhere. I am using a powershell array and the foreach statement:
#('program 1','program 2','program 3').foreach{winget show $_ -args}
I then want to have a pause between each one so I added ;sleep 1
This does not work. It pauses for 3s (based on this eg.) and then lists the items. What am I missing here?
Indeed it doesn't seem to respect the order, I don't know the technical reason why. You could either use a normal ForEach-Object
'program 1','program 2','program 3' | ForEach-Object {
winget show $_
sleep 1
}
or force the output to go to the console instead of being "buffered"
('program 1','program 2','program 3').ForEach{
winget show $_ | Out-Host
sleep 1
}
Doug Maurer's helpful answer provides effective solutions.
As for an explanation of the behavior you saw:
The intrinsic .ForEach() method first collects all success output produced by the successive, per-input-object invocations of the script block ({ ... }) passed to it, and only after having processed all input objects outputs the collected results to the pipeline, i.e. the success output stream, in the form of a [System.Collections.ObjectModel.Collection[psobject]] instance.
In other words:
Unlike its cmdlet counterpart, the ForEach-Object cmdlet, the .ForEach() method does not emit output produced in its script block instantly to the pipeline.
As with any method, success output is only sent to the pipeline when the method returns.
Note that non-PowerShell .NET methods only ever produce success output (which is their return value) and only ever communicate failure via exceptions, which become statement-terminating PowerShell errors.
By contrast, the following do take instant effect inside a .ForEach() call's script block:
Suspending execution temporarily, such as via a Start-Sleep
Forcing instant display (host) output, such as via Out-Host or Write-Host.
Note that to-host output with Out-Host prevents capturing the output altogether, whereas Write-Host output, in PowerShell v5+, can only be captured via the information output stream (number 6).
Writing to an output stream other than the success output stream, such as via Write-Error, Write-Warning or Write-Verbose -Verbose.
Alternatively, you may use the foreach statement, which, like the ForEach-Object cmdlet, also instantly emits output to the success output stream:
# Stand-alone foreach statement: emits each number right away.
foreach ($i in 1..3) { $i; pause }
# In a pipeline, you must call it via & { ... } or . { ... }
# to get streaming behavior.
# $(...), the subexpression operator would NOT stream,
# i.e. it would act like the .ForEach() method.
& { foreach ($i in 1..3) { $i; pause } } | Write-Output

Another PowerShell function return value and Write-Output

this has been beaten to death but can't find an exact solution for my problem.
I have a PowerShell script that can be run from the command line or from a scheduled task. I'm using the following line
Write-Output "Updating user $account" | Tee-Object $logfile -Append
to write relevant information to the screen and a log file. I need both because when run from a command line, I can physically see what's going on but when run from a scheduled task, I have no visibility to its output hence the log file.
Thing is, I'm modifying my code to use functions but as you might already know, Write-Output messes up the return values of functions when used within said functions.
What could I do that would do something similar to what I stated above without affecting the function's return value?
Thanks.
Just write to a log file. When running from the console, open another console and tail the log file.
Get-Content 'C:\path\to\the\logfile.txt' -Tail 10 -Wait
Assuming PowerShell version 5 or higher, where Write-Host writes to the information output stream (stream number 6), which doesn't interfere with the success output stream (stream number 1) and therefore doesn't pollute your function's data output:
The following is not a single command, but you could easily wrap this in a function:
Write-Host "Updating user $account" -iv msg; $msg >> $logfile
The above uses the common -InformationVariable (-iv) parameter to capture Write-Host's output in variable $msg (note how its name must be passed to -iv, i.e. without the leading $).
The message captured in $msg is then appended to file $logfile with >>, the appending redirection operator.
Note: >> is in effect an alias for Out-File -Append, and uses a fixed character encoding, both on creation and appending.
Use Add-Content and its -Encoding parameter instead, if you want to control the encoding.
Instead of explicitly writing each log line to a file, you may want to use a different approach that references the log file only at one location in the code.
Advantages:
Easy to change log path and customize the log output (e. g. prepending a timestamp), without having to modify all code locations that log something.
Captures any kind of messages, e. g. also error, verbose and debug messages (if enabled).
Captures messages of 3rd party code aswell, without having to tell them the name of the log file.
Function SomeFunction {
Write-Host "Hello from SomeFunction" # a log message
"SomeFunctionOutput" # Implicit output (return value) of the function.
# This is short for Write-Output "SomeFunctionOutput".
}
Function Main {
Write-Host "Hello from Main" # a log message
# Call SomeFunction and store its result (aka output) in $x
$x = SomeFunction
# To demonstrate that "normal" function output is not affected by log messages
$x -eq "SomeFunctionOutput"
}
# Call Main and redirect all of its output streams, including those of any
# called functions.
Main *>&1 | Tee-Object -FilePath $PSScriptRoot\Log.txt -Append
Output:
Hello from Main
Hello from SomeFunction
True
In this sample all code is wrapped in function Main. This allows us to easily redirect all output streams using the *>&1 syntax, which employs the redirection operator to "merge" the streams. This means that all commands further down the pipeline (in this example Tee-Object) receive any script messages that would normally end up in the console (except when written directly to the console, which circumvents PowerShells streams).
Possible further improvements
You may want to use try/catch in function Main, so you also capture script-terminating errors:
try {
SomeFunction # May also cause a script-terminating error, which will be catched.
# Example code that causes a script-terminating error
Write-Error "Fatal error" -ErrorAction Stop
}
catch {
# Make sure script-terminating errors are logged
Write-Error -ErrorRecord $_ -ErrorAction Continue
}

Is there a way to set a variable up to place output to stdout or null?

I would like to set up a variable in my code that would ultimately define if I'll see some output or not.
"hello" writes to stdout
"hello" > $null supresses output
My idea is something like this:
$debugOutputSwitch = $true
$outputVar = $null
if ($debugOutputSwitch){ $outputVar = **STDOUT** }
...
Write-Host "Something I want out anyway"
"Something I might not want on STDOUT" > $outputVar
If this general idea is a way to go, then STDOUT is what I'm looking for
If this idea is completely wrong...well...then I'm lost
What you want to read up on are output streams and redirection in Powershell. This includes information on all of the different output streams and how to control their relevance using built-in constructs. Just like there are the Write-Host and Write-Output cmdlets, there are also several others that control which stream to write to.
About the Output Streams
There are 6 streams in total. Make note of their numbers, because these stream identifiers are used to control which streams to redirect:
1 - Success Stream - This stream is used when passing information along the Powershell Pipeline. This is the "default" stream, but can also be written to with Write-Output.
2 - Error Stream - Errors should be written to this stream. Can be written to with Write-Error, accompanied by further error information.
3 - Warning Stream - Used to write warning information. Can be written to withWrite-Warning.
4 - Verbose Stream - Used to write verbose output. Does not display by default but can be made to display by either setting $VerbosePreference = "Continue", or by using the [CmdletBinding()] attribute on a function or script and passing in the -Verbose flag. Write to the verbose stream with Write-Verbose.
5 - Debug Stream - Used to write to the debug stream, and optionally trigger a breakpoint. Does not display or trigger a breakpoint by default, but can be controlled with the $DebugPreference variable, or by using the [CmdletBinding()] attribute on a script or function and using the -Debug flag. You can write to the debug stream by using theWrite-Debug cmdlet.
6 - Information Stream - Can be written to by Write-Host. This is the console host output and is not part of the pipeline.
Redirecting Streams
You can use redirection operators to redirect other streams to the success stream as well. Each stream above has a number associated with it. This is the numeric representation of each stream.
The redirection operators are as follows:
> - Redirect success stream to file (overwrite)
#> - Redirect the # stream to file (e.g. 2> somefile.txt)
>> - Redirect success stream to file (appends, you can also use a numbered stream as with the overwrite file operator)
>&1 - Redirect any stream to success stream (note that unlike the other redirection operators you can only redirect to the success stream. Using other stream identifiers will result in an error).
Also note that in place of a stream number, you can use * which will redirect all streams at the same time.
Here are some examples of redirecting output from one stream to another (if you're familiar with it, it's somewhat UNIX-y):
# Write success stream to file
Write-Output "Here is some text for a file" > .\somefile.txt
# Write error stream to file (you have to first
Write-Error "Some error occurred" 2> .\somefile.txt
# Redirect all error output to the success stream
$myErrorOutput = Write-Error "My error output" 2>&1
# Append all script output streams to a single file
Get-OutputFromAllStreams.ps1 *>> somefile.txt
Output to a File and the Pipeline Simultaneously
You can redirect the output stream to a file and the pipeline at the same time as well, using the Tee-Object cmdlet. This also works with variables, too:
$myString = "My Output" | Tee-Object -FilePath .\somefile.txt
$myString2 = "My Output 2" | Tee-Object -Variable varName
Sample function to show how to use the different Write- cmdlets
Note how the following function is decorated with the [CmdletBinding()] attribute. This is key in making the -Verbose and -Debug switches work without you having to define them yourself.
function Write-DifferentOutputs {
[CmdletBinding()]
# These all visible by default but only the output stream is passed down the pipeline
Write-Output "Output stream"
Write-Warning "Warning stream"
Write-Error "Error stream"
Write-Host "Information stream"
# These are not visible by default, but are written when the `-Verbose` or `-Debug` flags are passed
# You can also manually set the $VerbosePreference or $DebugPreference variables to control this without parameters
Write-Verbose "Verbose stream"
Write-Debug "Debug stream"
}
Call the above function with the -Verbose or -Debug switches to see how the behavior differs, and also call it with neither flag.
Redirecting output to $null if you really need to
If there is output that you never want to see or for some other reason using the Write- cmdlets to write to the Verbose or Debug streams isn't an option, you can still redirect output to $null or make use of the Out-Null cmdlet. Recall the numbered streams at the top of this answer, they will be referenced here:
Using redirection
# Don't forget that *> redirects ALL streams, and may be what you want
Write-Output 'Success Stream' > $null
Write-Error 'Error Stream' 2> $null
Write-Warning 'Warning Stream' 3> $null
Write-Verbose 'Verbose Stream' 4> $null
Write-Debug 'Debug Stream' 5> $null
Write-Host 'Information Stream (yes you can suppress/redirect me)' 6> $null
You can also redirect target streams per command: The following example (using the earlier Write-DifferentOutputs function) redirects all streams except for the Error and Success streams:
Note: You are not limited to redirecting targeted streams only to $null.
Write-DifferentOutputs 6>$null 5>$null 4>$null 3>$null
Using Out-Null
Remember, you can redirect other streams to the success stream by redirecting the output to &1.
# Remember, to pass information on the pipeline
# it MUST be on the success stream first
# Don't forget that *> redirects ALL streams, and may be what you want
Write-Output 'Success Stream' | Out-Null
Write-Error 'Error Stream' 2>&1 | Out-Null
Write-Warning 'Warning Stream' 3>&1 | Out-Null
Write-Verbose 'Verbose Stream' 4>&1 | Out-Null
Write-Debug 'Debug Stream' 5>&1 | Out-Null
Write-Host 'Information Stream (yes you can suppress/redirect me)' 6>&1 | Out-Null
When using Out-Host is appropriate ("Don't Cross the Streams")
Warning: Unlike Write-Host, Out-Host does not output to the information stream. Instead, it outputs directly to the host console. This makes redirection of anything written directly to Out-Host impossible short of using Start-Transcript or using a custom PowerShell host. Note that information written to the console host is still visible to external applications which may be watching the output of PowerShell, as ultimately evenOut-Host output makes it to STDOUT.
Calling yourself Out-Host is usually redundant. By default, PowerShell sends all unassigned output on the success stream here via way of the Out-Default cmdlet (which you should never callOut-Default directly). That said, one useful invocation of Out-Host is to synchronously output formatted object data to the console:
Note: You can redirect information from other output streams and output to Out-Host as well, but there is no reason to do so. Object data will only remain intact on the success stream, the other streams will first convert an object to its ToString() representation prior to redirection. This is also why piping the object to Out-Host in this case is preferable to Write-Host.
Get-Process msedge | Out-Host
One of the caveats of the different output streams is that there is no synchronicity between streams. Normally this is not an issue as PowerShell executes instructions line by line in order, and with the exception of Write-Output success stream this is not a problem with the other streams. However, many types will have a computed for-display attribute which is computed asynchronously from the script execution before the information is sent to Out-Default.
This can result in the displayed object data being intermingled with other output streams which are written to the console host. In some cases, this can even result in loss of information written to the console. "Crossing the streams", if you will, as it pertains to how the rendered output may look.
Consider the following example and output. This does not showcase the streams intermingling, but consider the trouble you would have parsing the output externally if Write-Host "end `n" were written in the middle of the table:
Write-Host "start `n"
Get-LocalUser
Write-Host "end `n"
And the output:
start
end
Name Enabled Description
---- ------- -----------
Administrator True
DefaultAccount False A user account managed by the system.
Disabled False Built-in account for guest access to the computer/domain
In particular this is problematic for types which define a table format that must calculate the column width before sending the formatted data to Out-Host for display. Types which pre-define the table width or do not format output as a table at all do not have this problem. Out-Default can take up to 300ms to calculate column width.
When Out-Host is called explicitly as part of a pipeline, however, the table width calculation is skipped for these objects as the object data never makes it to Out-Default. This is useful primarily to ensure that object data intended to be written to the console is done so in the correct order. The downside is that table columns may not be wide enough to accommodate all of your data on each row.
This all said, if you must process the console output of a script, it is recommended to format the data you wish to process into a string and use Write-Host instead, or use another method to get the data somewhere suitable for external processing. for-display formatting is not intended for use with external processing.
#mklement1's answer here dives further into the details about this if you are curious to learn more about this problem.
Redirecting whole command outputs to Write- cmdlets
You can easily pipe all output of a command or cmdlet to one of the Write- cmdlets. I'll use the Write-DifferentOutputs provided earlier in my example below, but this will work with any cmdlet, script, or command you run:
Write-DifferentOutputs *>&1 | Write-Verbose
What the above will do is only show the command output if $VerbosePreference = $Continue, or if you passed -Verbose as an argument to your script or function.
In Summarium
In your original question, you are attempting to reinvent a wheel that Powershell already supports fairly well. I would suggest that you learn how to make use of the different Write-Output cmdlets for each stream and especially learn how to make use of the Write-Warning, Write-Verbose, Write-Error, and Write-Debug cmdlets.
All right.
Thanks to all the brainiacs here for the motivation.
This answer may not be the best way to go about it, but it works!
Two things you need to understand to achieve this:
If you are used to using Write-Host, it won't work, you'll have to go with Write-Output.
You may have to learn to use a block of script as a function parameter.
One is self explanatory, so here's how to attain #2:
Function Test-SctiptBlockParam {
Param(
$scriptblock
)
if ($debugOutput) {
Invoke-Command $scriptblock
} else {
(Invoke-Command $scriptblock) > $null
}
}
Test-SctiptBlockParam -scriptblock { Write-Output "I want to see on the STDOUT sometimes" }
Finally, here is an example of my output and code

Powershell Script output to variable - capture Write-Host output

Using this script: https://github.com/byterogues/powershell-bittrex-api
which I call from another script.
e.g.
$order = .\bittrex-api.ps1 -action buylimit -market BTC-TX -quantity 1 -rate 0.00011300
bittrex-api.ps1 catches an error and shows this on screen
BITTREX ERROR: API Query returned an error.
Error Message: MIN_TRADE_REQUIREMENT_NOT_MET
How can I capture the output from bittrex-api.ps1 into a variable so I can use this variable in my base script?
You can't. The script uses Write-Host to output the error. Write-Host only writes text to the console, it doesn't return any objects which means there's nothing to capture.
I would recommend modifying the script to use other cmdlets like Write-Error, Write-Output or any other Write-* which outputs to a stream (which you can redirect to the stdout-stream and save).
See http://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/
To complement Frode F.'s helpful answer, which sensibly recommends modifying the script to use Write-Error for error reporting:
If modifying the code is not an option and you're using PSv5+, you can capture Write-Host output, because since PSv5 Write-Host writes to the newly introduced information output stream (to which primarily Write-Information is designed to write):
PowerShell's output streams are numbered, and the information stream has number 6 so that output-redirection expression 6>&1 redirects the information stream into the success output stream, whose number is 1, allowing regular capturing in a variable, as the following example shows:
# PSv5+
$captured = Write-Host 'write-host output' 6>&1
$captured # output what was captured -> 'write-host output'
To learn more about PowerShell's output streams and redirection, run Get-Help about_Redirection
Note:
Write-Host output captured via 6>&1 consists of one or more System.Management.Automation.InformationRecord instances, which print as if they were strings, namely by their .MessageData.Message property value, which is the string content of the argument(s) passed to Write-Host.
Therefore, any coloring that stems from the use of the -ForegroundColor and -BackgroundColor parameters is not (directly) passed through:
However, the information is preserved, namely in the .MessageData.ForegroundColor and .MessageData.BackgroundColor properties, along with the information about whether -NoNewLine was passed to Write-Host, in Boolean property .MessageData.NoNewLine
This answer shows how to recreate the original coloring from the captured objects.
By contrast, coloring via ANSI / VT escape sequences embedded in the original string argument(s) is preserved.
Note: To capture the output and also pass it through (to the success output stream), you have two options:
Simply enclose the statement in (...), but note that output will only show after all output has been collected.
($captured = Write-Host 'write-host output' 6>&1)
For streaming pass-through, use Tee-Object with the -Variable parameter:
Write-Host 'write-host output' 6>&1 | Tee-Object -Variable captured
The above techniques generally work for capturing and passing success-stream output through, irrespective of whether redirections are involved.

Powershell: Write-Output -NoEnumerate not suppressing output to console

I'm writing a function in PowerShell that I want to be called via other PowerShell functions as well as be used as a standalone function.
With that objective in mind, I want to send a message down the pipeline using Write-Output to these other functions.
However, I don't want Write-Output to write to the PowerShell console. The TechNet page for Write-Output states:
Write-Output:
Sends the specified objects to the next command in the pipeline. If the command is the last command in the pipeline, the objects are displayed in the console.
-NoEnumerate:
By default, the Write-Output cmdlet always enumerates its output. The NoEnumerate parameter suppresses the default behavior, and prevents Write-Output from enumerating output. The NoEnumerate parameter has no effect on collections that were created by wrapping commands in parentheses, because the parentheses force enumeration.
For some reason, this -NoEnumerate switch will not work for me in either the PowerShell ISE or the PowerShell CLI. I always get output to my screen.
$data = "Text to suppress"
Write-Output -InputObject $data -NoEnumerate
This will always return 'Text to suppress' (no quotes).
I've seen people suggest to pipe to Out-Null like this:
$data = "Text to suppress"
Write-Output -InputObject $data -NoEnumerate | Out-Null
$_
This suppresses screen output, but when I use $_ I have nothing in my pipeline afterwards which defeats the purpose of me using Write-Output in the first place.
System is Windows 2012 with PowerShell 4.0
Any help is appreciated.
Write-Output doesn't write to the console unless it's the last command in the pipeline. In your first example, Write-Output is the only command in the pipeline, so its output is being dumped to the console. To keep that from happening, you need to send the output somewhere. For example:
Write-Output 5
will send "5" to the console, because Write-Output is the last and only command in the pipeline. However:
Write-Output 5 | Start-Sleep
no longer does that because Start-Sleep is now the next command in the pipeline, and has therefore become the recipient of Write-Output's data.
Try this:
Write your function as you have written it with Write-Output as the last command in the pipeline. This should send the output up the line to the invoker of the function. It's here that the invoker can use the output, and at the same time suppress writing to the console.
MyFunction blah, blah, blah | % {do something with each object in the output}
I haven't tried this, so I don't know if it works. But it seems plausible.
My question is not the greatest.
First of all Write-Output -NoEnumerate doesn't suppress output on Write-Output.
Secondly, Write-Output is supposed to write its output. Trying to make it stop is a silly goal.
Thirdly, piping Write-Output to Out-Null or Out-File means that the value you gave Write-Output will not continue down the pipeline which was the only reason I was using it.
Fourth, $suppress = Write-Output "String to Suppress" also doesn't pass the value down the pipeline.
So I'm answering my question by realizing if it prints out to the screen that's really not a terrible thing and moving on. Thank you for your help and suggestions.
Explicitly storing the output in a variable would be more prudent than trying to use an implicit automatic variable. As soon as another command is run, that implicit variable will lose the prior output stored in it. No automatic variable exists to do what you're asking.
If you want to type out a set of commands without storing everything in temporary variables along the way, you can write a scriptblock at the command line as well, and make use of the $_ automatic variable you've indicated you're trying to use.
You just need to start a new line using shift + enter and write the code block as you would in a normal scriptblock - in which you could use the $_ automatic variable as part of a pipeline.