きっかけ
以前、Mac miniで動かしていた「Desktopに置いたpngを自動で圧縮する」仕組みを、Windows側でも欲しくなりました。fswatch、launchd、osascriptはいずれもmacOS専用なので、Windowsでは素直に別物へ置き換える必要があります。
やりたいことは同じで、キャプチャフォルダを監視して、pngが増えたらpngquantとoxipngで圧縮し、通知だけ出してログファイルは一切残しません。この記事ではその実装を、実際にハマった箇所も含めてまとめます。
この記事でできるようになること
任意のフォルダ(今回はスクリーンショット保存先のCaptures)を監視して自動でpngを圧縮し、サインイン時に自動でバックグラウンド起動するようになります。圧縮完了時にはWindowsのトースト通知で削減率が分かり、ログファイルは一切残らない構成になります。
準備
パッケージマネージャはScoopを使います。管理者権限は不要で、非管理者権限での利用を前提に設計されているツールなので、通常のPowerShellで進めます。
※既にインストールされていれば不要です
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
irm get.scoop.sh | iex
scoopでインストール
scoop install pngquant oxipng
通知用にBurntToastも入れます。
Install-Module -Name BurntToast -Scope CurrentUser -Force
Set-ExecutionPolicy実行時に「アクセスが拒否されました」が出る場合は、管理者権限のPowerShellを開いていないか確認する。ScoopはあえてPATHの通し方を非管理者権限前提で設計しているため、管理者権限だと逆に警告が出て失敗することがあるようです。
監視スクリプト
C:\Users\<ユーザー名>\Scripts\png-compress-watch.ps1として保存します。
$global:WatchDir = "<スクショが保存されるフォルダ>"
$global:OutputDir = "$global:WatchDir\compressed"
New-Item -ItemType Directory -Force -Path $global:OutputDir | Out-Null
$watcher = New-Object System.IO.FileSystemWatcher
$watcher.Path = $global:WatchDir
$watcher.Filter = "*.png"
$watcher.IncludeSubdirectories = $false
$watcher.EnableRaisingEvents = $true
$action = {
$path = $Event.SourceEventArgs.FullPath
if ($path -like "*\compressed\*") { return }
if (-not (Test-Path $path)) { return }
$lastSize = -1
while ($true) {
if (-not (Test-Path $path)) { return }
$currentSize = (Get-Item $path).Length
if ($currentSize -eq $lastSize -and $currentSize -gt 0) { break }
$lastSize = $currentSize
Start-Sleep -Milliseconds 300
}
$filename = Split-Path $path -Leaf
$outputPath = Join-Path $global:OutputDir $filename
$beforeSize = (Get-Item $path).Length
& pngquant --quality=65-85 --output $outputPath --force $path 2>$null
if ($LASTEXITCODE -ne 0) {
Copy-Item $path $outputPath -Force
}
& oxipng -o 4 -q $outputPath 2>$null
$afterSize = (Get-Item $outputPath).Length
$reduced = [math]::Round(100 - ($afterSize * 100 / $beforeSize), 1)
$msg = "$filename : ${beforeSize}B to ${afterSize}B (${reduced} percent reduced)"
New-BurntToastNotification -Text "PNG Compress Done", $msg
}
Register-ObjectEvent -InputObject $watcher -EventName "Created" -Action $action | Out-Null
Register-ObjectEvent -InputObject $watcher -EventName "Renamed" -Action $action | Out-Null
Write-Host "Watching: $global:WatchDir"
while ($true) { Start-Sleep -Seconds 1 }
監視先はスクリーンショットやゲームバーの録画が保存されるフォルダにしました。Win+Alt+PrtScnで撮ったキャプチャがそのまま自動圧縮されます。
Renamedイベントも監視しているのには理由があります。Windowsの一部のキャプチャツールは、書き込み中は一時ファイル名で保存し、完了後に本来のファイル名へリネームします。Createdイベントだけだとこの変化を取りこぼすことがあるため、Renamedも合わせて監視対象にしています。
タスクスケジューラで常駐化
サインイン時の自動起動はタスクスケジューラに任せます。
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File `"$env:USERPROFILE\Scripts\png-compress-watch.ps1`""
$trigger = New-ScheduledTaskTrigger -AtLogOn
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero)
Register-ScheduledTask -TaskName "PngCompressWatch" -Action $action -Trigger $trigger -Principal $principal -Settings $settings
アクセス拒否のエラーが発生した場合はPowerShellを管理者権限で実行してみます。
タスクの起動・停止・削除は以下の通りです。
Stop-ScheduledTask -TaskName "PngCompressWatch"
Start-ScheduledTask -TaskName "PngCompressWatch"
Unregister-ScheduledTask -TaskName "PngCompressWatch" -Confirm:$false
動作確認
新しいPowerShellウィンドウを開いて(Scoopインストール直後はPATH反映のため必須です)手動実行します。
& "$env:USERPROFILE\Scripts\png-compress-watch.ps1"
Watching: <フォルダ名>と表示されたら、Win+Alt+PrtScnでキャプチャを撮ってみます。compressedフォルダに圧縮版が生成され、トースト通知が出れば成功です。問題なければCtrl+Cで止めて、タスクスケジューラ経由の常駐に任せます。
キャプチャして、
↓compressedに圧縮された画像が保存されます
まとめ
- fswatch pngquant oxipngをインストール(無ければscoopも)
- 監視スクリプトを作成
- タスクスケジューラで常駐化



