'Set-Clipboard only remembering the last value when called in rapid succession
I am trying to create a powershell snippet that will copy the first column of a multi-line piped input to clipboard.
The intended usage is: kubectl get pods | copyfirst
.
This should allow me to have all pod names in the clipboard, and use Win+V to select the individual pod name that I need.
What I have so far is:
function copyfirst {
[CmdletBinding()]Param([Parameter(ValueFromPipeline)]$Param)
process {
$Param.Split(" ")[0] | Set-Clipboard
}
}
The problem is - this only copies the last entry to clipboard, while all the others are ignored.
If I change Set-Clipboard
to some other command - it works as intended. For example echo
outputs all pod names, not just the last one.
Solution 1:[1]
I think mklement0's answer was the right one to begin with and I personally was not aware of this Win + V clipboard functionality. So, you were right, as it seems it can't capture the history when done in rapid succession.
By adding Start-Sleep
it works fine:
function copyfirst {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[object[]] $Param
)
process {
$Param.Split(' ')[0] | Set-Clipboard
Start-Sleep -Milliseconds 250
}
}
@'
string1 string4
string2 string5
string3 string6
'@ -split '\r?\n' | copyfirst
It should capture string1
, string2
and string3
.
Tweak the sleep timer until it's not too slow and it can capture everything.
After some testing, seems like Start-Sleep
can be reduced to -Milliseconds 250
, lower than that would produce inconsistent results.
Solution 2:[2]
Note:
This answer shows how to copy the first whitespace-separated field across all input lines as a single clipboard entry.
As it turns out, the OP's intent was to copy each such field separately to the clipboard, to create a succession of entries that can be recalled via the Windows 10 clipboard-history function (WinKey+V), for which Santiago Squarzon's helpful answer provides a solution.
To copy data from all input objects, you must collect it in the process
block and copy the collection to the clipboard in the end
block, after all input objects have been processed:
function copyfirst {
[CmdletBinding()]Param([Parameter(ValueFromPipeline)]$Param)
begin {
# Initialize the collection (list) that will collect data.
$coll = [System.Collections.Generic.List[object]]::new()
}
process {
# Add to the collection.
$coll.Add($Param.Split(" ")[0])
}
end {
# Copy the collection to the clipboard.
# More efficient alternative:
# Set-Clipboard $coll
$coll | Set-Clipboard
}
}
The process
block is called for each input object, and Set-Clipboard
always replaces the previous content on the clipboard, which explains why only the last data item was placed there.
Note that stdout output from external programs is passed line by line through the pipeline.
Note that in your case there is a simpler alternative using a simple (non-advanced) function and the automatic $input
variable:
function copyfirst {
$input | ForEach-Object { $_.Split(' ')[0] } | Set-Clipboard
}
The potential downside of this approach is the loss of the streaming aspect of the pipeline (one-by-one processing), because a simple function's body is like an implicit end
block, in which PowerShell provides the collected-up-front input objects via $input
.
However, in this case, that doesn't matter, because you need to collect all data anyway.
You could speed up the operation a bit by using the .ForEach()
array method instead of the ForEach-Object
cmdlet, though I doubt that it matters in this case.
Solution 3:[3]
What I've found is that the clipboard would be overwritten when the set-clipboard
is used in the same process. There is a -append
but that appends to the last clipboard value rather than adding to the clipboard list.
What I ended up doing was building a command and executing it for each item in the array under a new process (which is slow!) as below:
function copyfirst {
[CmdletBinding()]Param([Parameter(ValueFromPipeline)]$Param)
foreach($line in $Param.split("`n")){
$podName = $line.split(" ")[0]
if ($podName -ne "NAME"){
$scriptBlock = "Write-Output `"$podName`" | Set-Clipboard; Start-sleep -milliseconds 1"
Powershell $scriptBlock
}
}
}
"NAME READY STATUS RESTARTS AGE
redis-fd794cd65-k9mhp 1/1 Running 0 3h15m
website-restrictor-c6f5bbd56-fxb59 1/1 Running 0 3h15" | copyfirst
This results in individual clipboard entries as seen when using Windows Key + V
Note the Start-sleep -milliseconds 1
this is because of some weird behavior when executing the set-clipboard
cmdlet very quickly, its noted here - https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/set-clipboard?view=powershell-7.1#notes
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | |
Solution 3 |