Behind the PowerShell Pipeline logo

Behind the PowerShell Pipeline

Subscribe
Archives
October 22, 2024

Tool Time - Clipboard History

A reminder that all previous articles can be found in the online archive. Premium subscribers have full access.

As I've worked with PowerShell over the years, I've realized that the reason for writing PowerShell code generally falls into two camps. One outcome is to achieve a desired result. For example, you may write PowerShell code to provision a new user account in Active Directory. I tend to think this is the purpose of many PowerShell scripts, although functions and cmdlets can fall into this category as well. Something as simple as Start-Service is a cmdlet that achieves a desired result.

The other category is PowerShell code as a tool. Perhaps another way to consider this is as a utility like nslookup or ipconfig. I think an argument could be made that Get- commands fall into this group. Another good example of a PowerShell tool is your prompt function. Since I spend a lot of time working from a PowerShell prompt, I have written a number of PowerShell tools to help me work more efficiently. Recently, I've added a few new tools to my toolbox that I'd like to share with you. If nothing else, you might pick up a scripting tip or trick that you can use in your projects.

Clipboard History

Windows systems have a clipboard history feature that I keep forget to take advantage of. Press Windows+V to see a list of items you've copied to the clipboard. Although, you might need to turn it on.

Turn on clipboard history
figure 1

Once you've added some items, you can see them in the clipboard history.

Clipboard history
figure 2

You can click on an item and Windows will insert it the cursor location of the active application. I wanted to find a way to access this clipboard history from PowerShell. The Get-Clipboard cmdlet only retrieves the last copied content. I wanted to see the entire history, which is limited to 25 items.

Windows Runtime and WinMetadata

To be honest, I had no idea of where to even begin. I assumed I would need to use .NET classes and methods, but I'm not a .NET developer. I decided to take advantage of AI and asked CoPilot to write some PowerShell code to achieve my goal. The code was complicated and didn't work in PowerShell 7, which is my primary environment. I kept digging and searching. I read a lot of stuff from StackOverflow. Eventually, I started to get the picture.

The clipboard history feature required use of the WindowsRuntime assembly. Typically, this command should get the job done.

Add-Type -AssemblyName System.Runtime.WindowsRuntime

In PowerShell 7, this looks like it works. But it actually doesn't. During code development, run the command like this:

Add-Type -AssemblyName System.Runtime.WindowsRuntime -PassThru

Now, I see that this assembly isn't supported.

Unsupported .NET assembly
figure 3

A little more research confirmed that this class is not supported in PowerShell 7. It requires the full .NET Framework. Remember, PowerShell 7 is running on .NET Core. Here's a helper function, i.e. another tool, that will tell you what .NET version you are using in a given PowerShell session.

Function Get-RuntimeInformation {
    [cmdletbinding()]
    [OutputType('PSRunTimeInformation')]
    [alias('rti')]
    Param()

    [PSCustomObject]@{
        PSTypeName       = 'PSRuntimeInformation'
        PSVersion        = $PSVersionTable.PSVersion
        Framework        = [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription
        FrameworkVersion = [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription.Replace('.NET', '').Trim()
        OSVersion        = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription
        Architecture     = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture
        Computername     = [Environment]::MachineName
    }
}

Here's an example from PowerShell 7

PS C:\> Get-RuntimeInformation

PSVersion        : 7.5.0-preview.4
Framework        : .NET 9.0.0-preview.6.24327.7
FrameworkVersion : 9.0.0-preview.6.24327.7
OSVersion        : Microsoft Windows 10.0.26100
Architecture     : X64
Computername     : JEFFDESK

And from Windows PowerShell

PS C:\> get-RuntimeInformation


PSVersion        : 5.1.26100.2152
Framework        : .NET Framework 4.8.9277.0
FrameworkVersion : Framework 4.8.9277.0
OSVersion        : Microsoft Windows 10.0.26100
Architecture     : X64
Computername     : JEFFDESK

Now, I know that I need to use Windows PowerShell 5.1.

Adding WindowsRuntime in Windows PowerShell
figure 4

The other piece of information I learned I would need is the use of WinMetadata assemblies. I believe these are used in UWP (Universal Windows Platform) applications. These are files with a .winmd extension. You can find them in the C:\Windows\System32\WinMetadata folder. Here's an example of what you might see.

PS C:\> dir C:\windows\system32\WinMetadata\*.winmd


    Directory: C:\windows\system32\WinMetadata


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         9/30/2024  11:08 PM          58888 Windows.AI.winmd
-a----        10/15/2024   8:43 AM         893040 Windows.ApplicationModel.winmd
...

My sample code indicated I needed to use the Windows.ApplicationModel assembly. However, you can't just load these assemblies with Add-Type, even in Windows PowerShell.

Fail to add .winmd assembly
figure 5

My research dug up this bit of code.

$null = [Windows.ApplicationModel.DataTransfer.Clipboard, Windows.ApplicationModel.DataTransfer, ContentType = WindowsRuntime]

If I try this without sending it to $null, I can see that it does something.

Add DataTransfer.Clipboard
figure 6

I have never seen syntax like this, and even though it does something, I didn't know what that was. Years ago, I would have had to bug one of my .NET developer friends to explain it to me. But now, I have CoPilot. I asked it to explain the syntax to me.

Explain what this line of code is doing: [Windows.ApplicationModel.DataTransfer.Clipboard, Windows.ApplicationModel.DataTransfer, ContentType = WindowsRuntime]

This line of code is actually a metadata attribute applied to a class or method in a Windows Runtime (WinRT) environment, like a UWP (Universal Windows Platform) app. Here’s the breakdown:

  • Windows.ApplicationModel.DataTransfer.Clipboard: This refers to the clipboard API provided by the Windows Runtime. It's used to interact with the system clipboard, enabling apps to cut, copy, and paste data.
  • Windows.ApplicationModel.DataTransfer: This is the namespace that contains classes for working with data transfer operations, such as clipboard and drag-and-drop.
  • ContentType = WindowsRuntime: This indicates that the content type is specific to the Windows Runtime, which is a platform for Windows apps that provides a common API layer.

Effectively, this attribute specifies that the clipboard functionality from the Windows.ApplicationModel.DataTransfer namespace should be used, and that the content type is suitable for the Windows Runtime.

I'll be honest, there's no way I would have figured this out on my own. But I know enough about .NET to understand this explanation and began to visualize what the code snippets were doing. I also have enough experience to turn them into PowerShell code.

Asynchronous Operations

Now it gets really interesting.I knew from my research I need to use [Windows.ApplicationModel.DataTransfer.Clipboard] to access the clipboard history.

DataTransfer.Clipboard methods
figure 7

This screen shot confirms what I have in my suggested code snippets.

PS C:\> [Windows.ApplicationModel.DataTransfer.Clipboard]::GetHistoryItemsAsync

OverloadDefinitions
-------------------
static Windows.Foundation.IAsyncOperation[Windows.ApplicationModel.DataTransfer.ClipboardHistoryItemsResult]
GetHistoryItemsAsync()

The output is going to be an asynchronous operation.

Want to read the full issue?
GitHub Bluesky LinkedIn About Jeff
Powered by Buttondown, the easiest way to start and grow your newsletter.