Powershellでクリップボードを監視しよう
windows標準環境(Powershell+ブラウザ)でクリップボードを監視しようではないかという投稿です。
背景
色々とセキュリティが厳しい昨今、PCにソフト1つ入れるのも難しい環境がお有りではないでしょうか。
クリップボード監視ツールなんてフリーソフトでcliborとかいろいろあるんですが、
そのへんが導入できない壁があり、
windows標準環境だけでクリップボード監視できるものを作ってみようと思い立ちました。
この記事の対象者
- 会社でWindows使っている方+なかなかソフトが自由に入れられない方
- Powershellでクリップボード監視する方法に興味がある方
今回作るもの
フォルダ構成
■フォルダ
│ backup.bat
│ clipboard-monitor.ps1
│ clipboard.html
│ start.bat
│
├─backup
│ yyyymmdd_HHMMSS_clipboard.html
│
└─tmpl
clipboard.html
ソース一覧
主処理起動用のバッチ(start.bat)
powershellを呼び出すバッチを作っておきます
@echo off
mode 35,10
color 1F
powershell -command "&{$h=Get-Host;$w=$h.UI.RawUI;$s=$w.BufferSize;$s.height=3000;$w.BufferSize=$s;}"
powershell -ExecutionPolicy RemoteSigned .\clipboard-monitor.ps1
補足
- 画面幅を縦横指定
- カラーはお好みで...
- 画面幅を指定するとバッファサイズまで画面幅と同じになってしい都合が悪いので、powershellでバッファを3000行取る
主処理 監視バッチ(clipboard-monitor.ps1)
次にstart.bat
から呼ばれるpowershellです。
ソースコード
# set window title
(Get-Host).UI.RawUI.WindowTitle = $MyInvocation.MyCommand
# get clipboard text
Add-Type -AssemblyName System.Windows.Forms
$clipText = [Windows.Forms.Clipboard]::GetText()
$dateFormat = Get-Date -Format "yyyyMMdd"
#Write-Host $clipText
Write-Host ""
Write-Host " ■■■■■■■■■■■■"
Write-Host " ■"
Write-Host " ■ CLIP BOARD START!"
Write-Host " ■"
Write-Host " ■■■■■■■■■■■■"
Write-Host ""
while ($true) {
# check the data, whether it do change or not.
$latestClipText = [Windows.Forms.Clipboard]::GetText()
if ($latestClipText -ne $clipText) {
# if changed, update data.
$clipText = $latestClipText
# output console.
Write-Host "- - -"
Write-Host $clipText
# write data to history file.
$baseClipText = ${clipText}
$baseClipText = ${baseClipText} -replace "&", "&"
$baseClipText = ${baseClipText} -replace "'", "'"
$baseClipText = ${baseClipText} -replace "``", "`"
$baseClipText = ${baseClipText} -replace '"', """
$baseClipText = ${baseClipText} -replace "<", "<"
$baseClipText = ${baseClipText} -replace ">", ">"
$baseClipText = ${baseClipText} -replace "\r\n", "<br/>"
$baseClipText = ${baseClipText} -replace "\n", "<br/>"
$time = Get-Date -Format "yyyy/MM/dd HH:mm:ss"
$fileOutput = "<tr class='cb-tr-clip'><td><span>${time}</span></td><td>${baseClipText}</td></tr>"
Write-Output ${fileOutput} | Add-Content "clipboard.html" -Encoding UTF8
}
# 1秒スリープ
Start-Sleep -Seconds 1
}
補足
- HTMLファイル出力するので、手前でescape処理を挟んでいます。
- 1秒おきにクリップボードを監視して、変更があればHTML(clipboard.html)に書き込みに行っています。
クリップボードテキスト出力先ファイル(clipboard.html)
出力先のHTMLファイルです。
ブラウザで表示した時に確認しやすいようにスタイルをJS処理を書いています。
ソースコード
<head>
<!-- Required meta tags -->
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Bootstrap CSS -->
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous"
/>
<!-- fontawesome -->
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.9.0/css/all.min.css"
integrity="sha256-UzFD2WYH2U1dQpKDjjZK72VtPeWP50NoJjd26rnAdUI="
crossorigin="anonymous"
/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.css" rel="stylesheet" />
<title>Clip4Win</title>
<style>
.header-title-text {
font-size: 5rem;
letter-spacing: -5px;
line-height: 140px;
font-weight: bold;
color: white;
}
.sticky_table thead th {
/* 縦スクロール時に固定する */
position: -webkit-sticky;
position: sticky;
top: -1px;
/* tbody内のセルより手前に表示する */
z-index: 10;
background-color: black;
}
.sticky_table tbody td span {
/* 縦スクロール時に固定する */
position: -webkit-sticky;
position: sticky;
top: 55px;
z-index: 9;
}
.cb-tr-clip:hover {
color: #ccc !important;
}
.cb-reload {
position: fixed;
bottom: 100px;
right: 100px;
cursor: pointer;
}
input[type="checkbox"]{
display: none;
}
input[type="checkbox"] ~ label {
border: solid 1px #CCC;
overflow: hidden;
width: 50px;
white-space: nowrap;
}
input[type="checkbox"] ~ label::before {
content: "OFF";
cursor: pointer;
width: 50px;
text-align: center;
background-color: gray;
border-right: solid 10px #FFF;
display: inline-block;
transition-duration: 0.1s;
float: left;
}
input[type="checkbox"]:checked ~ label::before {
content: "OFF";
cursor: pointer;
display: inline-block;
transform: translate(-40px, 0px);
}
input[type="checkbox"] ~ label::after {
content: "ON";
cursor: pointer;
width: 50px;
text-align: center;
background-color: green;
border-right: solid 10px #FFF;
display: inline-block;
transition-duration: 0.1s;
}
input[type="checkbox"]:checked ~ label::after {
content: "ON";
cursor: pointer;
background-color: green;
display: inline-block;
transform: translate(-40px, 0px);
}
</style>
</head>
<body class="container bg-dark text-white" onload="init()">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"
></script>
<script
src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"
></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script>
function init() {
document.querySelectorAll(".cb-tr-clip").forEach(function (el) {
// console.log("innerHTML > " + el.getElementsByTagName("td")[1].innerHTML);
// console.log("innerText > " + el.getElementsByTagName("td")[1].innerText);
// console.log("textContent > " + el.getElementsByTagName("td")[1].textContent);
//el.getElementsByTagName("td")[1].textContent = escapeHtml(el.getElementsByTagName("td")[1].innerHTML);
el.getElementsByTagName("td")[1].dataset.text = el.getElementsByTagName("td")[1].innerText;
el.getElementsByTagName("td")[1].setAttribute("title", el.getElementsByTagName("td")[1].innerText);
el.getElementsByTagName("td")[1].innerText = tripleReader(el.getElementsByTagName("td")[1].innerText);
el.addEventListener("click", function () {
// console.log("click");
const text = el.getElementsByTagName("td")[1].dataset.text;
if (execCopy(text)) {
// alert("Copied!!");
toastr.options = {
positionClass: "toast-bottom-right",
timeOut: "2000",
};
toastr.success("Success to Copy");
} else if (window.getSelection) {
alert("Failed to copy...");
}
});
});
document.querySelector("#reload").addEventListener("click", function () {
window.location.reload();
});
var tbody = $("table tbody");
tbody.html($("tr", tbody).get().reverse());
document.querySelector("#check").checked = localStorage.getItem("isChecked") == "false" ? false : true;
if (document.querySelector("#check").checked) {
setInterval(function() {
window.location.reload();
}, 10000);
}
document.querySelector("#check").addEventListener("change", function() {
let isChecked = document.querySelector("#check").checked;
console.log("isChecked:" + isChecked);
localStorage.setItem("isChecked",isChecked);
setTimeout(function(){window.location.reload();},1000);
});
// document.querySelector(".js-change-persec").addEventListener("change", function() {
// let ps = Number(document.querySelector(".js-change-persec").value);
// if(!Number(ps)){
// return;
// }
// localstorage.setItem("persec",ps);
// toastr.success("Success to set Reload Span.");
// });
}
/**
* コピー処理
*/
function execCopy(string) {
// console.log("target string: " + string);
// 空div 生成
var tmp = document.createElement("div");
// 選択用のタグ生成
var pre = document.createElement("pre");
// 親要素のCSSで user-select: none だとコピーできないので書き換える
pre.style.webkitUserSelect = "auto";
pre.style.userSelect = "auto";
tmp.appendChild(pre).textContent = string;
// 要素を画面外へ
var s = tmp.style;
s.position = "fixed";
s.right = "200%";
// body に追加
document.body.appendChild(tmp);
// 要素を選択
document.getSelection().selectAllChildren(tmp);
// クリップボードにコピー
var result = document.execCommand("copy");
// 要素削除
document.body.removeChild(tmp);
return result;
}
function tripleReader(text, count) {
if (!count) count = 30; // default 20
if (text.length > count) {
text = text.split("\n")[0].substr(0, count) + "…";
}
return text;
}
// /**
// * HTMLエスケープ処理
// */
// function escapeHtml(string) {
// if (typeof string !== "string") {
// return string;
// }
// return string.replace(/[&'`"<>]/g, function (match) {
// return {
// "&": "&",
// "'": "'",
// "`": "`",
// '"': """,
// "<": "<",
// ">": ">",
// }[match];
// });
// }
</script>
<div class="d-flex align-items-center justify-content-center w-100" style="height: 200px;">
<h1 class="header-title-text">CLIPBOARD</h1>
</div>
<div class="cb-reload">
<div class="d-flex flex-column">
<!--<div class="my-2">
<input type="text" class="form-control js-change-persec" style="width:50px;" placeholder="sec">
</div>-->
<div>
<input type="checkbox" id="check" />
<label for="check" title="Auto Reload (default: per 10seconds)"></label>
</div>
<i id="reload" class="fas fa-sync-alt fa-3x"></i>
</div>
</div>
<table class="w-100 table table-hover text-white cb-table-clip sticky_table table-sm">
<thead class="cb-th-clip">
<th style="width: 200px;">日付</th>
<th>コンテンツ</th>
</thead>
テンプレートファイル(clipboard.html)
./tmpl/clipboard.html に、clipboard.htmlと同じファイルをおいておきます。
バックアップバッチ(backup.bat)
バックアップを取得するためのバッチです。
出力先のclipboard.html
が肥大化してきたら実行
ソースコード
@echo off
set date_str=%date:~-10,4%%date:~-5,2%%date:~-2,2%
rem echo %date_str%@
set time_str=%time: =0%
set time_str=%time_str:~0,2%%time_str:~3,2%%time_str:~6,2%
rem echo %time_str%
echo F | xcopy /Y clipboard.html backup\%date_str%_%time_str%_clipboard.html
xcopy /Y tmpl\clipboard.html clipboard.html
バックアップフォルダ(backup)
- バックアップバッチの出力先として作成しておく!
実行方法
上記のstart.bat
を実行するだけでOK!
実行イメージ
ブラウザからclipboard.html
を確認
- 一覧で履歴を確認できる
- クリックでコピーができる
- マウスホバーでテキスト全体を表示できる
Github
以下に完成形のソースを上げています。
よろしければご確認ください。
さいごに
すごく走り書きですが以上です。
ご参考になれば幸いです!