๐ Clipboard Vault (PowerShell 7.5)
Overview
A powerful clipboard manager for PowerShell 7.5. It captures everything you copy and provides a rich, searchable history through an interactive HTML interface. It's designed to be a "vault" for your clipboard, preventing data loss and making it easy to find previously copied items.
Functionality
- Live Clipboard Monitoring: Continuously captures text copied to the clipboard.
- Interactive HTML Viewer: Automatically generates a
Clipboard-Viewer.htmlfile to browse, search, and manage your history. - Intelligent De-duplication: When you re-copy an item, it's moved to the top of the history instead of creating a duplicate entry.
- Powerful Search: The HTML viewer supports real-time searching of your clipboard history, including regular expressions.
- One-Click Copy: Easily copy any item from the history back to your clipboard.
- Database Cleanup: A feature in the viewer allows you to remove all historical duplicates, keeping only the most recent unique entries.
- JSON Storage: History is stored in a clean, human-readable
clipboard_history.jsonfile.
Usage
- Ensure
Clipboard-Manager.ps1andClipboard-Functions.ps1are in the same directory. - Open a PowerShell 7 terminal.
- Navigate to the script's directory.
- Run the script:
.\Clipboard-Manager.ps1 - The script will start monitoring. To view your history, open the
Logs\Clipboard-Viewer.htmlfile that is created in the script's directory. The viewer updates as you copy new items. - To stop the script, press
Ctrl+Cin the PowerShell window.
Requirements
- PowerShell 7.5 or later
- Windows operating system
Code Example
# Configuration
$LogDir = "$PSScriptRoot\Logs"
$ClipboardLogFile = "$LogDir\clipboard_history.json"
$MaxHistoryItems = 50
# Ensure log directory exists
if (!(Test-Path $LogDir)) { New-Item -ItemType Directory -Path $LogDir | Out-Null }
Write-Host "--- Clipboard Vault Active ---" -ForegroundColor Cyan
Write-Host "Press 'Ctrl+C' in this window to stop monitoring." -ForegroundColor Gray
function Update-HtmlViewer {
param($History)
$HtmlPath = "$PSScriptRoot\Logs\Clipboard-Viewer.html"
$Header = @"
<!DOCTYPE html>
<html>
<head>
<title>Clipboard Vault Viewer</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: <a href='/?search=%231a1a1a' class='obsidian-tag'>#1a1a1a</a>; color: <a href='/?search=%23e0e0e0' class='obsidian-tag'>#e0e0e0</a>; padding: 20px; }
.entry { background: <a href='/?search=%232d2d2d' class='obsidian-tag'>#2d2d2d</a>; border-radius: 8px; padding: 15px; margin-bottom: 10px; border: 1px solid <a href='/?search=%233d3d3d' class='obsidian-tag'>#3d3d3d</a>; transition: 0.2s; }
.entry:hover { border-color: <a href='/?search=%2300ff9d' class='obsidian-tag'>#00ff9d</a>; }
.meta { font-size: 0.8em; color: #888; margin-bottom: 8px; display: flex; justify-content: space-between; }
.content { font-family: 'Cascadia Code', monospace; white-space: pre-wrap; background: #121212; padding: 10px; border-radius: 4px; font-size: 0.9em; }
.copy-btn { background: <a href='/?search=%2300ff9d' class='obsidian-tag'>#00ff9d</a>; color: <a href='/?search=%231a1a1a' class='obsidian-tag'>#1a1a1a</a>; border: none; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-weight: bold; }
.copy-btn:active { transform: scale(0.95); }
input { width: 100%; padding: 10px; border-radius: 5px; border: 1px solid <a href='/?search=%233d3d3d' class='obsidian-tag'>#3d3d3d</a>; background: <a href='/?search=%232d2d2d' class='obsidian-tag'>#2d2d2d</a>; color: white; margin-bottom: 20px; }
</style>
</head>
<body>
<h2>๐ Clipboard Vault</h2>
<div style="margin-bottom: 15px; display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="hideDupes" style="width: auto; margin: 0;" onchange="filter()">
<label for="hideDupes">Hide Duplicate Content</label>
</div>
<button class="copy-btn" style="background: <a href='/?search=%23ff4d4d' class='obsidian-tag'>#ff4d4d</a>;" onclick="requestCleanup()">๐๏ธ DELETE DUPLICATES</button>
<input type="text" id="search" placeholder="Search clips (Regex supported)..." onkeyup="filter()">
<div id="container">
"@
$Body = ""
foreach ($Item in $History) {
$EscapedContent = [System.Web.HttpUtility]::HtmlEncode($Item.Content)
$Body += @"
<div class="entry" data-hash="$($Item.Hash)">
<div class="meta">
<span>$($Item.Timestamp)</span>
<button class="copy-btn" onclick="copyToClipboard(this)">COPY</button>
</div>
<div class="content">$EscapedContent</div>
</div>
"@
}
$Footer = @"
</div>
<script>
function requestCleanup() {
// We create a tiny "flag" file that the PowerShell script will see
// This is a clever way for a browser to 'talk' to a local script
const blob = new Blob(['cleanup'], {type: 'text/plain'});
const anchor = document.createElement('a');
anchor.download = 'cleanup.req'; // The extension the script looks for
anchor.href = window.URL.createObjectURL(blob);
anchor.click();
alert('Cleanup request sent! PowerShell will process this in a few seconds.');
}
</script>
<script>
function copyToClipboard(btn) {
const text = btn.parentElement.nextElementSibling.innerText;
navigator.clipboard.writeText(text);
const original = btn.innerText;
btn.innerText = 'โ
SAVED';
setTimeout(() => btn.innerText = original, 1000);
}
function filter() {
const query = document.getElementById('search').value;
const hideDupes = document.getElementById('hideDupes').checked;
const entries = document.querySelectorAll('.entry');
const regex = new RegExp(query, 'i');
const seenHashes = new Set();
entries.forEach(entry => {
const text = entry.querySelector('.content').innerText;
const hash = entry.getAttribute('data-hash');
let isMatch = regex.test(text);
let isDuplicate = seenHashes.has(hash);
// Mark hash as seen
seenHashes.add(hash);
// Logic: Show if it matches search AND (if hideDupes is on, it must not be a duplicate)
if (isMatch && (!hideDupes || !isDuplicate)) {
entry.style.display = 'block';
} else {
entry.style.display = 'none';
}
});
}
</script>
</body>
</html>
"@
$Header + $Body + $Footer | Out-File $HtmlPath -Force
}
$LastContent = ""
# The "Engine"
try {
while ($true) {
# Fetch clipboard text (PS 7.5 compatible)
$CurrentContent = Get-Clipboard -Raw -ErrorAction SilentlyContinue
# Inside your while ($true) loop, at the very beginning:
$ReqFile = "$LogDir\cleanup.req"
if (Test-Path $ReqFile) {
Write-Host "๐งน Cleanup Requested! Removing duplicates..." -ForegroundColor Yellow
if (Test-Path $ClipboardLogFile) {
$CurrentHistory = Get-Content $ClipboardLogFile | ConvertFrom-Json
# This magic line keeps only the newest unique hash
$CleanedHistory = $CurrentHistory | Group-Object Hash | ForEach-Object { $_.Group | Select-Object -First 1 }
# Sort back by time (Newest first)
$CleanedHistory = $CleanedHistory | Sort-Object Timestamp -Descending
# Save and Update
$CleanedHistory | ConvertTo-Json | Out-File $ClipboardLogFile -Force
Update-HtmlViewer -History $CleanedHistory
}
Remove-Item $ReqFile -ErrorAction SilentlyContinue
Write-Host "โจ Database Cleaned." -ForegroundColor Green
}
# Inside your while($true) loop:
if ($null -ne $CurrentContent -and $CurrentContent -ne $LastContent) {
$LastContent = $CurrentContent
$History = if (Test-Path $ClipboardLogFile) { Get-Content $ClipboardLogFile | ConvertFrom-Json } else { @() }
# Create a unique ID based on the content so we can identify duplicates
$ContentHash = [Convert]::ToBase64String([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($CurrentContent)))
# Remove the old version if it exists (so it "jumps" to the top instead of duplicating)
$History = $History | Where-Object { $_.Hash -ne $ContentHash }
$NewEntry = [PSCustomObject]@{
Timestamp = (Get-Date).ToString("HH:mm:ss")
Hash = $ContentHash # Add this!
Content = $CurrentContent.Trim()
}
$History = ,$NewEntry + $History | Select-Object -First $MaxHistoryItems
$History | ConvertTo-Json | Out-File $ClipboardLogFile -Force
Update-HtmlViewer -History $History
}
# Check if it's new, not null, and not just empty whitespace
if ($null -ne $CurrentContent -and $CurrentContent -ne $LastContent -and !([string]::IsNullOrWhiteSpace($CurrentContent))) {
$LastContent = $CurrentContent
# Load existing history
$History = if (Test-Path $ClipboardLogFile) {
Get-Content $ClipboardLogFile | ConvertFrom-Json
} else { @() }
# Create a new Entry
$NewEntry = [PSCustomObject]@{
Timestamp = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
Content = $CurrentContent.Trim()
}
# Add to history (Newest at top)
$History = ,$NewEntry + $History | Select-Object -First $MaxHistoryItems
# Save to JSON
$History | ConvertTo-Json -Depth 3 | Out-File $ClipboardLogFile -Force
# Update HTML Viewer
Update-HtmlViewer -History $History
# Visual Feedback
$Display = if ($CurrentContent.Length -gt 40) { $CurrentContent.Substring(0,40).Trim() + "..." } else { $CurrentContent.Trim() }
Write-Host "[$(Get-Date -Format HH:mm:ss)] ๐ Captured: $Display" -ForegroundColor Green
}
Start-Sleep -Milliseconds 500
}
}
catch {
Write-Error "Vault Error: $($_.Exception.Message)"
}
finally {
Write-Host "`nVault Closed." -ForegroundColor Yellow
}