13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Powershellでクリップボードを監視しよう

Last updated at Posted at 2020-04-09

Powershellでクリップボードを監視しよう

windows標準環境(Powershell+ブラウザ)でクリップボードを監視しようではないかという投稿です。

背景

色々とセキュリティが厳しい昨今、PCにソフト1つ入れるのも難しい環境がお有りではないでしょうか。
クリップボード監視ツールなんてフリーソフトでcliborとかいろいろあるんですが、
そのへんが導入できない壁があり、
windows標準環境だけでクリップボード監視できるものを作ってみようと思い立ちました。

この記事の対象者

  • 会社でWindows使っている方+なかなかソフトが自由に入れられない方
  • Powershellでクリップボード監視する方法に興味がある方

今回作るもの

overview.png

フォルダ構成

■フォルダ
│  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 "<", "&lt;"
    $baseClipText = ${baseClipText} -replace ">", "&gt;"
    $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 {
    //       "&": "&amp;",
    //       "'": "&#x27;",
    //       "`": "&#x60;",
    //       '"': "&quot;",
    //       "<": "&lt;",
    //       ">": "&gt;",
    //     }[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!

実行イメージ

image.png

ブラウザからclipboard.htmlを確認

image.png

  • 一覧で履歴を確認できる
  • クリックでコピーができる
  • マウスホバーでテキスト全体を表示できる

Github

以下に完成形のソースを上げています。
よろしければご確認ください。

さいごに

すごく走り書きですが以上です。
ご参考になれば幸いです!

13
14
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?