Skip to content

Instantly share code, notes, and snippets.

@esperecyan
Last active October 8, 2022 09:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save esperecyan/648b4831c6b65ea347abdf045451eb93 to your computer and use it in GitHub Desktop.
Save esperecyan/648b4831c6b65ea347abdf045451eb93 to your computer and use it in GitHub Desktop.
『life-and-death-monitor.ps1.jse』 通知領域 (タスクトレイ) に常駐する、Windows向けのシンプルなPowershell製死活監視ツールです。
#@~^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