PowerShellのプログレスバーを使ったポモドーロタイマーの作成方法

PowerShellでポモドーロタイマー作ってみた

こんにちは。
けいぞうです。

今回はPowerShellプログレスバー(Write-Progressコマンド)を使ったタイマーの作成方法について、「ポモドーロタイマー」を例に解説していきたいと思います。

(というより、ポモドーロタイマーの作成方法、ソースコード公開になります。)

出来上がりのイメージはTwitterで呟いた以下のような感じです。


PowerShellでポモドーロタイマーって作れんの?

そう思っている方に読んで欲しい記事になります。
(そんな奴おらんやろ~(CV:大木こだま))

[目次]

プログレスバーを使ったポモドーロタイマーの作成方法

PowerShellが初めてという方は、まずは下記の記事をご覧になってください。

事前準備

今回のタイマーは時間が来たことに気づかないと意味がないので、時間が来たら音を鳴らしてお知らせしてくれるようにしました。

効果音についてはテキトーに拾ってきたもので良いと思います。

私は日本のYouTuber御用達の効果音ラボさんからダウンロードしてきました♪


好きな効果音をダウンロードしてきたら、任意のフォルダに置いてください。

ソースコード

先ほど効果音のmp3ファイルを置いた場所と同じ場所に空のps1ファイルを作成しておきましょう。そうしたら以下のソースコードをコピペすれば99%作業は終わりです。

ソースコードの全文は以下になります。

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

# フォーム
$form = New-Object System.Windows.Forms.Form
$form.Text = "ポモドーロタイマー"
$form.Size = New-Object System.Drawing.Size(380,180)

# OKボタンの設定
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(100,93)
$OKButton.Size = New-Object System.Drawing.Size(75,30)
$OKButton.Text = "スタート"
$OKButton.DialogResult = "OK"

# 終了ボタンの設定
$ExitButton = New-Object System.Windows.Forms.Button
$ExitButton.Location = New-Object System.Drawing.Point(190,93)
$ExitButton.Size = New-Object System.Drawing.Size(75,30)
$ExitButton.Text = "終了"
$ExitButton.DialogResult = "Cancel"

#初期値
[int]$workTime = 25
[int]$breakTime = 5
[int]$setCount = 2

# 作業時間テキストボックス
$workTimeTextBox = New-Object System.Windows.Forms.TextBox
$workTimeTextBox.Location = New-Object System.Drawing.Point(10,50)
$workTimeTextBox.Size = New-Object System.Drawing.Size(100,50)
$workTimeTextBox.Text = $workTime

# 休憩時間テキストボックス
$breakTimeTextBox = New-Object System.Windows.Forms.TextBox
$breakTimeTextBox.Location = New-Object System.Drawing.Point(130,50)
$breakTimeTextBox.Size = New-Object System.Drawing.Size(100,50)
$breakTimeTextBox.Text = $breakTime

# セット数テキストボックス
$setCountTextBox = New-Object System.Windows.Forms.TextBox
$setCountTextBox.Location = New-Object System.Drawing.Point(250,50)
$setCountTextBox.Size = New-Object System.Drawing.Size(100,50)
$setCountTextBox.Text = $setCount

# 作業時間ラベルの設定
$workTimelabel = New-Object System.Windows.Forms.Label
$workTimelabel.Location = New-Object System.Drawing.Point(10,30)
$workTimelabel.Size = New-Object System.Drawing.Size(100,20)
$workTimelabel.Text = "作業時間[分]"

# 休憩時間ラベルの設定
$breakTimelabel = New-Object System.Windows.Forms.Label
$breakTimelabel.Location = New-Object System.Drawing.Point(130,30)
$breakTimelabel.Size = New-Object System.Drawing.Size(100,20)
$breakTimelabel.Text = "休憩時間[分]"

# セット数ラベルの設定
$setCountlabel = New-Object System.Windows.Forms.Label
$setCountlabel.Location = New-Object System.Drawing.Point(250,30)
$setCountlabel.Size = New-Object System.Drawing.Size(100,20)
$setCountlabel.Text = "セット数[回]"

# キーとボタンの関係
$form.AcceptButton = $OKButton
$form.CancelButton = $CancelButton

# ボタン等をフォームに追加
$form.Controls.Add($OKButton)
$form.Controls.Add($ExitButton)
$form.Controls.Add($workTimelabel)
$form.Controls.Add($breakTimelabel)
$form.Controls.Add($setCountlabel)
$form.Controls.Add($workTimeTextBox)
$form.Controls.Add($breakTimeTextBox)
$form.Controls.Add($setCountTextBox)

#メッセージ表示用のオブジェクト
$wsobj = new-object -comobject wscript.shell

#タイマー音のオブジェクト準備
#ファイル名とパス
$WorkFinishMp3FilePath = "【各自の環境のmp3を置いた場所のフルパス】\WorkFinish.mp3"
$BreakTimeFinishMp3FilePath = "【各自の環境のmp3を置いた場所のフルパス】\BreaktimeFinish.mp3"
$mediaPlayer = New-Object system.windows.media.mediaplayer


$roop = $true
while($roop){

# フォームを表示させ、その結果を受け取る
$result = $form.ShowDialog()

    #終了ボタンを押したらreturnで終了する
    if($result -eq "Cancel"){
       return
    }

#ここで入力チェック
if(-not([int]::TryParse($workTimeTextBox.Text,[ref]$workTime) -and [int]$workTimeTextBox.Text -gt 0)){
    $result = $wsobj.popup("作業時間は正の整数で入力してください",0,"入力チェック",48)
    continue
}
if(-not([int]::TryParse($breakTimeTextBox.Text,[ref]$breakTime) -and [int]$breakTimeTextBox.Text -gt 0)){
    $result = $wsobj.popup("休憩時間は正の整数で入力してください",0,"入力チェック",48)
    continue
}
if(-not([int]::TryParse($setCountTextBox.Text,[ref]$setCount) -and [int]$setCountTextBox.Text -gt 0)){
    $result = $wsobj.popup("セット数は正の整数で入力してください",0,"入力チェック",48)
    continue
}

#メッセージ表示
$StartMessage = "作業時間:" + $workTimeTextBox.Text + "分、休憩時間:" + $breakTimeTextBox.Text + "分、セット数" + $setCountTextBox.Text + "回で開始しますか?"
$result = $wsobj.popup($StartMessage,0,"確認メッセージ",4)

    if($result -eq 6){
        #はいボタンでbreakして次に進む
        break
    }

    #いいえボタンならフォームをもう一度表示

}

# 作業時間[秒]
[int]$workTimeSecond = [int]$workTimeTextBox.Text * 60
# 休憩時間[秒]
[int]$breakTimeSecond = [int]$breakTimeTextBox.Text * 60
# セット数
[int]$setCount = [int]$setCountTextBox.Text

#セット数の分ループする
$currentSetCount = 0
while ($true){

    $currentSetCount = $currentSetCount + 1    

    # 作業時間開始
    # 指定した秒数分、繰り返す
    foreach ($i in (1..$workTimeSecond))
    {
        $restTime = [int]$workTimeSecond - ($i - 1)
        $message = "" + $currentSetCount + "セット目 作業中。あと" + $restTime + "秒..."
    
        # 進捗表示と待ち
        Write-Progress -Activity $message -PercentComplete ([int]((100 * $i)/$workTimeSecond))
        Start-Sleep -Second 1
    }
    
    #作業時間が終了したらアラートを出す
    $mediaPlayer.open($WorkFinishMp3FilePath)
    $mediaPlayer.Play()
    
    #指定されたセット数が完了したら休憩に入らずに終了
    if($currentSetCount -eq $setCount){
        $Message = "お疲れ様です。" + $setCount + "セット終了しました。"
        $result = $wsobj.popup($Message,0,"全セット完了メッセージ",0)
        return
    }

    $result = $wsobj.popup("作業時間が終了しました。休憩に入ります。",0,"作業時間終了メッセージ",0)
    
    # 休憩時間開始
    # 指定した秒数分、繰り返す
    foreach ($i in (1..$breakTimeSecond))
    {
        $restTime = [int]$breakTimeSecond - ($i - 1)
        $message = "" + $currentSetCount + "セット目 休憩中。あと" + $restTime + "秒..."
    
        # 進捗表示と待ち
        Write-Progress -Activity $message -PercentComplete ([int]((100 * $i)/$breakTimeSecond))
        Start-Sleep -Second 1
    }

    #休憩時間が終了したらアラートを出す
    $mediaPlayer.open($BreakTimeFinishMp3FilePath)
    $mediaPlayer.Play()
    $Message = "休憩時間が終了しました。次の作業に入ります。あと" + ($setCount - $currentSetCount) + "セット"
    $result = $wsobj.popup($Message,0,"休憩時間終了メッセージ",0)

}

85行目と86行目は先ほどmp3ファイルを置いた各自の環境のパスに書き換えてください。ファイル名も各自が使うmp3のファイル名に書き換えてください。

実行方法

実行方法ですが、ただps1をダブルクリックするだけだとおそらくソースコードが開くだけになってしまいます。

今回は「ショートカットを作成する」方法で実行したいと思います。

手順は以下になります。

①ps1ファイルを右クリックして 送る > デスクトップ(ショートカットを作成)

デスクトップにショートカットが作成されます。デスクトップが嫌な人は、ps1ファイルを置いている作業フォルダに移動してもらってもOKです。

②ショートカットを編集

ショートカットを右クリックして プロパティをクリック

リンク先(T):」のパスの頭に「powershell -file 」を付ける。(※-fileの後ろの半角スペースを忘れずに)

そうするとショートカットがPowerShellのマークに変わったと思います。これでダブルクリック実行ができます!


実行してエラーになった人は、ソースに間違いがなければ実行ポリシーの問題の可能性が高いので、以下の記事をご確認ください。

ソースの解説

ソースの中でメッセージポップアップを多用していますが、ポップアップについては以下の記事で詳細を解説していますので、ご覧ください。

その他ポップアップ以外については、重要な要素に絞って解説します。

アセンブリの読み込み

1行目~4行目はアセンブリのと読み込みです。

それぞれ「入力フォーム作成」のため、「描画」のため、「MP3再生」のために、先頭で読み込みを行っています。

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

フォームの作成

一番最初に表示させるフォーム(メッセージウィンドウみたいなやつ)の作成を行います。

$form.Textにはフォームの左上に表示させるタイトルのテキストを設定

$form.Sizeにはフォーム画面の大きさを設定します。第一引数が横の長さ[px]、第二引数が高さ[px]になります。

今回は「ポモドーロタイマー」と表示させて、長さ280px、高さ180pxのフォームを作成しました。

# フォーム
$form = New-Object System.Windows.Forms.Form
$form.Text = "ポモドーロタイマー"
$form.Size = New-Object System.Drawing.Size(380,180)

ボタン、テキストボックス、ラベルの配置

フォームを作ったら、そこにオブジェクトを配置していきます。

今回はポモドーロタイマーに必要な

  • ボタン
  • テキストボックス(文字を入力してもらう場所)
  • ラベル(文字を表示するだけのもの)

を配置しました。

また、このオブジェクト達のことをControl(コントロール)と呼んだりします。

Drawing.Pointの引数には、オブジェクトの配置場所を指定します。全て左上を起点とした距離を指定します。第一引数が左上から右方向への横の距離、第二引数が左上から下方向への縦の距離になります。

Drawing.Sizeには、オブジェクトの大きさを指定します。第一引数が横の長さ、第二引数が縦の高さになります。

Textにはボタンやラベルの場合は表示させたい文字を、テキストボックスには初期表示時に入力させておきたい文字を指定します。今回は、テキストボックスには初期値として変数に持たせた値を渡しています。

ボタンの種類ですが「AcceptButton」と「CancelButton」をそれぞれ設定することができます。今回は「スタート」と表示させたボタンを「AcceptButton」に、「終了」と表示させたボタンを「CancelButton」に設定しています。(67行目、68行目)

DialogResultには、ボタンを押したときの戻り値の文字列を指定します。今回の場合は、スタートボタンの戻り値は「OK」、終了ボタンの戻り値は「Cancel」となります。

$form.Controls.Add()メソッドで作ったコントロールをフォームの上に配置してきます。これをしないと、ただテキストボックスとかボタンを作っただけで、フォームの上に配置したことにはならないので表示されません。よくあるミスなので注意です!

# OKボタンの設定
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Point(100,93)
$OKButton.Size = New-Object System.Drawing.Size(75,30)
$OKButton.Text = "スタート"
$OKButton.DialogResult = "OK"
 
# 終了ボタンの設定
$ExitButton = New-Object System.Windows.Forms.Button
$ExitButton.Location = New-Object System.Drawing.Point(190,93)
$ExitButton.Size = New-Object System.Drawing.Size(75,30)
$ExitButton.Text = "終了"
$ExitButton.DialogResult = "Cancel"
 
#初期値
[int]$workTime = 25
[int]$breakTime = 5
[int]$setCount = 2
 
# 作業時間テキストボックス
$workTimeTextBox = New-Object System.Windows.Forms.TextBox
$workTimeTextBox.Location = New-Object System.Drawing.Point(10,50)
$workTimeTextBox.Size = New-Object System.Drawing.Size(100,50)
$workTimeTextBox.Text = $workTime
 
# 休憩時間テキストボックス
$breakTimeTextBox = New-Object System.Windows.Forms.TextBox
$breakTimeTextBox.Location = New-Object System.Drawing.Point(130,50)
$breakTimeTextBox.Size = New-Object System.Drawing.Size(100,50)
$breakTimeTextBox.Text = $breakTime
 
# セット数テキストボックス
$setCountTextBox = New-Object System.Windows.Forms.TextBox
$setCountTextBox.Location = New-Object System.Drawing.Point(250,50)
$setCountTextBox.Size = New-Object System.Drawing.Size(100,50)
$setCountTextBox.Text = $setCount
 
# 作業時間ラベルの設定
$workTimelabel = New-Object System.Windows.Forms.Label
$workTimelabel.Location = New-Object System.Drawing.Point(10,30)
$workTimelabel.Size = New-Object System.Drawing.Size(100,20)
$workTimelabel.Text = "作業時間[分]"
 
# 休憩時間ラベルの設定
$breakTimelabel = New-Object System.Windows.Forms.Label
$breakTimelabel.Location = New-Object System.Drawing.Point(130,30)
$breakTimelabel.Size = New-Object System.Drawing.Size(100,20)
$breakTimelabel.Text = "休憩時間[分]"
 
# セット数ラベルの設定
$setCountlabel = New-Object System.Windows.Forms.Label
$setCountlabel.Location = New-Object System.Drawing.Point(250,30)
$setCountlabel.Size = New-Object System.Drawing.Size(100,20)
$setCountlabel.Text = "セット数[回]"
 
# キーとボタンの関係
$form.AcceptButton = $OKButton
$form.CancelButton = $CancelButton
 
# ボタン等をフォームに追加
$form.Controls.Add($OKButton)
$form.Controls.Add($ExitButton)
$form.Controls.Add($workTimelabel)
$form.Controls.Add($breakTimelabel)
$form.Controls.Add($setCountlabel)
$form.Controls.Add($workTimeTextBox)
$form.Controls.Add($breakTimeTextBox)
$form.Controls.Add($setCountTextBox)

タイマー音のオブジェクト準備

時間が来たら音でお知らせしてくれるためのオーディオファイルの準備をします。

相対パスで指定してもよかったですが、ps1ファイルを移動した際に困るので、今回はフルパスでオーディオファイルを指定する形としています。

●New-Object system.windows.media.mediaplayerでオーディオファイルを再生するためのオブジェクトの作成ができます。

●$mediaPlayer.openで再生したいオーディオファイルを指定して、Play()メソッドで音声が流れます。今回は、時間が経過したタイミングでPlay()メソッドを実行して、音が流れるようにしています。

#タイマー音のオブジェクト準備
#ファイル名とパス
$WorkFinishMp3FilePath = "【各自の環境のmp3を置いた場所のフルパス】\WorkFinish.mp3"
$BreakTimeFinishMp3FilePath = "【各自の環境のmp3を置いた場所のフルパス】\BreaktimeFinish.mp3"
$mediaPlayer = New-Object system.windows.media.mediaplayer
$mediaPlayer.open($WorkFinishMp3FilePath)
$mediaPlayer.Play()

フォームの表示とボタンの引数

●$form.ShowDialog()で、作成したフォームを表示させることができます。ボタンを押したら、 DialogResultに設定した戻り値が返されます。今回は、終了ボタンの戻り値である「Cancel」が返されたらreturnで処理を終了させる作りにしました。

# フォームを表示させ、その結果を受け取る
$result = $form.ShowDialog()
 
    #終了ボタンを押したらreturnで終了する
    if($result -eq "Cancel"){
       return
    }

プログレスバーで待ち時間設定

大きく2つの仕組みになっています。

●foreachで毎秒Start-Sleepさせて時間制御

1分の作業時間の場合は、$workTimeSecondを60として、毎ループで1秒待機しています。なので、本当の本当にはほかの処理の分だけ1秒以上時間が経過しているのですが、まあほぼ誤差の範囲いないです。ポモドーロタイマーとしては25分で使うことが多いと思いますが、その場合は1秒以下の誤差だったので問題ないでしょう!

●Write-Processコマンドレットで進捗表示

-Activity には表示させたいメッセージを指定します。

-PercentCompleteには、進捗状況のパーセンテージを指定します。今回の場合は、指定された時間が経過したら100%になるような式を渡しています。

    # 作業時間開始
    # 指定した秒数分、繰り返す
    foreach ($i in (1..$workTimeSecond))
    {
        $restTime = [int]$workTimeSecond - ($i - 1)
        $message = "" + $currentSetCount + "セット目 作業中。あと" + $restTime + "秒..."
    
        # 進捗表示と待ち
        Write-Progress -Activity $message -PercentComplete ([int]((100 * $i)/$workTimeSecond))
        Start-Sleep -Second 1
    }

疑問、質問、間違い、要望などありましたらコメント欄へどうぞ👇