Windowsの謎
Windowsを使用していて、たまに???となることがある。それは長年のOS開発、更改により、つぎはぎ的に機能を追加(Apache?)、あるいはGUIを新たに被せていくことにより生じた、矛盾である。
例えばサービス一覧は、GUIで表示される情報をそのまま出してくれるコマンドが見当たらない。
GUIのサービス一覧
GUIでサービス一覧を表示させると、「スタートアップの種類」には(遅延開始)(トリガー開始)といった情報が表示される。
ところが、同じことをPowerShellまたは CMDで実現しようとすると、上記の情報をGUIと同じように出力するコマンドはない。
強いて言えば services.msc を起動して 出力内容をファイルにエクスポートする方法があげられるが、コマンドでそのまま加工するには不便である。
旧来のコマンド sc.exe
古くから伝わるコマンドにsc.exe または net start というものがある。
これはサービスの情報を表示するものの、トリガー情報は個別にqtriggerinfo というコマンドで取得する為、コードをループさせてネストさせないとうまく情報が取れない。。
Powershell
Get-Service というコマンドレットがある。
これも残念ながら英語表記で トリガー情報は全く入ってこない。
Win32 API
仕方なく根幹のAPIを当ってみる。
なんとWin32のAPI Win32_Serviceですら トリガー情報を持っていない。
レジストリ!
HKLMの配下のサービス情報を見てみる。
あった!TriggerInfo!
えっと!?これがあるとトリガー開始で、ないとトリガー開始ではない…
遅延開始情報
遅延開始の情報はWin32_Serviceが DelayedAutoStartというキーで持っている。
これが1だと遅延開始?
いや、StartTypeがAutoでさらにDelayedAutoStartだと遅延開始となる。
まとめ
以上の情報をもって組むと下記の通りとなる。
いちいちループ内でレジストリのパス情報を検査するので遅い。
できれば最初に全部持ってきてメモリ上に展開してマッチングしたいが…
そんな暇はない。
$Services = Get-WmiObject -Class Win32_Service
foreach ($Service in $Services) {
$ts = 0
$ds = 0
switch($Service.StartMode) {
"Manual" { $JS = "手動" }
"Disabled" { $JS = "無効" }
"Auto" { $JS = "自動" }
}
$no = New-Object -TypeName PSObject -Property @{
Status = $Service.State
Name = $Service.Name
DisplayName = $Service.DisplayName
StartMode = $JS
}
if (Test-Path -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($Service.Name)\TriggerInfo\") {
$ts = 1
}
if ($Service.StartMode -eq "Auto" -and $Service.DelayedAutoStart -eq 1)
$ds = 1
}
if ($ts -eq 1 -Or $ds -eq 1) {
if ($ts -eq 1 -and $ds -eq 1) {
$no.StartMode = "$($JS) (遅延開始、トリガー開始)"
}
Elseif ($ts -eq 1) {
$no.StartMode = "$($JS) (トリガー開始)"
}
Elseif ($ds -eq 1) {
$no.StartMode = "$($JS) (遅延開始)"
}
}
$no
}
と思ったらPowerShellの達人が現れてレジストリの検索を一回にしてくれたので再掲します。
ありがとうございました。
予めレジストリのTriggerInfoを持っているエントリの名前を配列にしておくというのが秀逸ですね。
$triggers = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" |
Where-Object { $_.GetSubkeyNames().Contains("TriggerInfo") } |
ForEach-Object { $_.Name.Split("\")[-1] }
$startMode = @{ Manual = "手動"; Disabled = "無効"; Auto = "自動"; Unknown = "不明" }
$startOption = @{ 01 = " (トリガー開始)"; 10 = " (遅延開始)"; 11 = " (遅延開始、トリガー開始)" }
$serviceData = Get-CimInstance -ClassName Win32_Service | Select-Object @(
@{ n = "表示名"; e = { $_.DisplayName } }
@{ n = "サービス名"; e = { $_.Name } }
@{ n = "スタートアップの種類"; e = { $startMode[$_.StartMode] + $startOption[10 * ($_.StartMode -eq "Auto" -and $_.DelayedAutoStart) + $triggers.Contains($_.Name)] } }
@{ n = "状態"; e = { if($_.State -eq "Running") { "実行" } else { "停止" } } }
)
$serviceData
使い方
こんな感じ
select や、where 、 sort -Property "Name"とパイプでつないで表示を絞っています。
そのあたりの詳細
PS C:\Users\kiban2> $triggers = Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Services" |
>> Where-Object { $_.GetSubkeyNames().Contains("TriggerInfo") } |
>> ForEach-Object { $_.Name.Split("\")[-1] }
>>
>> $startMode = @{ Manual = "手動"; Disabled = "無効"; Auto = "自動"; Unknown = "不明" }
>> $startOption = @{ 01 = " (トリガー開始)"; 10 = " (遅延開始)"; 11 = " (遅延開始、トリガー開始)" }
>>
>> $serviceData = Get-CimInstance -ClassName Win32_Service | Select-Object @(
>> @{ n = "表示名"; e = { $_.DisplayName } }
>> @{ n = "サービス名"; e = { $_.Name } }
>> @{ n = "スタートアップの種類"; e = { $startMode[$_.StartMode] + $startOption[10 * ($_.StartMode -eq "Auto" -and $_
.DelayedAutoStart) + $triggers.Contains($_.Name)] } }
>> @{ n = "状態"; e = { if($_.State -eq "Running") { "実行" } else { "停止" } } }
>> )
>>
>> $serviceData | select サービス名,スタートアップの種類,状態 |
>> where { $_.スタートアップの種類 -match "遅延開始"} | sort -Property スタートアップの種類,サービス名
サービス名 スタートアップの種類 状態
---------- -------------------- ----
gupdate 自動 (遅延開始) 停止
ImControllerService 自動 (遅延開始) 実行
jhi_service 自動 (遅延開始) 実行
LMS 自動 (遅延開始) 実行
MapsBroker 自動 (遅延開始) 停止
OneSyncSvc_98a5adb 自動 (遅延開始) 実行
sedsvc 自動 (遅延開始) 実行
SgrmBroker 自動 (遅延開始) 実行
UsoSvc 自動 (遅延開始) 実行
wscsvc 自動 (遅延開始) 実行
WSearch 自動 (遅延開始) 実行
CDPSvc 自動 (遅延開始、トリガー開始) 実行
sppsvc 自動 (遅延開始、トリガー開始) 停止