【PowerShell】スクリーンショットを自動でPowerPointに貼り付ける(作業証跡取得など)

スポンサーリンク
PowerPoint操作
スポンサーリンク

作業証跡のためのScreenShotを自動でパワーポイントに貼り付けられるようにしたい

Windowsにはデフォルトのスクリーンショット取得機能(PrintScreen、snipping tool)があるので、作業証跡の取得には困らないが、それの整理となると少し面倒ではないだろうか。

スクリーンショットのファイル命名規則は「スクリーンショット yyyy-mm-dd hhMMss」となっている。数が少ないうちはこれでも良いが、ファイル数が増えるとどのファイルがどの作業項番にあたるものなのか、後から追うのはちょっと面倒だ。(少なくとも私は)

Excelの適当なシートに次々と貼り付けていくのも手だが、Excelだとサムネイル表示等がないので、それこそキャプチャ画面が多くなるとどの画面がどこにあるのか、遡るのが大変になる。ファイル名で検索もできないので、貼り付けるたびに別のセルに取得日時や作業項番を手動で控えておかないといけない。

そこで、スクリーンショットを取るたびに自動でPowerPointの新規スライドにスクショ画像とスクショ取得日時、必要に応じてカスタムテキストを貼り付けられるスクリプトを作成した。
PowerPointならば、1つのファイル(.ppt/.pptx)にまとめつつサムネイル表示が可能であり、かつ取得した画像の修正も容易であり、後から画像を別ファイルへ流用することも容易だ。

搭載機能

  • スクリーンショットの取得が行われるたびに、予め開いているPowerPointの末尾に新規スライドを追加し、スクショ画像とスクショ取得日時、指定があればカスタムテキストを貼り付ける。
イメージ
  • 貼り付け先となるPowerPointファイルは、指定することと新規作成すること双方に対応する。
  • カスタムテキストは作業項番や作業メモとして利用することを想定している。そのため、毎回カスタムテキストの指定をさせるのではなく、カスタムテキストを入力したい時だけカスタムテキストの入力フォームを開くようにしている。カスタムテキストの入力を求められない場合は、スクショ画像とスクショ取得日時のみをPowerPointのスライドに貼り付ける。

コード

絶対改善余地があるけどこんな感じ。
今回、機能としてはシンプルだがGUIを多様している都合上かなり長文となっている。

# アセンブリ読み込み
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

### ユーザー定義 ###

# sleep間隔
[int32]$sleepTime = 100

# loggerの設定
function logger($msg) {
    Write-Host (Get-Date -Format "u")"`t"$msg
}

# COMオブジェクト格納配列
[int32]$arrayUbound = 9
[Object[]]$coms = 0..$arrayUbound
for ([int32]$i=0;$i -le $arrayUbound;$i++){
    $coms[$i] = $null
}

# PowerPoint操作用配列
New-Variable -Name ppApp    -Value 0 -Option ReadOnly -Description 'PowerPoint.Application'
New-Variable -Name ppPress  -Value 1 -Option ReadOnly -Description 'PowerPoint.Presentations'
New-Variable -Name ppPres   -Value 2 -Option ReadOnly -Description 'PowerPoint.Presentation'
New-Variable -Name ppSlds   -Value 3 -Option ReadOnly -Description 'PowerPoint.Slides'
New-Variable -Name ppSld    -Value 4 -Option ReadOnly -Description 'PowerPoint.Slide'
New-Variable -Name ppShps   -Value 5 -Option ReadOnly -Description 'PowerPoint.Shpaes'
New-Variable -Name ppPic    -Value 6 -Option ReadOnly -Description 'PowerPoint.Shape Pasted Picture'
New-Variable -Name ppTitle  -Value 7 -Option ReadOnly -Description 'PowerPoint.Shpae Title PlaceHolder'
New-Variable -Name ppTxtFrm -Value 8 -Option ReadOnly -Description 'PowerPoint.TextFrame Title'
New-Variable -Name ppTxtRng -Value 9 -Option ReadOnly -Description 'PowerPoint.TextRange Title'

# PowerPoint定数(https://learn.microsoft.com/ja-jp/office/vba/api/powerpoint.ppslidelayout)
New-Variable -Name ppLayoutTitleOnly -Value 11 -Option ReadOnly -Description 'PowerPoint.PpSlideLayout.ppLayoutTitleOnly'

# C言語でClipboardの更新ごとに処理を行うクラスの定義
Add-Type -TypeDefinition @'
    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    public class ClipboardListeningForm : Form
    {
        [DllImport("user32.dll", SetLastError = true)]
        private extern static void AddClipboardFormatListener(IntPtr hwnd);

        [DllImport("user32.dll", SetLastError = true)]
        private extern static void RemoveClipboardFormatListener(IntPtr hwnd);

        public event EventHandler ClipboardUpdate;
        public const int WM_CLIPBOARDUPDATE = 0x031D;

        protected override void OnLoad(EventArgs e)
        {
            AddClipboardFormatListener(Handle);
            base.OnLoad(e);
        }

        protected override void Dispose(bool disposing)
        {
            RemoveClipboardFormatListener(Handle);
            base.Dispose(disposing);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_CLIPBOARDUPDATE)
            {
                OnClipboardUpdate(EventArgs.Empty);
                m.Result = IntPtr.Zero;
            }
            else
                base.WndProc(ref m);
        }

        protected virtual void OnClipboardUpdate(EventArgs e)
        {
            EventHandler handler = ClipboardUpdate;
            if (handler != null)
            {
                handler(this, e);
            }
        }            
    }
'@ -ReferencedAssemblies System.Windows.Forms

######


### FilePathフォーム要素 ###

# 追記/貼り付けするpowerpointファイル指定の初期化
$PastePowerpointFile = $null

# FilePathフォーム作成
$FilePathForm = New-Object System.Windows.Forms.Form 
$FilePathForm.Size = New-Object System.Drawing.Size(500,170) 
$FilePathForm.Text = "ScreenshotToPowerPoint"

# FilePathラベル作成
$LabelFilePath = New-Object System.Windows.Forms.Label
$LabelFilePath.Location = New-Object System.Drawing.Point(10,10)
$LabelFilePath.AutoSize = $true
$LabelFilePath.Text = "スクリーンショットを追記するパワーポイントを指定してください(空欄可)"
$LabelFilePath.Font = New-Object System.Drawing.Font("Arial", 11) 

# FilePath入力用テキストボックス
$TextBoxFilePath = New-Object System.Windows.Forms.TextBox
$TextBoxFilePath.Location = New-Object System.Drawing.Point(20,42)
$TextBoxFilePath.Size = New-Object System.Drawing.Size(370,30)
$TextBoxFilePath.Font = New-Object System.Drawing.Font("Arial", 10) 

# FilePath参照ボタン
$ButtonFilePath = New-Object System.Windows.Forms.Button
$ButtonFilePath.Location = New-Object System.Drawing.Point(400,40)
$ButtonFilePath.AutoSize = $true
$ButtonFilePath.Text = "参照"
$ButtonFilePath.Font = New-Object System.Drawing.Font("Arial", 10) 

# FilePathOKボタン
$ButtonOKFilePath = New-Object System.Windows.Forms.Button
$ButtonOKFilePath.Location =  New-Object System.Drawing.Point(320,90)
$ButtonOKFilePath.AutoSize = $true
$ButtonOKFilePath.Text = "OK"
$ButtonOKFilePath.Font = New-Object System.Drawing.Font("Arial", 10) 
$ButtonOKFilePath.DialogResult = "OK"

# FilePathCancelボタン
$ButtonCancelFilePath = New-Object System.Windows.Forms.Button
$ButtonCancelFilePath.Location =  New-Object System.Drawing.Point(400,90)
$ButtonCancelFilePath.AutoSize = $true
$ButtonCancelFilePath.Text = "キャンセル"
$ButtonCancelFilePath.Font = New-Object System.Drawing.Font("Arial", 10) 
$ButtonCancelFilePath.DialogResult = "Cancel"

# FilePathフォームへのラベル等構成要素の配置
$FilePathForm.Controls.AddRange(@($LabelFilePath,$TextBoxFilePath,$ButtonFilePath,$ButtonOKFilePath,$ButtonCancelFilePath))

# FilePath参照ボタンをクリック時の動作
$ButtonFilePath.add_click{

    #ダイアログを表示しファイルを選択する
    $Dialog = New-Object System.Windows.Forms.OpenFileDialog
    $Dialog.Filter = "PowerPointプレゼンテーション(*.pptx;*.ppt;*.pptm)|*.pptx;*.ppt;*.pptm"
    if($Dialog.ShowDialog() -eq "OK"){
        $TextBoxFilePath.Text = $Dialog.FileName
    }
}

######


### CustomTextフォーム要素 ###

# CustomTextフォーム作成
$CustomTextForm = New-Object System.Windows.Forms.Form 
$CustomTextForm.Size = New-Object System.Drawing.Size(500,130) 
$CustomTextForm.Text = "CustomText"
$CustomTextForm.Owner = $OnOffForm

# サブフォーム(CustomTextフォーム)のクロージングイベント
$Close = {
    $_.Cancel = $True
    $CustomTextForm.Visible = $false
}
$CustomTextForm.Add_Closing($Close)

# CustomTextラベル作成
$LabelCustomText = New-Object System.Windows.Forms.Label
$LabelCustomText.Location = New-Object System.Drawing.Point(20,10)
#$LabelCustomText.Size = New-Object System.Drawing.Size(450,20)
$LabelCustomText.AutoSize = $true
$LabelCustomText.Text = "スクリーンショット取得日時に付記したいテキストを入力してください(空欄可)"
$LabelCustomText.Font = New-Object System.Drawing.Font("Arial", 10) 

# CustomText入力用テキストボックス
$TextBoxCustomText = New-Object System.Windows.Forms.TextBox
$TextBoxCustomText.Location = New-Object System.Drawing.Point(20,30)
$TextBoxCustomText.Size = New-Object System.Drawing.Size(450,30)
$TextBoxCustomText.Font = New-Object System.Drawing.Font("Arial", 10) 

# CustomTextOKボタン
$ButtonOKCustomText = New-Object System.Windows.Forms.Button
$ButtonOKCustomText.Location =  New-Object System.Drawing.Point(230,60)
#$ButtonOKCustomText.Size = New-Object System.Drawing.Size(50,30)
$ButtonOKCustomText.AutoSize = $true
$ButtonOKCustomText.Text = "OK"
$ButtonOKCustomText.Font = New-Object System.Drawing.Font("Arial", 10) 

# CustomTextCancelボタン
$ButtonCancelCustomText = New-Object System.Windows.Forms.Button
$ButtonCancelCustomText.Location =  New-Object System.Drawing.Point(320,60)
#$ButtonCancelCustomText.Size = New-Object System.Drawing.Size(160,30)
$ButtonCancelCustomText.AutoSize = $true
$ButtonCancelCustomText.Text = "キャンセル(指定しない)"
$ButtonCancelCustomText.Font = New-Object System.Drawing.Font("Arial", 10) 

# CustomTextフォームへのラベル等構成要素の配置
$CustomTextForm.Controls.AddRange(@($LabelCustomText,$TextBoxCustomText,$ButtonOKCustomText,$ButtonCancelCustomText))

######


### OnOffフォーム要素 ###

# OnOffフォーム
$OnOffForm = New-Object -TypeName ClipboardListeningForm
$OnOffForm.Size = New-Object System.Drawing.Size(550,250) 
$OnOffForm.StartPosition = "CenterScreen"
$OnOffForm.Text = "ScreenshotToPowerpoint"
$OnOffForm.TopLevel = $True
$OnOffForm.MaximizeBox = $False

# OnOffラベル1
$label1OnOff = New-Object System.Windows.Forms.Label
$label1OnOff.Location = New-Object System.Drawing.Point(10,10)
$label1OnOff.AutoSize = $true
$label1OnOff.Font = New-Object System.Drawing.Font("Arial", 10) 
$label1OnOff.Text = "PowerPointへのスクリーンショット貼り付けを開始しますか?"

# OnOffラベル2
$label2OnOff = New-Object System.Windows.Forms.Label
$label2OnOff.Location = New-Object System.Drawing.Point(270,50)
$label2OnOff.AutoSize = $true
$label2OnOff.Font = New-Object System.Drawing.Font("Arial", 12) 
$label2OnOff.Text = "停止中"
$label2OnOff.TextAlign = "BottomCenter"

# OnOff-Start/Stopボタン
$StartButtonOnOff = New-Object -TypeName System.Windows.Forms.CheckBox
$StartButtonOnOff.location = New-Object System.Drawing.Point(200,50)
$StartButtonOnOff.Autosize = $true
$StartButtonOnOff.Font = New-Object System.Drawing.Font("Arial", 12) 
$StartButtonOnOff.Text = "Start"
$StartButtonOnOff.TextAlign = "MiddleCenter"
$StartButtonOnOff.Appearance = "Button"
$StartButtonOnOff.FlatStyle = "System"

$StartButtonOnOff.add_CheckedChanged({
    if ($StartButtonOnOff.Checked) {
		#StartButtonがクリックされたとき、黄色背景で実行中を強調する
        $StartButtonOnOff.Text = 'Stop'
        $label2OnOff.Text = "実行中"
        $label2OnOff.BackColor = "yellow"
        $label2OnOff.Font = New-Object System.Drawing.Font("Arial", 12, [System.Drawing.FontStyle]::Bold)
        $label1OnOff.Text = "PowerPointへのスクリーンショット貼り付けを停止しますか?"
    } else {
    	#StartButtonが再クリックされたとき、開始前(停止中)に戻す
        $StartButtonOnOff.Text = 'Start'
        $label2OnOff.Text = "停止中"
        $label2OnOff.ResetBackColor()
        $label2OnOff.Font = New-Object System.Drawing.Font("Arial", 12)
        $label1OnOff.Text = "PowerPointへのスクリーンショット貼り付けを開始しますか?"
    }
})

# OnOff-ツール終了ボタン
$CloseButtonOnOff = New-Object System.Windows.Forms.Button
$CloseButtonOnOff.Location = New-Object System.Drawing.Point(215,150)
$CloseButtonOnOff.AutoSize = $true
$CloseButtonOnOff.Font = New-Object System.Drawing.Font("Arial", 10) 
$CloseButtonOnOff.Text = "ツールを閉じる"
$CloseButtonOnOff.DialogResult = [System.Windows.Forms.DialogResult]::cancel

# OnOff-チェックボックス(CustomTextの有効化無効化)
$CheckBoxOnOff = New-Object System.Windows.Forms.CheckBox
$CheckBoxOnOff.Location = New-Object System.Drawing.Point(20, 100)

$CheckBoxOnOff.AutoSize = $true
$CheckBoxOnOff.Font = New-Object System.Drawing.Font("Arial", 10) 
$CheckBoxOnOff.Text = "スクリーンショット取得時、日時情報に任意のテキストを付与する(作業項番等)"
$CheckBoxOnOff.Checked = $false

# OnOffフォームへのラベル等構成要素の配置
$OnOffForm.Controls.AddRange(@($label1OnOff,$label2OnOff,$StartButtonOnOff,$CloseButtonOnOff,$CheckBoxOnOff))



### 以下スクリプト処理 ###

# 処理① スクリプト処理開始を通知するlogger
logger "Process starting"


# 処理② FilePathフォームを表示し処理が完了したらファイルパスを返す
if($FilePathForm.ShowDialog() -eq "OK"){
    if($TextBoxFilePath.text -ne ""){
        $PastePowerpointFile = $TextBoxFilePath.text
    }else{
        $PastePowerpointFile = $null
    }
}else{
	# キャンセルボタンをクリックされたら以降の処理を実施しない
    break
}


# 処理③ Powerpointを起動する。処理②でファイル指定がされていれば対象ファイルをopenし、指定がなければ新規ファイルを作成する
logger "Connected to PowerPoint."

$coms[$ppApp] = New-Object -ComObject PowerPoint.Application
$coms[$ppPress] = $coms[$ppApp].Presentations

 if ($PastePowerpointFile -ne $null) {
        try {
            $coms[$ppPres] = $coms[$ppPress].Open($PastePowerpointFile)
            logger "Open $PastePowerpointFile"
        } catch {
            logger "File Open Failed. => $PastePowerpointFile"
            return;
        }
    } else {
        $coms[$ppPres] = $coms[$ppPress].Add($true)
    }

    $coms[$ppSlds] = $coms[$ppPres].Slides

logger "Clear Clipboard."


# 処理④ クリップボードに更新があった場合ClipboardUpdateイベントハンドルへ
$OnOffForm.add_ClipboardUpdate({

	#PowerPointが非表示モードなら、PowerPointを閉じて以降の処理を行わない
    if ($coms[$ppApp].Visible -eq $False) {
            logger "PowerPoint Closed."
            $coms[$ppApp].Quit()
            break
        }

	# 処理⑤ StartButtonがクリックされている、かつクリップボードに画像が更新された場合
    if ($StartButtonOnOff.Checked -and [System.Windows.Forms.Clipboard]::ContainsImage()) {
            Measure-Command {

                # 現在のスライド枚数を参照して、末尾に挿入する
                $coms[$ppSld]  = $coms[$ppSlds].Add($coms[$ppSlds].Count + 1, $ppLayoutTitleOnly)
                $coms[$ppShps] = $coms[$ppSld].Shapes
                $coms[$ppPic]  = $coms[$ppShps].Paste()
                $coms[$ppTitle]  = $coms[$ppShps].Title
                $coms[$ppTxtFrm] = $coms[$ppTitle].TextFrame
                $coms[$ppTxtRng] = $coms[$ppTxtFrm].TextRange
                $coms[$ppTxtRng].Text = Get-Date -Format "yyyy/MM/dd HH:mm:ss"

				# 処理⑥ OnOffフォームのチェックボックス($CheckBoxOnOff)にチェックが入っている場合、日時に付与するテキストを入力させる
                if ($CheckBoxOnOff.Checked) {
                        $TextBoxCustomText.text = $null

						# CustomTextフォームを表示する。OnOffフォームがShowDialog利用しているので、Show()で開く
                        $CustomTextForm.Show()

						# OKボタンが押された場合の処理
                        $ButtonOKCustomText.add_Click({  
                          	# テキストボックスが空白でなければ、テキストボックスに入力されたテキストを日時に追記する
                            if($TextBoxCustomText.text -ne ""){
                                $coms[$ppTxtRng].Text = $coms[$ppTxtRng].Text + "_" + $TextBoxCustomText.text
                                $TextBoxCustomText.text = $null
                                
                            }else{
								# テキストボックスが空白の場合、OKボタンが押されても何もしない
								
                            }                            
							# CustomTextを閉じる(サブフォーム(CustomTextフォーム)のクロージングイベントへ)
                            $CustomTextForm.Close()
                            
                        })

						# キャンセルボタンが押された場合の処理
                        $ButtonCancelCustomText.add_Click({
                        	# CustomTextを閉じる(サブフォーム(CustomTextフォーム)のクロージングイベントへ)
                            $CustomTextForm.Close()
                        })

            	}

                # 処理⑦ スクリーンショット取得時、必ず最新(末尾)のスライドが表示されるようにする
                $coms[$ppSlds].Item($coms[$ppSlds].Count).Select()

                [System.Windows.Forms.Clipboard]::Clear()

            } | % { logger $_.TotalMilliseconds.ToString(("#####.000ms").PadLeft(10)) }
        } else {
            Start-Sleep -Milliseconds $sleepTime
        }
    
})

# OnOffフォームの表示(処理起点)
$OnOffForm.ShowDialog()

# 処理⑧ COMオブジェクトの解放処理
for ([int32]$i=$arrayUbound;$i -ge 0;$i--){
        if ($coms[$i] -ne $null) {
            [System.Runtime.InteropServices.Marshal]::FinalReleaseComObject($coms[$i]) >$null
            $coms[$i] = $null
        }
    }
    
# 処理⑨ スクリプト処理終了を通知するlogger
logger "Terminating"

コード解説 兼 実行イメージ

さすがに長過ぎるので、少しコードを解説する。

COMオブジェクト格納配列、PowerPoint操作用配列、PowerPoint定数

コードで以下に該当する箇所について解説する。

# COMオブジェクト格納配列
[int32]$arrayUbound = 9
[Object[]]$coms = 0..$arrayUbound
for ([int32]$i=0;$i -le $arrayUbound;$i++){
    $coms[$i] = $null
}

# PowerPoint操作用配列
New-Variable -Name ppApp    -Value 0 -Option ReadOnly -Description 'PowerPoint.Application'
New-Variable -Name ppPress  -Value 1 -Option ReadOnly -Description 'PowerPoint.Presentations'
New-Variable -Name ppPres   -Value 2 -Option ReadOnly -Description 'PowerPoint.Presentation'
New-Variable -Name ppSlds   -Value 3 -Option ReadOnly -Description 'PowerPoint.Slides'
New-Variable -Name ppSld    -Value 4 -Option ReadOnly -Description 'PowerPoint.Slide'
New-Variable -Name ppShps   -Value 5 -Option ReadOnly -Description 'PowerPoint.Shpaes'
New-Variable -Name ppPic    -Value 6 -Option ReadOnly -Description 'PowerPoint.Shape Pasted Picture'
New-Variable -Name ppTitle  -Value 7 -Option ReadOnly -Description 'PowerPoint.Shpae Title PlaceHolder'
New-Variable -Name ppTxtFrm -Value 8 -Option ReadOnly -Description 'PowerPoint.TextFrame Title'
New-Variable -Name ppTxtRng -Value 9 -Option ReadOnly -Description 'PowerPoint.TextRange Title'

# PowerPoint定数(https://learn.microsoft.com/ja-jp/office/vba/api/powerpoint.ppslidelayout)
New-Variable -Name ppLayoutTitleOnly -Value 11 -Option ReadOnly -Description 'PowerPoint.PpSlideLayout.ppLayoutTitleOnly'

PowerPointはCOM(Component Object Model)オブジェクトとして利用(操作)する。
COM[0]~COM[9]を予め定義しているが、これは後続のPowerPoint操作用配列「ppApp、ppPress、ppPres、ppPres、ppSlds、ppSld、ppShps、ppPic、ppTitle、ppTxtFrm、ppTxtRng」に対応する形となる。

# PowerPoint定数のところでは変数ppLayoutTitleOnlyの値を11としているが、これは以下サイトで定められているとおり「タイトルのみ」のスライドスタイルのことを表している。

PpSlideLayout 列挙 (PowerPoint)
Office VBA リファレンス トピック

C言語でClipboardの更新ごとに処理を行うクラスの定義

コードで以下に該当する箇所について解説する。

# C言語でClipboardの更新ごとに処理を行うクラスの定義
Add-Type -TypeDefinition @'
    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;

    public class ClipboardListeningForm : Form
    {
        [DllImport("user32.dll", SetLastError = true)]
        private extern static void AddClipboardFormatListener(IntPtr hwnd);

        [DllImport("user32.dll", SetLastError = true)]
        private extern static void RemoveClipboardFormatListener(IntPtr hwnd);

        public event EventHandler ClipboardUpdate;
        public const int WM_CLIPBOARDUPDATE = 0x031D;

        protected override void OnLoad(EventArgs e)
        {
            AddClipboardFormatListener(Handle);
            base.OnLoad(e);
        }

        protected override void Dispose(bool disposing)
        {
            RemoveClipboardFormatListener(Handle);
            base.Dispose(disposing);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_CLIPBOARDUPDATE)
            {
                OnClipboardUpdate(EventArgs.Empty);
                m.Result = IntPtr.Zero;
            }
            else
                base.WndProc(ref m);
        }

        protected virtual void OnClipboardUpdate(EventArgs e)
        {
            EventHandler handler = ClipboardUpdate;
            if (handler != null)
            {
                handler(this, e);
            }
        }            
    }
'@ -ReferencedAssemblies System.Windows.Forms

これはスクリーンショットの取得を検知するために、C#言語を利用してClipboard監視を行うクラス「ClipboardListeningForm」を定義している。これにより、スクリーンショットの取得(=クリップボードが更新される)のイベント駆動が出来るようになっている。細かい項目一つ一つの意味は参考文献等を確認されたし。

FilePathフォーム要素、CustomTextフォーム要素、OnOffフォーム要素

それぞれ以下のシーンで利用するWindowsFormsである。

FilePathフォーム

スクリプトを立ち上げた際に、スクリーンショットを追記するPowerPointファイルをユーザーに指定/入力してもらうためのフォームである。

直接フルパスを入力してもよいし、参照ボタンから対象ファイルを指定してもよい。
空欄のままOKボタンを押してもよい。(その場合は新規PowerPointファイルを立ち上げる)

コードに関して、特にこのフォームでは難しい記述はないので解説は省略する。

OnOffフォーム

順番が前後するが先にこちらを解説する。
これはFilePathフォームでOKをクリックしたあとに立ち上がる画面である。

本フォームを立ち上げた際に、FilePathフォームで指定したPowerPointファイルまたは新規PowerPointファイルを開く。このままの状態ではスクショを撮ってもPowerPointへの貼り付けは行われず、「Start」ボタンを押下して状態が「実行中」となっている間に実行したスクショのみPowerPointへの貼り付けが行われる。

また、「スクリーンショット取得時、日時情報に任意のテキストを付与する(作業項番等)」のチェックボックスをチェックすることで、PowerPointに貼り付けるカスタムテキストを入力するフォームがスクショをする度に立ち上がるようになる。

コードに関して、特にこのフォームでは難しい記述はないので解説は省略する。

CustomTextフォーム

OnOffフォームでチェックボックスにチェックが入っていた場合、スクショを撮る度に表示されるカスタムテキストを入力するためのフォームである。

テキストボックスに文字列を入力しOKをクリックすると、通常「2024/02/10 22:43:48」のように日時情報しかPowetPointのスライドに貼り付けられないところ、「2024/02/10 22:43:48_test」のように、ユーザーカスタムのテキストを追記させることができる。作業項番や作業時の事象などをタグ付け代わりにメモすることで、遡ってのエビデンス確認が容易になる、と考えている。

実際にPowerPointにスクショ画像、日時、カスタムテキストが貼り付けられている

CustomTextフォームのコードでは、2点ほど分かりづらい動きがあるので解説する。

サブフォーム(CustomTextフォーム)のクロージングイベント

CustomTextフォームは、ShowDialog()ではなく、Show()で起動/表示している。
これはなぜかと言うと、CustomTextフォームが開かれるタイミングでは、OnOffフォームがShowDialog()を利用しており、モーダルなShowDialog()はCustomTextフォームで並行利用が出来ないためである。

ここで一点問題が起きる。Show()でCustomTextフォームを開き、カスタムテキストを入力したあと、OKボタンやキャンセルボタンを押してもShowDialog()ではないので、.DialogResultプロパティの更新が出来ず、CustomTextフォームが閉じれなくなってしまう(ボタンを押しても無反応になってしまう)。

そこで、OKボタンやキャンセルボタンを押された時に明示的にCustomTextフォームを閉じるため$CustomTextForm.Close()を実行するのだが、ここで破棄してしまうと2回目以降のスクショで$CustomTextForm.Show()の再利用ができなくなり、カスタムテキストの入力ができなくなってしまう。

これを解決するためには、CustomTextフォームを閉じるため$CustomTextForm.Close()を実行するが、実際は非表示にするだけでCustomTextフォームは終了させない、という処理が必要になる。

この疑似クロージングを定義しているのが、以下のコードである。

# サブフォーム(CustomTextフォーム)のクロージングイベント
$Close = {
    $_.Cancel = $True
    $CustomTextForm.Visible = $false
}
$CustomTextForm.Add_Closing($Close)
親フォームの指定 $CustomTextForm.Owner = $OnOffForm

CustomTextフォームは、OnOffフォームが実行されている最中に呼び出されるフォームである。
そのため、OnOffフォームが閉じられてしまう(=スクリプトの終了処理が走る)ときに、CustomTextフォームを閉じていないと、CustomTextフォームの処理結果の行き先がなくなってしまい、スクリプトがフリーズ状態になってしまう。
これを解決するためには、OnOffフォームが閉じられた場合に、CustomTextフォームも追随して閉じるようにしてやる必要がある。
.Ownerを使用することでフォーム間で親子関係を作ることができる。親フォームが終了した場合子フォームも終了するようになるので、$CustomTextForm.Owner = $OnOffFormと指定することでOnOffフォームが閉じられた場合に、CustomTextフォームも追随して閉じる動作を実現する。

# CustomTextフォーム作成
$CustomTextForm = New-Object System.Windows.Forms.Form 
$CustomTextForm.Size = New-Object System.Drawing.Size(500,130) 
$CustomTextForm.Text = "CustomText"
$CustomTextForm.Owner = $OnOffForm

処理⑤ StartButtonがクリックされている、かつクリップボードに画像が更新された場合

本スクリプトは長さの割にやっていること(処理)そのものはそれほど難解ではないと思うが、処理⑤の部分だけ前述したShowDialog()とShow()の制限などで複雑になっているので解説する。

まず、処理⑤はスクリーンショットを取得するためStartボタンが押されている+スクリーンショットが実行された場合に分岐する処理である。
以下のコードで判定している。

if ($StartButtonOnOff.Checked -and [System.Windows.Forms.Clipboard]::ContainsImage())

次に、現在のスライド枚数を参照して、末尾にスクリーンショットとスクショ取得日時を挿入する処理を行う。
これは、事前に定義していたCOMオブジェクトで現在のスライド枚数を参照し、+1したスライドを新規作成したのち、ペーストの実行とスライドタイトルへのテキスト挿入で実現している。

 # 現在のスライド枚数を参照して、末尾に挿入する
                $coms[$ppSld]  = $coms[$ppSlds].Add($coms[$ppSlds].Count + 1, $ppLayoutTitleOnly)
                $coms[$ppShps] = $coms[$ppSld].Shapes
                $coms[$ppPic]  = $coms[$ppShps].Paste()
                $coms[$ppTitle]  = $coms[$ppShps].Title
                $coms[$ppTxtFrm] = $coms[$ppTitle].TextFrame
                $coms[$ppTxtRng] = $coms[$ppTxtFrm].TextRange
                $coms[$ppTxtRng].Text = Get-Date -Format "yyyy/MM/dd HH:mm:ss"

処理⑥ OnOffフォームのチェックボックス($CheckBoxOnOff)にチェックが入っている場合、日時に付与するテキストを入力させる

CustomTextフォームの項で触れたが、OnOffフォームのチェックボックス($CheckBoxOnOff)にチェックが入っている場合にCustomTextフォームを表示する。またそこで入力されたカスタムテキストが空白でなく、かつOKボタンがクリックされた場合に、前述のスライドタイトルへのテキスト挿入に追記する。
追記後、次回以降のカスタムテキスト指定時に前回入力情報を残さないよう、$TextBoxCustomText.textをnullする。カスタムテキストが空白の場合は何もせず後続処理へ続ける。

OKボタンの処理がすべて完了したのち、$CustomTextForm.Close()でCustomTextフォームを閉じる(実際は、非表示状態にする)キャンセルボタンが押された場合も、同様に$CustomTextForm.Close()でCustomTextフォームを閉じる。

# 処理⑥ OnOffフォームのチェックボックス($CheckBoxOnOff)にチェックが入っている場合、日時に付与するテキストを入力させる
                if ($CheckBoxOnOff.Checked) {
                        $TextBoxCustomText.text = $null

						# CustomTextフォームを表示する。OnOffフォームがShowDialog利用しているので、Show()で開く
                        $CustomTextForm.Show()

						# OKボタンが押された場合の処理
                        $ButtonOKCustomText.add_Click({  
                          	# テキストボックスが空白でなければ、テキストボックスに入力されたテキストを日時に追記する
                            if($TextBoxCustomText.text -ne ""){
                                $coms[$ppTxtRng].Text = $coms[$ppTxtRng].Text + "_" + $TextBoxCustomText.text
                                $TextBoxCustomText.text = $null
                                
                            }else{
								# テキストボックスが空白の場合、OKボタンが押されても何もしない
								
                            }                            
							# CustomTextを閉じる(サブフォーム(CustomTextフォーム)のクロージングイベントへ)
                            $CustomTextForm.Close()
                            
                        })

						# キャンセルボタンが押された場合の処理
                        $ButtonCancelCustomText.add_Click({
                        	# CustomTextを閉じる(サブフォーム(CustomTextフォーム)のクロージングイベントへ)
                            $CustomTextForm.Close()
                        })

注意点と補足

  • PowerShellが実行可能な環境である必要があります。
  • PowerPointが実行可能な環境である必要があります。
  • 何かしらのスクリーンショットツール(snipping tool等)が実行可能な環境である必要があります。(screenpressoなんかでも動作します)
  • OnOffフォームでStartボタンを押下している状態で、PowerPointを閉じてしまうと、次にスクリーンショットを撮った際にエラーが発生します。

改善(改案)点

コードが長すぎるので、もう少しコンパクトにまとめたい。
PowerPoint操作用配列の部分を列挙型に変えることも考えたが、拡張性を考慮するようなスクリプトでもないので、効果は薄い気がする。

PowerPointへの画像貼り付けについて、ど真ん中に貼り付ける仕様なので少し見辛いと感じることがある。一応位置指定やスライドのサイズに合わせて画像を縮小させることも出来そうだが、使用者によってはかえって使いづらく感じる要素でもあり、一旦シンプルな形としている。

カスタムテキスト入力フォーム周りの処理は、もう少しスマートなやり方がある気がする・・・。

参考文献

改版履歴

2024/02/11 初版公開

コメント

タイトルとURLをコピーしました