Skip to content
Menu
The Lonely Administrator
  • PowerShell Tips & Tricks
  • Books & Training
  • Essential PowerShell Learning Resources
  • Privacy Policy
  • About Me
The Lonely Administrator

Teeing Up to the Clipboard

Posted on August 12, 2015August 12, 2015

Because I spend a great part of my day creating PowerShell related content, I often need to copy command output from a PowerShell session. The quick and dirty solution is to pipe my expression to the Clip.exe command line utility.

Manage and Report Active Directory, Exchange and Microsoft 365 with
ManageEngine ADManager Plus - Download Free Trial

Exclusive offer on ADManager Plus for US and UK regions. Claim now!
get-service | where { $_.status -eq 'running'} | clip

This works in both the console and PowerShell ISE. But there are situations where I'd like to see the result so that I know it is worth pasting into whatever I'm working on. In other words I want to send the results of the command to the pipeline and the clipboard at the same time. PowerShell can already do something similar with the Tee-Object cmdlet. With that cmdlet you can tee output to a variable or a file. So I decided to build a wrapper around Tee-Object and add functionality to send tee output to the clipboard.

I created a copy of Tee-Object and started modifying. I didn't want to reverse-engineer the cmdlet. I simply wanted to add a new parameter. In some ways, my new command is like a proxy function.

#requires -version 4.0


<#
This is a copy of:

CommandType Name       ModuleName                  
----------- ----       ----------                  
Cmdlet      Tee-Object Microsoft.PowerShell.Utility

Created: 8/12/2015
Author : Jeff Hicks @JeffHicks

  ****************************************************************
  * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
  * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK.  IF   *
  * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
  * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING.             *
  ****************************************************************
#>


Function Tee-MyObject {
<#

.SYNOPSIS

Saves command output in a file, variable or Windows Clipboard and also sends it down the pipeline.


.DESCRIPTION

The cmdlet redirects output, that is, it sends the output of a command in two directions (like the letter "T"). It stores the output in a file or variable and also sends it down the pipeline. If  is the last command in the pipeline, the command output is displayed at the prompt.

This command is a modified version of Tee-Object.
.PARAMETER Clipboard
Copy the output to the Windows clipboard

.PARAMETER Append
Appends the output to the specified file. Without this parameter, the new content replaces any existing content in the file without warning.

This parameter is introduced in Windows PowerShell 3.0.

.PARAMETER FilePath
Saves the object in the specified file. Wildcard characters are permitted, but must resolve to a single file.

.PARAMETER InputObject
Specifies the object to be saved and displayed. Enter a variable that contains the objects or type a command or expression that gets the objects. You can also pipe an object to .

When you use the InputObject parameter with , instead of piping command results to , the InputObject value—even if the value is a collection that is the result of a command, such as –InputObject (Get-Process)—is treated as a single object. Because InputObject cannot return individual properties from an array or collection of objects, it is recommended that if you use  to perform operations on a collection of objects for those objects that have specific values in defined properties, you use  in the pipeline, as shown in the examples in this topic.

.PARAMETER Variable
Saves the object in the specified variable. Enter a variable name without the preceding dollar sign ($).

.PARAMETER LiteralPath
Saves the object in the specified file. Unlike FilePath, the value of the LiteralPath parameter is used exactly as it is typed. No characters are interpreted as wildcards. If the path includes escape characters, enclose it in single quotation marks. Single quotation marks tell Windows PowerShell not to interpret any characters as escape sequences.


.EXAMPLE
PS C:\> Get-Eventlog -list | Tee-MyObject -clipboard


  Max(K) Retain OverflowAction        Entries Log 
  ------ ------ --------------        ------- --- 
  20,480      0 OverwriteAsNeeded      30,245 Application
  20,480      0 OverwriteAsNeeded           0 HardwareEvents 
     512      7 OverwriteOlder              0 Internet Explorer
  20,480      0 OverwriteAsNeeded           0 Key Management Service
     128      0 OverwriteAsNeeded       1,590 OAlerts               
  32,768      0 OverwriteAsNeeded      49,749 Security 
  20,480      0 OverwriteAsNeeded      33,273 System 
  15,360      0 OverwriteAsNeeded      11,442 Windows PowerShell

Display eventlog information and also copy it to the Windows clipboard.

.EXAMPLE

PS C:\> get-process | Tee-MyObject -filepath C:\Test1\testfile2.txt

Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)    Id ProcessName
-------  ------    -----      ----- -----   ------    -- -----------
83       4     2300       4520    39     0.30    4032 00THotkey
272      6     1400       3944    34     0.06    3088 alg
81       3      804       3284    21     2.45     148 ApntEx
81       4     2008       5808    38     0.75    3684 Apoint
...
This command gets a list of the processes running on the computer and sends the result to a file. Because a second path is not specified, the processes are also displayed in the console.

.EXAMPLE

PS C:\> get-process notepad | Tee-MyObject -variable proc | select-object processname,handles

ProcessName                              Handles
-----------                              -------
notepad                                  43
notepad                                  37
notepad                                  38
notepad                                  38
This command gets a list of the processes running on the computer and sends the result to a variable named "proc". It then pipes the resulting objects along to Select-Object, which selects the ProcessName and Handles property. Note that the $proc variable includes the default information returned by Get-Process.

.EXAMPLE

PS C:\>get-childitem –path D: –file –system –recurse | Tee-MyObject –file c:\test\AllSystemFiles.txt –append | out-file c:\test\NewSystemFiles.txt
This command saves a list of system files in a two log files, a cumulative file and a current file.
The command uses the Get-ChildItem cmdlet to do a recursive search for system files on the D: drive. A pipeline operator (|) sends the list to , which appends the list to the AllSystemFiles.txt file and passes the list down the pipeline to the Out-File cmdlet, which saves the list in the NewSystemFiles.txt file.

.NOTES

You can also use the Out-File cmdlet or the redirection operator, both of which save the output in a file but do not send it down the pipeline.
 uses Unicode encoding when it writes to files. As a result, the output might not be formatted properly in files with a different encoding. To specify the encoding, use the Out-File cmdlet.

Learn more about PowerShell:
Essential PowerShell Learning Resources
.INPUTS System.Management.Automation.PSObject .OUTPUTS System.Management.Automation.PSObject .LINK Tee-Object Select-Object about_Redirection #> [CmdletBinding(DefaultParameterSetName='File')] Param( [Parameter(ValueFromPipeline=$true)] [psobject]$InputObject, [Parameter(ParameterSetName='File', Mandatory=$true, Position=0)] [string]$FilePath, [Parameter(ParameterSetName='LiteralFile', Mandatory=$true)] [Alias('PSPath')] [string]$LiteralPath, [Parameter(ParameterSetName='File')] [switch]$Append, [Parameter(ParameterSetName='Variable', Mandatory=$true)] [string]$Variable, [Parameter(ParameterSetName='Clip')] [Switch]$Clipboard ) Begin { Write-Verbose "Starting $($MyInvocation.Mycommand)" Write-Verbose "Using parameter set $($PSCmdlet.ParameterSetName)" #initialize array to hold all input. We will process it later #with Tee-Object $data=@() } #begin Process { #add each piped in object to the array $data+= $InputObject } #process End { Write-Verbose "Processing $($data.count) input items" #run results through Tee-Object or Clipboard #remove Clipboard from PSBoundParameters if ($Clipboard) { #copy to clipboard with extra spaces removed $text = ($data | Out-String).Trim() -replace "\s+`n","`n" #make sure type is loaded Write-Verbose "Adding necessary .NET assembly" Add-Type -AssemblyName system.windows.forms [System.Windows.Forms.Clipboard]::SetText( $text ) #write data to the pipeline $data } Else { $PSBoundParameters.InputObject = $data Write-Verbose "Using PSBoundparameters $($PSBoundParameters | Out-String)" Tee-Object @PSBoundParameters if ($Variable) { #raise the scope to the parent or calling level to persist it Write-Verbose "Adjusting variable scope" #create a copy of the variable in the parent scope New-Variable -Name $Variable -Value $(Get-Variable -name $variable).value -Scope 1 -Force } } Write-Verbose "Ending $($MyInvocation.Mycommand)" } #end } #end function Tee-Object #define an optional alias Set-Alias -Name tmo -Value Tee-MyObject

I use Tee-MyObject much the same way as Tee-Object, even with the same parameters. Those are splatted through $PSBoundParameters. There are a few items I want to point out, more as a matter of scripting technique than anything.

First, because my function is wrapping around Tee-Object, which typically has input piped to it, I needed to temporarily store piped in objects to my function. That's why I initialize a variable, $data and add each input object to it. After everything has been piped to my function, I can pass the data to Tee-Object, assuming I'm using one of its parameters.

$PSBoundParameters.InputObject = $data
Write-Verbose "Using PSBoundparameters $($PSBoundParameters | Out-String)"
Tee-Object @PSBoundParameters

However, because I am running Tee-Object inside of a function, scope becomes an issue. When I use the –Variable parameter, Tee-Object creates the variable with no problem. But when the function ends, the variable is destroyed. My solution is to create a new copy of the variable and specify the parent scope.

if ($Variable) {
            #raise the scope to the parent or calling level to persist it
            Write-Verbose "Adjusting variable scope"
            #create a copy of the variable in the parent scope
            New-Variable -Name $Variable -Value $(Get-Variable -name $variable).value -Scope 1 -Force
        }

I needed this "tricks" in order to pass on full functionality between my wrapper function and Tee-Object.

For the clipboard part, I originally used code to simply pipe data to Clip.exe. However, this can temporary flash a console window and depending on the command I also end up with lots of extra white space at the end of each line. So I decided to use the .NET framework to add content to the clipboard. The tricky part was converting the output to text and cleaning it up. I ended up using the Trim() method to remove leading and trailing spaces after piping the data to Out-String. This leaves me with one long string. To clean up the extra space at the end of some lines, I use the –Replace operator to find anything that matches a bunch of spaces followed by the new line marker and replace it with just the new line marker. This has the effect of trimming away all those spaces.

$text = ($data | Out-String).Trim() -replace "\s+`n","`n"

And that's it! My version even has a modified copy of the help for Tee-Object.

Modified help

I can use my command in place of Tee-Object. I even defined my own alias.

Using Tee-MyObject

To be clear, I have not modified, overwritten or removed Tee-Object. I have simply created a new version of the command with some additional functionality. If you have any questions on my process or some scripting element, please post a comment.

Enjoy!


Behind the PowerShell Pipeline

Share this:

  • Click to share on X (Opens in new window) X
  • Click to share on Facebook (Opens in new window) Facebook
  • Click to share on Mastodon (Opens in new window) Mastodon
  • Click to share on LinkedIn (Opens in new window) LinkedIn
  • Click to share on Pocket (Opens in new window) Pocket
  • Click to share on Reddit (Opens in new window) Reddit
  • Click to print (Opens in new window) Print
  • Click to email a link to a friend (Opens in new window) Email

Like this:

Like Loading...

Related

4 thoughts on “Teeing Up to the Clipboard”

  1. Mike Shepard says:
    August 12, 2015 at 11:23 am

    Great example of a proxy function!

    1. Mike Shepard says:
      August 12, 2015 at 11:30 am

      Or, an almost-proxy function. 🙂

      1. Larry Weiss says:
        August 17, 2015 at 11:53 am

        It would be very interesting to see this implemented as an actual proxy function, and then we could contrast the effort needed for each approach.

      2. Jeffery Hicks says:
        August 17, 2015 at 12:34 pm

        So I tried doing this as a true proxy function. In this situation I don’t think a proxy will work. Ran into a number of issues such as required parameters and scoping that don’t have quick and easy solutions.

Comments are closed.

reports

Powered by Buttondown.

Join me on Mastodon

The PowerShell Practice Primer
Learn PowerShell in a Month of Lunches Fourth edition


Get More PowerShell Books

Other Online Content

github



PluralSightAuthor

Active Directory ADSI Automation Backup Books CIM CLI conferences console Friday Fun FridayFun Function functions Get-WMIObject GitHub hashtable HTML Hyper-V Iron Scripter ISE Measure-Object module modules MrRoboto new-object objects Out-Gridview Pipeline PowerShell PowerShell ISE Profile prompt Registry Regular Expressions remoting SAPIEN ScriptBlock Scripting Techmentor Training VBScript WMI WPF Write-Host xml

©2025 The Lonely Administrator | Powered by SuperbThemes!
%d