Disclaimer : I am the epitome of a scipting/Powershell rookie, so please bear with me.
I've written a script to return the Active Directory username of any user currently logged into a given workstation.
$input = Read-Host "Workstation Name"
$domain = ".*****.***.com"
$computer = $input + $domain
$list = gwmi win32_computersystem -comp $computer | select Username,Caption
Write-Output $list
However, if I run this from a pinned script in the taskbar, the Powershell window closes before I have a chance to view the results.
I have tried method 2 and 3 from this post, but to no avail. Method 2 prompts for user input before the results are displayed instead of after, even when the code for the prompt is added at the end of the script.
Any help would be greatly appreciated.
Method 2 from the linked post - i.e., waiting for the user to press a key before exiting the script - can be used, but it requires additional effort:
End your script as follows in order to see the value of $list before the pause command prompts:
$list | Out-Host # Force *synchronous* to-display output.
pause # Wait for the user to press Enter before exiting.
Note: pause in PowerShell is simply a function wrapper around Read-Host as follows: $null = Read-Host 'Press Enter to continue...' Therefore, if you want to customize the prompt string, call Read-Host directly.
This answer explains why the use of Out-Host (or Format-Table) is necessary in this case; in short:
In PSv5+, an implicitly applied Format-Table command asynchronously waits for up to 300 msecs. for additional pipeline input, in an effort to derive suitable column widths from the input data.
Because you use Write-Output output objects without predefined formatting data that have 2 properties (4 or fewer ), tabular output is implicitly chosen, and Format-Table is used behind the scenes, asynchronously.
Note: The asynchronous behavior applies only to output objects for whose types formatting instructions aren't predefined (as would be reported with Get-FormatData <fullOutputTypeName>); for instance, the output format for the System.Management.Automation.AliasInfo instances output by Get-Alias is predefined, so Get-Alias; pause does produce output in the expected sequence.
The pause command executes before that waiting period has elapsed, and only after you've answered the prompt does the table print, after which point the window closes right away.
The use of an explicit formatting command (Out-Host in the most generic case, but any Format-* cmdlet will do too) avoids that problem by producing display output synchronously, so that the output will be visible by the time pause displays its prompt.
I had the same problem for scripts that I'm executing "on demand". I tend to simply add a Read-Host at the end of the script like so
$str = "This text is hardly readable because the console closes instantly"
Write-Output $str
Read-Host "Script paused - press [ENTER] to exit"
Related
Disclaimer : I am the epitome of a scipting/Powershell rookie, so please bear with me.
I've written a script to return the Active Directory username of any user currently logged into a given workstation.
$input = Read-Host "Workstation Name"
$domain = ".*****.***.com"
$computer = $input + $domain
$list = gwmi win32_computersystem -comp $computer | select Username,Caption
Write-Output $list
However, if I run this from a pinned script in the taskbar, the Powershell window closes before I have a chance to view the results.
I have tried method 2 and 3 from this post, but to no avail. Method 2 prompts for user input before the results are displayed instead of after, even when the code for the prompt is added at the end of the script.
Any help would be greatly appreciated.
Method 2 from the linked post - i.e., waiting for the user to press a key before exiting the script - can be used, but it requires additional effort:
End your script as follows in order to see the value of $list before the pause command prompts:
$list | Out-Host # Force *synchronous* to-display output.
pause # Wait for the user to press Enter before exiting.
Note: pause in PowerShell is simply a function wrapper around Read-Host as follows: $null = Read-Host 'Press Enter to continue...' Therefore, if you want to customize the prompt string, call Read-Host directly.
This answer explains why the use of Out-Host (or Format-Table) is necessary in this case; in short:
In PSv5+, an implicitly applied Format-Table command asynchronously waits for up to 300 msecs. for additional pipeline input, in an effort to derive suitable column widths from the input data.
Because you use Write-Output output objects without predefined formatting data that have 2 properties (4 or fewer ), tabular output is implicitly chosen, and Format-Table is used behind the scenes, asynchronously.
Note: The asynchronous behavior applies only to output objects for whose types formatting instructions aren't predefined (as would be reported with Get-FormatData <fullOutputTypeName>); for instance, the output format for the System.Management.Automation.AliasInfo instances output by Get-Alias is predefined, so Get-Alias; pause does produce output in the expected sequence.
The pause command executes before that waiting period has elapsed, and only after you've answered the prompt does the table print, after which point the window closes right away.
The use of an explicit formatting command (Out-Host in the most generic case, but any Format-* cmdlet will do too) avoids that problem by producing display output synchronously, so that the output will be visible by the time pause displays its prompt.
I had the same problem for scripts that I'm executing "on demand". I tend to simply add a Read-Host at the end of the script like so
$str = "This text is hardly readable because the console closes instantly"
Write-Output $str
Read-Host "Script paused - press [ENTER] to exit"
I'm just wondering if I can clear Out-Gridview on every loop like I can in the console:
while (1) { ps | select -first 5; sleep 1; clear-host }
Unfortunately this doesn't clear out-gridview every time:
& { while (1) { ps | select -first 5; sleep 1; clear-host } } | out-gridview
Clear-Host clears the display of the host, which is the console window's content in a regular PowerShell console.
By contrast, Out-GridView is a separate GUI window, over which PowerShell offers no programmatic display once it is being displayed.
Notably, you can neither clear no refresh the window's content after it is displayed with the initial data.
The best approximation of this functionality is to close the old window and open a new one with the new data in every iteration - but note that this will be visually disruptive.
In the simplest case, move the Out-GridView into the loop and call it with -Wait, which requires you to close it manually in every iteration, however:
# NOTE: Doesn't move to the next iteration until you manually close the window.
while (1) { ps | select -first 5 | Out-GridView -Wait }
This answer shows how to implement an auto-closing Out-GridView window, but it is a nontrivial effort - and with a sleep period as short as 1 second it will be too visually disruptive.
Ultimately, what you're looking for is a GUI version of the Unix watch utility (or, more task-specifically, the top utility).
However, since you're not looking to interact with the Out-GridView window, there's little benefit to using Out-GridView in this case.
Instead, you could just spawn a new console window that uses Clear-Host to display the output in the same screen position periodically:
The following defines helper function Watch-Output to facilitate that:
# Simple helper function that opens a new console window and runs
# the given command periodically, clearing the screen beforehand every time.
function Watch-Output ([scriptblock] $ScriptBlock, [double] $timeout = 1) {
$watchCmd = #"
while (1) {
Clear-Host
& { $($ScriptBlock -replace '"', '\"') } | Out-Host
Start-Sleep $timeout
}
"# #'
Start-Process powershell.exe "-command $watchCmd"
}
# Invoke with the command of interest and a timeout.
Watch-Output -ScriptBlock { ps | Select -First 5 } -Timeout 1
Note that this will still flicker every time the window content is refreshed.
Avoiding that would require substantially more effort.
The PowerShellCookbook module offers the sophisticated Watch-Command cmdlet, which not only avoids the flickering but also offers additional features.
The big caveat is that - as of version 1.3.6 - the module has several cmdlets that conflict with built-in ones (Format-Hex, Get-Clipboard, New-SelfSignedCertificate, Send-MailMessage, Set-Clipboard), and the only way to import the module is to allow the module's commands to override the built-in ones (Import-Module PowerShellCookbook -AllowClobber).
I have a script that runs several loops of code and relies on specific input at various phases in order to advance. That functionality is working. My current issue revolves around extraneous input being supplied by the user displaying on screen in the console window wherever I have the cursor position currently aligned.
I have considered ignoring this issue since the functionality of the script is intact, however, I am striving for high standards with the console display of this script, and I would like to know a way to disable all user input period, unless prompted for. I imagine the answer has something to do with being able to command the Input Buffer to store 0 entries, or somehow disabling and then re-enabling the keyboard as needed.
I have tried using $HOST.UI.RawUI.Flushinputbuffer() at strategic locations in order to prevent characters from displaying, but I don't think there's anywhere I could put that in my loop that will perfectly block all input from displaying during code execution (it works great for making sure nothing gets passed when input is required, though). I've tried looking up the solution, but the only command I could find for manipulating the Input Buffer is the one above. I've also tried strategic implementation of the $host.UI.RawUI.KeyAvailable variable to detect keystrokes during execution, then $host.UI.RawUI.ReadKey() to determine if these keystrokes are unwanted and do nothing if they are, but the keystrokes still display in the console no matter what.
I am aware that this code is fairly broken as far as reading the key to escape the loop goes, but bear with me. I hashed up this example just so that you could see the issue I need help eliminating. If you hold down any letter key during this code's execution, you'll see unwanted input displaying.
$blinkPhase = 1
# Set Coordinates for cursor
$x = 106
$y = 16
$blinkTime = New-Object System.Diagnostics.Stopwatch
$blinkTime.Start()
$HOST.UI.RawUI.Flushinputbuffer()
do {
# A fancy blinking ellipses I use to indicate when Enter should be pressed to advance.
$HOST.UI.RawUI.Flushinputbuffer()
while ($host.UI.RawUI.KeyAvailable -eq $false) {
if ($blinkTime.Elapsed.Milliseconds -gt 400) {
if ($blinkPhase -eq 1) {
[console]::SetCursorPosition($x,$y)
write-host ". . ." -ForegroundColor gray
$blinkPhase = 2
$blinkTime.Restart()
} elseif ($blinkPhase -eq 2) {
[console]::SetCursorPosition($x,$y)
write-host " "
$blinkPhase = 1
$blinkTime.Restart()
}
}
start-sleep -m 10
}
# Reading for actual key to break the loop and advance the script.
$key = $host.UI.RawUI.ReadKey()
} while ($key.key -ne "Enter")
The expected result is that holding down any character key will NOT display the input in the console window while the ellipses is blinking. The actual result, sans error message, is that a limited amount of unwanted/unnecessary input IS displaying in the console window, making the script look messy and also interfering with the blinking process.
What you're looking for is to not echo (print) the keys being pressed, and that can be done with:
$key = $host.UI.RawUI.ReadKey('IncludeKeyDown, NoEcho')
Also, your test for when Enter was pressed is flawed[1]; use the following instead:
# ...
} while ($key.Character -ne "`r")
Caveat: As of at least PSReadLine version 2.0.0-beta4, a bug causes $host.UI.RawUI.KeyAvailable to report false positives, so your code may not work as intended - see this GitHub issue.
Workaround: Use [console]::KeyAvailable instead, which is arguably the better choice anyway, given that you're explicitly targeting a console (terminal) environment with your cursor-positioning command.
As an aside: You can simplify and improve the efficiency of your solution by using a thread job to perform the UI updates in a background thread, while only polling for keystrokes in the foreground:
Note: Requires the ThreadJob module, which comes standard with PowerShell Core, and on Windows PowerShell can be installed with Install-Module ThreadJob -Scope CurrentUser, for instance.
Write-Host 'Press Enter to stop waiting...'
# Start the background thread job that updates the UI every 400 msecs.
# NOTE: for simplicity, I'm using a simple "spinner" here.
$jb = Start-ThreadJob {
$i=0
while ($true) {
[Console]::Write("`r{0}" -f '/-\|'[($i++ % 4)])
Start-Sleep -ms 400
}
}
# Start another thread job to do work in the background.
# ...
# In the foreground, poll for keystrokes in shorter intervals, so as
# to be more responsive.
While (-not [console]::KeyAvailable -or ([Console]::ReadKey($true)).KeyChar -ne "`r" ) {
Start-Sleep -Milliseconds 50
}
$jb | Remove-Job -Force # Stop and remove the background UI thread.
Note the use of [Console]::Write() in the thread job, because Write-Host output wouldn't actually be passed straight through to the console.
[1] You tried to access a .Key property, which only the [SystemConsoleKeyInfo] type returned by [console]::ReadKey() has; the approximate equivalent in the $host.UI.rawUI.ReadKey() return type, [System.Management.Automation.Host.KeyInfo], is .VirtualKeyCode, but its specific type differs, so you can't (directly) compare it to "Enter"; The latter type's .Character returns the actual [char] instance pressed, which is the CR character ("`r") in the case of Enter.
I am trying to print a PDF as XPS, the script opens the PDF Print Output As screen and enters the correct name before sending the ENTER command to the window to save the file.
How can I select the address bar to enter the desired path? Or how can I change the default save path?
EDIT: Thank you for the feedback. Here is the script:
function print_files($secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names($xps_dir, $fullname){
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys($xps_dir\$basename)
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files($secure_pdf_dir)
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
It seems that the $xps_dir variable is not being passed into the function properly.
Edit: Error I get when trying to add $xps_dir to the enter_my_names function:
Microsoft.PowerShell.Utility\Write-Error : Cannot validate argument on parameter
'FilePath'. The argument is null or empty. Provide an argument that is not null or
empty, and then try the command again.
At same_time:115 char:115
+
+ CategoryInfo : NotSpecified: (:) [Write-Error], ParameterBindingValidati
onException
+ FullyQualifiedErrorId : System.Management.Automation.ParameterBindingValidationEx
ception,Microsoft.PowerShell.Commands.WriteErrorCommand
+ PSComputerName : [localhost]
I'm not sure if you got this fully worked out or I misread your post, but I was able to implement some code from your initial post to finish my project [been beating my head against a wall as a non-traditional comp sci person]; so I wanted to share some things I did in hopes of helping you (in the event you're still at that crossroads).
Some points of interest:
-My overall process is a bulk report generator. A variable amount of .XML stylesheets are sent to IE because a javascript subroutine numbers the reports; to accomplish this I have visual basic doing some database parsing, passing values into custom scripts needed to form the .XML stylesheets, then VB creates PS .ps1 files with the various PS commands needed to create the IE com object. Finally, VB calls the .ps1 scripts, and the reports are generated by sending the stylesheet to IE, waiting for HTML render, send print command, and close the object.
-While your endgoal is to generate an XPS, mine was to fully render an HTML, then print it with a PDF printer; I assume we have a similar process in that we must engage a dialog box in the intermediate stage (in this case, using sendkeys to interact with it); this is specifically what I'm going to discuss below in hopes of helping out!
So, two points of discussion:
First, more of a general observation/query: I don't see where your $xps_dir variable is actually being defined; I mention this because there could be a string-related issue with the values being passed by the $xps_dir. One way to check your string to make sure its 'pretty' is to pass it to a .txt or something using the OUT-FILE command:
"$xps_dir" | Out-File -FilePath "C:\somefolder\sometext.txt" -Append
The text file itself doesn't need to exist, but the file does...the above command will create the text file.
Also, if you're ok with viewing the powershell prompt, a WRITE-HOST can be used.
Either way, I'm wondering if the final compiled string isn't compiled correctly, so its not recognizing it as a directory.
I think what more likely is happening is the SENDKEYS aren't being sent to the correct fields. For example, by using your code I was [FINALLY] able to set my 'Save As' dialog box as an object and interact with it. (When using my PDF printer, I get the 'Save As' dialog box).
At first, I tried to send the file name, then send enter. I assumed this would be fine, because during manual interaction the focus for the 'Save As' dialog box goes to the 'File Name' field; however, after trying:
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{ENTER}")
I realized the SendKeys command was sending the keys to the 'Save in' drop down (or probably the first element of the Save As dialog box). So, I manually click inside of the 'Save in' field, then tabbed around until I got to 1. 'File Name' (wrote the # of tabs down), and 2. the 'Save' button (wrote # of tabs down). By adding SendKeys("{TAB}") to correspond with the number of tabs I observed, I was able to successfully enter my name string and close out the print dialog box:
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
NOTE: the number of tabs might be different for your dialog box, so I strongly recommend doing a manual run through to count how many tabs get you to the field you want to interact with.
In conclusion, I have one reflection for consideration:
I've used (to much success) VB and PS to use html elements to dynamically enhance my routines. Since we're able to set the dialog box as a Com object, my next goal will be to try to tap into the respective 'elements', similar to how html elements can be accessed (or more generally, object-oriented language allows). This way, I might be able to better interact with the dialog box without suffering the drawbacks of SendKeys:
Has to run as active process; user cannot engage any other windows else keys might be sent to them;
Keys are based on a count; any variation to that count will require the subroutine to be updated/edited, making it not elastic;
Also, I have not done any work regarding error windows, but one I've already noticed is the "your document has the same name blah blah", so just a heads up.
Even though we're doing two different things, here's my final code for this part of my routine in case it helps provide context:
#send command to internet explorer application with execWB; 6 for print, 2 for no user prompt on window
while ( $ie.busy ) { Start-Sleep -second 3 }
$ie.execWB(6,2)
#create dialog box object
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save As') -ne $true){ Start-Sleep -second 3 }{
$wshell.AppActivate('Save As')
}
#create file string from 3 strings: directory,basename returned, file extension
#the return from my output log is: "c:\test_folder\mybasenamestring.PDF"
$mydirectory = "c:\test_folder\"
$mybasename = [io.path]::GetFileNameWithoutExtension($fullname)
$myextension = ".PDF"
$myfilename = "$mydirectory$mybasename$myextension"
#Using tabs to navigate around the dialog window; send my string to the 'File Name' field, then tab to 'Save' and send enter
#This is where the name is actually entered
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys($myfilename)
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{TAB}")
$wshell.SendKeys("{ENTER}")
#going to add a command to report on the file size of the pdf; the pdf will stay 0kb until its fully generated. Once fully generated will add the $ie.quit or the kill command here for the $ie object
#this sends a stop process command to kill powershell since I'm running it as a ps1 file and it will remain open otherwise
stop-process -Id $PID
Last thing: I observed some weirdness with the capitalization, so I updated the final SendKeys to capitalize the string since I want all caps anyhow:
$wshell.SendKeys($myfilename.toupper())
Hope this helps! Thanks for posting; I was finally able to complete my entire process. As I clean and improve, if I find improvements for this area I'll try to remember to share. Thanks for your help!
Pass the variable containing the desired directory ($xps_dir in this case) to each function in the process until it gets to the enter_my_names function where it can then be sent to the window with the $wshell.SendKeys("$xps_dir\$basename").
function print_files($xps_dir, $secure_pdf_dir){
#Retrieves the name for the .xps files
Get-ChildItem $secure_pdf_dir -Filter *.pdf -Recurse | Foreach-Object {
#For each .pdf file in that directory, continue
same_time $xps_dir $_.FullName
}
}
## The following function keeps checking for a new window called "Save Print Output As"
## When the window shows up, it enters the name of the file and press ENTER
function enter_my_names{
param ($xps_dir, $fullname)
$wshell = New-Object -ComObject wscript.shell;
while($wshell.AppActivate('Save Print Output As') -ne $true){
$wshell.AppActivate('Save Print Output As')
}
$basename = [io.path]::GetFileNameWithoutExtension($fullname)
#This is where the name is actually entered
$wshell.SendKeys("$xps_dir\$basename")
$wshell.SendKeys("{ENTER}")
}
## The following function launches simultaneously a print job on the input file
## and a function waiting for the print job to show up to name the file
workflow same_time{
Param(
$xps_dir, $fullname
)
parallel{
Start-Process -FilePath $fullname –Verb Print -PassThru
enter_my_names $xps_dir $fullname
}
}
#MAIN PROGRAM
#Here the script saves your current printer as default
$defprinter = Get-WmiObject -Query "Select * from Win32_Printer Where Default=$true"
#Queries for a XPS printer
$printer = Get-WmiObject -Query "Select * from Win32_Printer Where Name='Microsoft XPS Document Writer'"
#Sets the XPS printer as Default
$printer.SetDefaultPrinter()
#Starts the main job
print_files $xps_dir $secure_pdf_dir
#Sets the old default printer back as default again
$defprinter.SetDefaultPrinter()
#This is a small delay to be sure everything is completed before closing Adobe Reader. You can probably shorten it a bit
sleep 2
#Finally, close Adobe Reader
Get-Process "acrord32" | Stop-Process
The file dialogue will accept a path in the file name field, so instead of sending the filename file.pdf send the full path to the file C:\folder\folder\file.pdf
EDIT:
You can send the folder path like so:
$wshell.SendKeys($xps_dir)
$wshell.SendKeys("{ENTER}")
$wshell.SendKeys($basename)
$wshell.SendKeys("{ENTER}")
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.