0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

指定ファイル作成から一定時間後に通知.ps1

Posted at

1. スクリプト概要

スクリプト名: 指定ファイル作成から一定時間後に通知.ps1
監視対象フォルダ内のファイルが作成(または更新)されてから指定時間が経過した際に、通知で知らせます

2. 処理と目的

処理の流れ
 1. 監視対象フォルダの指定: 監視したいフォルダのパスを設定します
 2. 監視対象ファイルの指定: 監視したいファイルのパターン(例: *.log)を設定します
 3. 通知保留ファイルパターンの指定: 通知を一時的に保留したいファイルのパターンを設定します。
 4. 通知基準プロパティの指定: 通知の基準となるファイルプロパティ(作成日時または更新日時)を選択します
 5. 通知までの時間指定: ファイル作成(または更新)から通知を行うまでの秒数を設定します
 6. タスクトレイへの格納: スクリプトのウィンドウをタスクバーから非表示にし、タスクトレイに表示します
 7. 監視対象ファイルの検索: 監視対象フォルダ内で、指定された監視対象ファイルを検索します
 8. 通知保留ファイルの検索: 監視対象フォルダ内で、指定された通知保留ファイルパターンに合致するファイルを検索します
 9. 待機条件の確認: 監視対象ファイルがない場合、または通知保留パターンに合致するファイルが存在する場合は、手順7に戻ります
10. 通知時間までの待機: 監視対象ファイルの基準プロパティから、通知までの指定時間が経過するまで待機します
11. バルーンと通知音での通知: 指定時間が経過したら、バルーン通知と通知音で知らせます
12. 繰り返し: 手順7に戻り、監視を継続します

目的
 ファイルのダウンロード完了や、時間のかかる処理の完了をより簡単に把握できるようにするためです。ファイルの更新がある程度収まった段階で通知することで、ユーザーが待機する手間を省き、作業効率を向上させます

3. 動作環境と要件

PowerShellのバージョン
7.0以上

OS
Windows10

必要なモジュール
特になし

必要な権限
特になし

その他の設定
特になし

4. 使用方法

基本的な実行方法
スクリプトコードを拡張子ps1で保存してPowershellで実行してください。
ファイルを保存する際は、文字コードをUTF8 BOM付にしてください。

パラメータ
なし

使用例

  1. コマンドラインでpwsh 指定ファイル作成から一定時間後に通知.ps1を実行
  2. 監視対象フォルダの入力を求められるので、フォルダのフルパスを入力します
  3. 監視対象ファイルパターンを求められるので、ファイルの検索パターンを入力します
  4. 通知保留ファイルパターンを求められるので、ファイルの検索パターンを入力します
  5. 通知基準プロパティを求められるので、作成日時または更新日時を入力します
  6. 通知までの秒数をを求められるので、正の整数を入力します
  7. 終了する場合はタスクトレイアイコンを右クリックしてExitを選択してください

5. スクリプトコード

if ($PSVersionTable.PSVersion.Major -ge 7 -and ((ps -Id $PID | ?{$_.Parent.ProcessName -notin @('pwsh','powershell')}) -ne $null)) {
    cls
}

# コンソールウィンドウのタイトルをスクリプト名に設定します。
$host.UI.RawUI.WindowTitle = ([IO.Path]::GetFilenameWithoutExtension($PSCommandPath))

# エラー発生時にスクリプトの実行を停止するよう設定します。
$ErrorActionPreference = 'Stop'

# Windows Formsアセンブリをロードし、GUI要素(通知アイコンなど)を使用できるようにします。
Add-Type -AssemblyName System.Windows.Forms


# region 変数定義
# 定期実行関数の間隔をミリ秒単位で設定します(例: 60秒 = 60 * 1000ミリ秒)。
$TIMER_INTERVAL = 60 * 1000 
# スクリプトの多重起動を防ぐためのミューテックス名を定義します。
# スクリプト名とフォルダパスのハッシュコードを組み合わせることで、ユニークなミューテックス名を生成します。
$MUTEX_NAME = "Global\mutex_$([IO.Path]::GetFilenameWithoutExtension($PSCommandPath))" 
# 監視対象フォルダのパスを格納する変数です。
$SEARCH_FOLDER = ''
# 監視対象ファイルのパターンを格納する変数です(例: *.txt, *.log)。
$SEARCH_FILES = ''
# ファイル作成(更新)から通知までの時間を秒単位で格納する変数です。
$NOTIFY_INTERVAL_SECONDS = 0
# 通知基準とするファイルプロパティ名('CreationTime' または 'LastWriteTime')を格納する変数です。
$PROPERTY_NAME = ''
# endregion

# 定期的に実行されるメインの処理を定義する関数です。
function timer_function($notify){
    $datetime = (Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
    Write-Host "timer_function "  $datetime

    # 以下のコメントアウトされた行は、ツールチップやバルーン通知のサンプルコードです。
    # 必要に応じてコメントを解除して使用できます。
    # $notify.Text = $datetime
    # $notify.BalloonTipIcon = 'Info'
    # $notify.BalloonTipText = $datetime
    # $notify.BalloonTipTitle = 'sample'
    # $notify.ShowBalloonTip(1000)
    
    # 午前5時までの間は処理を行いません。夜間の不要な通知を避けるためです。
    $nowtime = (Get-Date).TimeOfDay
    if($nowtime.Hours -le 5){
        return
    }
    
    # 監視対象フォルダとファイルパターンを設定します。
    $dirname = $SEARCH_FOLDER
    $searchExtension = $SEARCH_FILES
    
    # 監視対象フォルダ内の指定されたファイルの中から、基準プロパティ(作成日時または更新日時)が最新のファイルを見つけます。
    $maxfiletime = ls -literal $dirname -Filter $searchExtension | Measure -Maximum -Property $PROPERTY_NAME

    # 最新のファイルが見つかった場合のみ処理を続行します。
    if($maxfiletime){
        $maxfiletime = $maxfiletime.Maximum

        Write-Host $maxfiletime
        # 現在時刻と最新ファイルの基準プロパティの差を計算します。
        $difftime = ((Get-Date) - ($maxfiletime))
        
        # 通知間隔(秒)をTimeSpanオブジェクトに変換します。
        $notifyInterval = New-TimeSpan -Seconds $NOTIFY_INTERVAL_SECONDS
        
        # 通知間隔の表示形式を整形します(日、時間、分、秒)。
        if($notifyInterval.TotalDays -ge 1.0){
            $intervalText = $notifyInterval.TotalDays.ToString("0.00") + "D"
        } elseif($notifyInterval.TotalHours -ge 1.0){
            $intervalText = $notifyInterval.TotalHours.ToString("0.00") + "H"
        } elseif($notifyInterval.TotalMinutes -ge 1.0){
            $intervalText = $notifyInterval.TotalMinutes.ToString("0.00") + "M"
        } else {
            $intervalText = $notifyInterval.TotalSeconds.ToString("0.00") + "S"
        }
        
        # タスクトレイアイコンのツールチップテキストを作成します。
        $notifyText = "「${SEARCH_FOLDER}」「${SEARCH_FILES}」「${intervalText}`n次回:$(($maxfiletime+$notifyInterval).ToString('HH:mm:ss'))"
        # ツールチップテキストが長すぎる場合は切り詰めます。
        if($notifyText.Length -ge 64){
            $notifyText = $notifyText.substring(0,63)
        }
        # タスクトレイアイコンのテキストを更新します。
        $notify.Text = $notifyText
        
        
        # 次回のタイマー処理までに通知条件を満たす可能性がある場合、その時間だけ待機します。
        # これにより、通知が設定時間ぴったりに行われるように調整されます。
        if( ($difftime -lt $notifyInterval) -and (($difftime.TotalMilliseconds+$TIMER_INTERVAL) -gt $notifyInterval.TotalMilliseconds) ){
            $overTimerMilliseconds = ($notifyInterval.TotalMilliseconds - $difftime.TotalMilliseconds)
            if( $overTimerMilliseconds -gt 0 ){
                sleep -Milliseconds $overTimerMilliseconds
                $difftime = $notifyInterval # 待機後、通知間隔に達したと見なす
            }
        }
        
        # 通知保留ファイルパターンに合致するファイルがあるか検索します。
        $waitfiles = ls -literal $dirname -Filter $WAIT_FILE_PATTERN
        
        # 通知保留ファイルが存在する場合、タスクトレイにその情報を表示します。
        if($waitfiles -ne $null){
            Write-Host "exist=$($WAIT_FILE_PATTERN)" -ForegroundColor White
            $notify.Text = "検索ファイル:「${SEARCH_FOLDER}`n待機ファイル:「$($waitfiles| %{$_.Name})」"
            # テキストが長すぎる場合は切り詰めます。
            if($notify.Length -ge 64){
                $notify.Text = $notify.substring(0,63)
            }
            
        # 通知保留ファイルがなく、かつ通知間隔を過ぎている場合、通知を行います。
        } elseif ($difftime -ge $notifyInterval){
            # ランダムな音を再生します。
            Get-Random -SetSeed ([IO.Path]::GetFilenameWithoutExtension($PSCommandPath)).GetHashCode();(1..4)| %{([Console]::Beep((Get-Random -Min 100 -Max 1000),200))}
            
            # バルーン通知の本文(TipText)を作成します。
            $tipText = ""
            $tipText += "フォルダ=${SEARCH_FOLDER}`n"
            $tipText += "ファイル=${SEARCH_FILES}`n"
            $tipText += "通知待機時間=${notifyInterval}`n"
            
            # バルーン通知のタイトル(TipTitle)を作成します。
            $tipTitle = ""
            $tipTitle += "対象ファイルの作成から "
            if($difftime.Days -gt 0){
                $tipTitle += "$($difftime.Days)日"
            }
            if($difftime.Hours -gt 0){
                $tipTitle += "$($difftime.Hours)時間"
            }
            if($difftime.Minutes -gt 0){
                $tipTitle += "$($difftime.Minutes)分"
            }
            if($difftime.Seconds -gt 0){
                $tipTitle += "$($difftime.Seconds)秒"
            }
            $tipTitle += " 経ちました。"
            
            # バルーン通知を表示します。
            $notify.BalloonTipIcon = 'Info'
            $notify.BalloonTipText = $tipText
            $notify.BalloonTipTitle = $tipTitle
            $notify.ShowBalloonTip(1000)
            
        # 通知間隔に達していない場合、残り時間をコンソールに表示します。
        } else {
            Write-Host "remain=$(60-$difftime.TotalMinutes) min" -ForegroundColor White
        }
    }
    
}

# スクリプトのメイン処理を定義する関数です。
function main(){
    
    # スクリプトの概要と使い方に関するメッセージをコンソールに表示します。
    Write-Host "------------------------------------------------------"
    Write-Host "対象フォルダ内の対象ファイルが作成(更新)されてから" -ForegroundColor White -BackGroundColor Black
    Write-Host "指定時間経過後にバルーンと通知音で通知します" -ForegroundColor White -BackGroundColor Black
    Write-Host "通知を保留するファイルのパターンも指定可能です" -ForegroundColor White -BackGroundColor Black
    Write-Host "通知の待機中はタスクトレイに格納されます" -ForegroundColor White -BackGroundColor Black
    Write-Host "------------------------------------------------------"
    
    
    # 監視対象フォルダのパスをユーザーに入力させます。無効なパスの場合は再入力を促します。
    do {
        $SEARCH_FOLDER = Read-Host '監視対象のフォルダパス(初期値:D:\Me Document\Downloaded Files)'
        if($SEARCH_FOLDER -eq ''){
            $SEARCH_FOLDER = 'D:\Me Document\Downloaded Files'
        }
    } while(-not ([IO.Directory]::Exists($SEARCH_FOLDER)))
    
    # 監視対象ファイルのパターンをユーザーに入力させます。無効なパターンは再入力を促します。
    do {
        $SEARCH_FILES = Read-Host '監視対象のファイル(*.*形式)(初期値:*.rar)'
        if($SEARCH_FILES -eq ''){
            $SEARCH_FILES = '*.rar'
        }
    } while(-not ($SEARCH_FILES -match '([^.]+)(\.)([^.]+)'))
    
    # 通知を保留するファイルパターンをユーザーに入力させます。無効なパターンは再入力を促します。
    do {
        $WAIT_FILE_PATTERN = Read-Host '通知保留を行うファイル(*.*形式)(初期値:*.part)'
        if($WAIT_FILE_PATTERN -eq ''){
            $WAIT_FILE_PATTERN = '*.part'
        }
    } while(-not ($WAIT_FILE_PATTERN -match '([^.]+)(\.)([^.]+)'))
    
    # 基準とするファイルプロパティ(CreationTimeまたはLastWriteTime)をユーザーに選択させます。
    do {
        $inputText = Read-Host 'ファイルのプロパティ名(0:CreationTime、1:LastWriteTime)(初期値:CreationTime)'
        if($inputText -in ('','0')){
            $PROPERTY_NAME = 'CreationTime'
        }
        if($inputText -eq '1'){
            $PROPERTY_NAME = 'LastWriteTime'
        }
        
    } while(-not ($PROPERTY_NAME -ne ''))
    
    
    # ファイル作成(更新)から通知までの時間を秒単位でユーザーに入力させます。
    do {
        $inputText = Read-Host '作成から通知までの時間(秒)(初期値:60*60)'
        if($inputText -eq ''){
            $inputText = "60*60"
        }
        # 入力された文字列を式として評価し、秒数を設定します。
        $NOTIFY_INTERVAL_SECONDS = Invoke-Expression $inputText
        
    } while( -not ($NOTIFY_INTERVAL_SECONDS -gt 1))
    
    # 設定された内容をコンソールに表示し、ユーザーに確認を促します。
    Write-Host "監視対象のフォルダ:${SEARCH_FOLDER}" -ForegroundColor Cyan
    Write-Host "監視対象のファイル:${SEARCH_FILES}" -ForegroundColor Cyan
    Write-Host "通知保留のファイル:${WAIT_FILE_PATTERN}" -ForegroundColor Cyan
    Write-Host "ファイルのプロパティ:${PROPERTY_NAME}" -ForegroundColor Cyan
    Write-Host "ファイル作成から通知までの時間:$(New-TimeSpan -Seconds $NOTIFY_INTERVAL_SECONDS)" -ForegroundColor Cyan
    pause
    
    # 多重起動を防ぐためのミューテックスを作成します。
    # スクリプト名とフォルダパスのハッシュコードを組み合わせて、このスクリプトと特定のフォルダパスの組み合わせでユニークなミューテックスを生成します。
    $mutex = New-Object System.Threading.Mutex($false, ($MUTEX_NAME+$SEARCH_FOLDER.GetHashCode().ToString()))
    
    # ミューテックスを取得して多重起動をチェックします。
    # 既に同じミューテックスが取得されている場合は、別のインスタンスが実行中であることを意味します。
    if ($mutex.WaitOne(0, $false)){
        # タスクバーからスクリプトウィンドウを非表示にするためのWin32 APIを定義します。
        $windowcode = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'
        $asyncwindow = Add-Type -MemberDefinition $windowcode -name Win32ShowWindowAsync -namespace Win32Functions -PassThru
        # 現在のプロセスのメインウィンドウハンドルを取得し、非表示にします(0はSW_HIDEに対応)。
        $null = $asyncwindow::ShowWindowAsync((Get-Process -PID $pid).MainWindowHandle, 0)

        # アプリケーションコンテキストを作成し、Windows Formsアプリケーションのイベントループを管理します。
        $application_context = New-Object System.Windows.Forms.ApplicationContext
        # タイマーオブジェクトを作成します。
        $timer = New-Object Windows.Forms.Timer
        # スクリプトの実行パスからアイコンを抽出し、タスクトレイアイコンとして使用します。
        $path = Get-Process -id $pid | Select-Object -ExpandProperty Path # icon用
        $icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path)

        # タスクトレイアイコンオブジェクトを作成し、設定します。
        $notify_icon = New-Object System.Windows.Forms.NotifyIcon
        $notify_icon.Text = [IO.Path]::GetFilenameWithoutExtension($PSCommandPath) # ツールチップに表示されるテキスト
        $notify_icon.Icon = $icon # 設定したアイコン
        $notify_icon.Visible = $true # アイコンを表示します。

        # タスクトレイアイコンがクリックされた際のイベントハンドラを設定します。
        # 左クリックされた場合、タイマーを停止して即座に再開することで、timer_functionをすぐに実行します。
        $notify_icon.add_Click({
            if ($_.Button -eq [Windows.Forms.MouseButtons]::Left) {
                $timer.Stop()
                $timer.Interval = 1 # インターバルを1ミリ秒に設定して即時実行を促します
                $timer.Start()
            }
        })

        # タスクトレイアイコンの右クリックメニューを作成します。
        if( $PSVersionTable.PSVersion -ge (New-Object System.Version(6,0,0,0)) ){
            # PowerShell Coreの場合のメニューアイテムの作成
            $menu_item_exit = New-Object System.Windows.Forms.ToolStripMenuItem
            $menu_item_exit.Text = "Exit"
            $notify_icon.ContextMenuStrip = New-Object System.Windows.Forms.ContextMenuStrip
            $notify_icon.ContextMenuStrip.Items.Add($menu_item_exit)
        } else {
            # Windows PowerShellの場合のメニューアイテムの作成
            $menu_item_exit = New-Object System.Windows.Forms.MenuItem
            $menu_item_exit.Text = "Exit"
            $notify_icon.ContextMenu = New-Object System.Windows.Forms.ContextMenu
            $notify_icon.contextMenu.MenuItems.AddRange($menu_item_exit)
            
        }

        # 「Exit」メニューアイテムがクリックされた際のイベントハンドラを設定します。
        # クリックされるとアプリケーションのイベントループを終了し、スクリプトを終了させます。
        $menu_item_exit.add_Click({
            $application_context.ExitThread()
        })

        # タイマーイベントハンドラを設定します。
        # タイマーがTickイベントを発生させるたびに、timer_functionを実行し、その後タイマーを再設定して再開します。
        $timer.Enabled = $true
        $timer.Add_Tick({
            $timer.Stop() # 処理中に次のTickイベントが発生しないように一時停止

            timer_function($notify_icon) # 定期実行処理を呼び出し

            # インターバルを再設定してタイマーを再開します。
            $timer.Interval = $TIMER_INTERVAL
            $timer.Start()
        })

        # 初回のtimer_functionを即座に実行するためにタイマーを起動します。
        $timer.Interval = 1
        $timer.Start()

        # Windows Formsアプリケーションのイベントループを開始します。
        # これにより、通知アイコンやタイマーイベントが処理されます。
        [void][System.Windows.Forms.Application]::Run($application_context)

        # イベントループが終了したら、タイマーを停止し、通知アイコンを非表示にします。
        $timer.Stop()
        $notify_icon.Visible = $false
        # 取得していたミューテックスを解放します。
        $mutex.ReleaseMutex()
    }
    # ミューテックスを閉じます。
    $mutex.Close()
}

# main関数を呼び出し、スクリプトの実行を開始します。
main

6. 注意事項と既知の問題

制約事項
スクリプト実行中はタスクバーから非表示になり、タスクトレイに格納されるため、起動中であることが分かりにくい場合があります

既知のバグ
もしバグを発見された場合は、コメントでご報告ください。

トラブルシューティング
・ps1ファイルのエンコーディングには注意してください。

7. 免責事項

本スクリプトにはいかなる保証もありません。使用は自己責任で行ってください。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?