Last active
October 8, 2022 09:37
-
-
Save esperecyan/648b4831c6b65ea347abdf045451eb93 to your computer and use it in GitHub Desktop.
『life-and-death-monitor.ps1.jse』 通知領域 (タスクトレイ) に常駐する、Windows向けのシンプルなPowershell製死活監視ツールです。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#@~^AQAAAA==~IAAAAA==^#~@ function toPSString(str) { return "'" + str.replace(/%/g, '"%"').replace(/'/g, "''") + "'"; } /* -*- mode: powershell;-*- | |
<#*/ var command = 'param(\ | |
$Names, \ | |
$IntervalSeconds = 5 * 60, \ | |
$StartupDelaySeconds = 0, \ | |
$DefaultTemporaryHaltSeconds = 3 * 60 * 60)' | |
+ '; $_PSCommandPath = ' + toPSString(WSH.ScriptFullName) | |
+ '; Invoke-Expression (Get-Content ' + toPSString(WSH.ScriptFullName) + ' -Encoding UTF8 -Raw)'; | |
var namePattern = /^-(?!(?:b(?:and|or|xor|not)|sh[lr]|[ic]?(?:eq|ne|gt|ge|lt|le|(?:not)?(?:like|match|contains|in)|replace|split)|join|is(?:not)?|as|and|or|not|f)$)[0-9a-z]+$/i; | |
var args = ''; for (var i = 0; i < WSH.Arguments.Length; i++) { | |
var arg = WSH.Arguments(i); args += ' ' + (namePattern.test(arg) ? arg : toPSString(arg)); } | |
WSH.CreateObject('WScript.Shell').Run('PowerShell -Command &{' + command + '}' + args, 0); /*#> | |
<# | |
.SYNOPSIS | |
通知領域 (タスクトレイ) に常駐するシンプルな死活監視ツールです。 | |
.DESCRIPTION | |
ショートカットを作成し、以下の例のように「-Names」パラメータを追加して、スタートアップフォルダなどに置いておきます。 | |
Licence: MPL-2.0 <https://www.mozilla.org/MPL/2.0/> | |
Version: 2.0.0 | |
Author: 100の人 | |
配布元: <https://twitter.com/esperecyan/status/1131887428594049024> | |
.EXAMPLE | |
life-and-death-monitor.ps1.jse -Names example,task:test | |
→ 「example.exe」が起動しているか、「test」という名前のタスクが有効になっているかを監視します。 | |
.PARAMETER Names | |
拡張子を除いたプロセス名を、「,」区切りで指定します。 | |
「task:」を前置すると、タスク名を指定したことになります。 | |
「,」を含むプロセス名などには対応していません。 | |
.PARAMETER IntervalSeconds | |
チェック間隔。1以上の秒数。既定値は5分。 | |
.PARAMETER StartupDelaySeconds | |
起動直後のチェック間隔。1以上の秒数。指定しなかった場合は「-IntervalSeconds」と同じ値になります。 | |
.PARAMETER DefaultTemporaryHaltSeconds | |
監視を一時停止するときの、自動的に再開するまでの間隔を指定するダイアログの既定値。1以上の秒数。既定値は3時間。 | |
#> | |
using namespace System.Collections.Generic | |
using namespace System.Drawing | |
using namespace System.Windows.Forms | |
using namespace System.Management.Automation | |
using namespace Microsoft.PowerShell.Cmdletization.GeneratedTypes.ScheduledTask | |
using namespace Esperecyan.Net | |
<# | |
.SYNOPSIS | |
例外の詳細を警告ダイアログで表示し、スクリプトを終了します。 | |
.PARAMETER ErrorRecord | |
catch { } 内の $_ 。 | |
#> | |
function Exit-Script([ErrorRecord]$ErrorRecord) | |
{ | |
[MessageBox]::Show( | |
"例外が発生しました。スクリプトを終了します。`n`n【例外メッセージ】(Ctrl+Cでコピー可能)`n$('-' * 78)`n" ` | |
+ "$($ErrorRecord | Out-String)`nScriptStackTrace:`n$($ErrorRecord.ScriptStackTrace)", | |
$SCRIPT_NAME, | |
[MessageBoxButtons]::OK, | |
[MessageBoxIcon]::Error, | |
[MessageBoxDefaultButton]::Button1, | |
[MessageBoxOptions]::DefaultDesktopOnly | |
) | Out-Null | |
try { | |
$notifyIcon.Visible = $false | |
} catch { | |
} | |
[Environment]::Exit(0) | |
} | |
try { Set-StrictMode -Version Latest; $ErrorActionPreference = 'Stop'; | |
Add-Type -AssemblyName @('System.Drawing', 'System.Windows.Forms') | |
Add-Type -Name ('ShellAPI') -Namespace 'Esperecyan.Net' -MemberDefinition @(' | |
[DllImport("shell32.dll")] | |
public static extern int ExtractIconEx( | |
string lpszFile, | |
int nIconIndex, | |
out System.IntPtr phiconLarge, | |
out System.IntPtr phiconSmall, | |
int nIcons | |
); | |
') | |
<# | |
.SYNOPSIS | |
ファイルからアイコンを取得します。 | |
.PARAMETER Path | |
「shell32.dll」のようなファイルパス。 | |
.PARAMETER Index | |
0から始まる、ファイル内のアイコンのインデックス。 | |
.OUTPUTS | |
アイコン。 | |
#> | |
function Get-IconFromFile | |
{ | |
[OutputType([Icon])] | |
param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][int]$Index) | |
$phiconLarge = New-Object IntPtr | |
$phiconSmall = New-Object IntPtr | |
[ShellAPI]::ExtractIconEx($Path, $Index, [ref]$phiconLarge, [ref]$phiconSmall, 1) | Out-Null | |
[Icon]::FromHandle($phiconSmall).Dispose() | |
$icon = [Icon]::FromHandle($phiconLarge) | |
$icon.Dispose() | |
return $icon | |
} | |
$SCRIPT_NAME = 'Life and Death Monitor' | |
$SCRIPT_ICON = Get-IconFromFile -Path (Get-Command -Name @('PowerShell')).Definition -Index 0 | |
$bitmap = $SCRIPT_ICON.ToBitmap() | |
$graphics = [Graphics]::FromImage($bitmap) | |
$graphics.DrawIcon((Get-IconFromFile -Path 'shell32.dll' -Index 109), 0, 0) | |
$graphics.Dispose() | |
$SCRIPT_HALT_ICON = [Icon]::FromHandle($bitmap.GetHicon()) | |
$bitmap.Dispose() | |
$LIVING_ICON_IMAGE = (Get-IconFromFile -Path 'shell32.dll' -Index 296).ToBitmap() | |
$DEAD_ICON_IMAGE = (Get-IconFromFile -Path 'shell32.dll' -Index 131).ToBitmap() | |
<# | |
.SYNOPSIS | |
イベントを妨げない状態で、スクリプトを終了させずに待機します。 | |
#> | |
function Wait-Exit | |
{ | |
$form = New-Object Form | |
$form.Text = $SCRIPT_NAME | |
$form.ShowInTaskbar = $false | |
$form.StartPosition = [FormStartPosition]::Manual | |
$form.Location = New-Object Point([int]::MaxValue, [int]::MaxValue) | |
$form.Add_Closed($exit) | |
$form.ShowDialog() | |
} | |
<# | |
.SYNOPSIS | |
秒数をたずねるダイアログを表示します。 | |
.PARAMETER DefaultSeconds | |
既定の秒数。 | |
.PARAMETER Message | |
ダイアログに表示するテキスト。 | |
.OUTPUTS | |
ユーザー指定の秒数。キャンセルされた場合は 0。 | |
#> | |
function Show-SecondsDialog | |
{ | |
[OutputType([int])] | |
param([Parameter(Mandatory)][int]$DefaultSeconds, [Parameter(Mandatory)][string]$Message) | |
$today = [DateTime]('{0:d}' -f (Get-Date)) | |
$form = New-Object Form | |
$form.Text = $SCRIPT_NAME | |
$form.AutoSize = $true | |
$panel = New-Object TableLayoutPanel | |
$panel.ColumnCount = 2 | |
$panel.RowCount = 3 | |
$panel.AutoSize = $true | |
$form.Controls.Add($panel) | |
$labelContorol = New-Object Label | |
$labelContorol.Text = $Message | |
$labelContorol.AutoSize = $true | |
$panel.Controls.Add($labelContorol) | |
$panel.SetColumnSpan($labelContorol, 2) | |
$timePicker = New-Object DateTimePicker | |
$timePicker.MinDate = $today | |
$timePicker.Format = [DateTimePickerFormat]::Time | |
$timePicker.ShowUpDown = $true | |
$timePicker.Value = $today.AddSeconds($DefaultSeconds) | |
$panel.Controls.Add($timePicker) | |
$panel.SetColumnSpan($timePicker, 2) | |
$okButton = New-Object Button | |
$okButton.DialogResult = [DialogResult]::OK | |
$okButton.Text = 'OK' | |
$panel.Controls.Add($okButton) | |
$form.AcceptButton = $okButton | |
$cancelButton = New-Object Button | |
$cancelButton.DialogResult = [DialogResult]::Cancel | |
$cancelButton.Text = 'キャンセル' | |
$panel.Controls.Add($cancelButton) | |
$form.CancelButton = $cancelButton | |
if ($form.ShowDialog() -ne [DialogResult]::OK) { | |
return 0 | |
} | |
return [int]($timePicker.Value - $today).TotalSeconds | |
} | |
$statusMenuItems = $Names -split ',' | ForEach-Object { New-Object ToolStripMenuItem($_, $null, $null, $_) } | |
$restart = { | |
$haltTimer.Stop() | |
$notifyIcon.Icon = $SCRIPT_ICON | |
$haltMenuItem.Text = '一時的に監視を停止' | |
& $confirmProcesses | |
} | |
$haltTimer = New-Object Timer | |
$haltMenuItem = New-Object ToolStripMenuItem('一時的に監視を停止', $null, { | |
if ($haltTimer.Enabled) { | |
& $restart | |
return | |
} | |
$timer.Stop() | |
$haltMenuItem.Enabled = $false | |
$haltSeconds = Show-SecondsDialog -DefaultSeconds $DefaultTemporaryHaltSeconds -Message '停止する時間' | |
if ($haltSeconds) { | |
$notifyIcon.Icon = $SCRIPT_HALT_ICON | |
$haltMenuItem.Text = '監視を再開 ({0:t}に自動で再開)' -f (Get-Date).AddSeconds($haltSeconds) | |
$haltTimer.Interval = $haltSeconds * 1000 | |
$haltTimer.Start() | |
} else { | |
& $confirmProcesses | |
} | |
$haltMenuItem.Enabled = $true | |
}) | |
$haltTimer.Add_Tick($restart) | |
$exit = { | |
$haltTimer.Stop() | |
$timer.Stop() | |
$notifyIcon.Visible = $false | |
[Environment]::Exit(0) | |
} | |
$notifyIcon = New-Object NotifyIcon | |
$notifyIcon.Icon = $SCRIPT_ICON | |
$notifyIcon.Text = $SCRIPT_NAME | |
$contextMenu = New-Object ContextMenuStrip | |
$contextMenu.Items.AddRange(@( | |
$haltMenuItem, | |
(New-Object ToolStripSeparator) | |
) + $statusMenuItems + @( | |
(New-Object ToolStripSeparator), | |
(New-Object ToolStripMenuItem('終了', $null, { | |
if ([DialogResult]::OK -ne [MessageBox]::Show( | |
'本当に終了してもよろしいですか?', | |
$SCRIPT_NAME, | |
[MessageBoxButtons]::OKCancel, | |
[MessageBoxIcon]::Question, | |
[MessageBoxDefaultButton]::Button1, | |
[MessageBoxOptions]::DefaultDesktopOnly | |
)) { | |
return | |
} | |
& $exit | |
})) | |
)) | Out-Null | |
$notifyIcon.ContextMenuStrip = $contextMenu | |
$notifyIcon.Visible = $true | |
$confirmProcesses = { | |
try { | |
$timer.Stop() | |
$processNames = Get-Process | ForEach-Object { $_.Name } | |
$tooltip = 'Last living time: {0:o}' -f (Get-Date) | |
$deadMessages = @() | |
foreach ($item in $statusMenuItems) { | |
$name = $item.Text | |
if ($name -like 'task:*') { | |
# タスク名 | |
$name = $name -replace '^task:', '' | |
try { | |
$task = Get-ScheduledTask -TaskName $name | |
} catch { | |
} | |
if ($task) { | |
if (($task.State -eq [StateEnum]::Ready) -or ($task.State -eq [StateEnum]::Running)) { | |
$item.Image = $LIVING_ICON_IMAGE | |
$item.ToolTipText = $tooltip | |
continue; | |
} | |
$deadMessages += "• タスク「$name」は無効になっています。" | |
} else { | |
$deadMessages += "• 「$name」という名前のタスクは見つかりません。" | |
} | |
} else { | |
# プロセス名 | |
if ($name -in $processNames) { | |
$item.Image = $LIVING_ICON_IMAGE | |
$item.ToolTipText = $tooltip | |
continue | |
} | |
$deadMessages += "• 「$name」という名前のプロセスは見つかりません。" | |
} | |
$item.Image = $DEAD_ICON_IMAGE | |
} | |
if ($deadMessages) { | |
[MessageBox]::Show( | |
($deadMessages -join "`n"), | |
$SCRIPT_NAME, | |
[MessageBoxButtons]::OK, | |
[MessageBoxIcon]::Error, | |
[MessageBoxDefaultButton]::Button1, | |
[MessageBoxOptions]::DefaultDesktopOnly | |
) | |
} | |
$timer.Interval = [int]$IntervalSeconds * 1000 | |
$timer.Start() | |
} catch { | |
Exit-Script -ErrorRecord $_ | |
} | |
} | |
$timer = New-Object Timer | |
if ([int]$StartupDelaySeconds -le 0) { | |
$StartupDelaySeconds = $IntervalSeconds | |
} | |
$timer.Interval = [int]$StartupDelaySeconds * 1000 | |
$timer.Add_Tick($confirmProcesses) | |
$timer.Start() | |
# 待機 | |
Wait-Exit | |
} catch { | |
Exit-Script -ErrorRecord $_ | |
} # */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment