Windowsの謎 ～サービス一覧を出すコマンド～

# Windowsの謎

Windowsを使用していて、たまに？？？となることがある。それは長年のOS開発、更改により、つぎはぎ的に機能を追加(Apache?)、あるいはGUIを新たに被せていくことにより生じた、矛盾である。  
例えばサービス一覧は、GUIで表示される情報をそのまま出してくれるコマンドが見当たらない。

# GUIのサービス一覧

![image.png](https://qiita-image-store.s3.amazonaws.com/0/192988/28b6eea4-07c8-efa2-1ca3-f44a86454bcf.png)
GUIでサービス一覧を表示させると、「スタートアップの種類」には（遅延開始）（トリガー開始）といった情報が表示される。

ところが、同じことをPowerShellまたは CMDで実現しようとすると、上記の情報をGUIと同じように出力するコマンドはない。 
強いて言えば services.msc を起動して 出力内容をファイルにエクスポートする方法があげられるが、コマンドでそのまま加工するには不便である。

# 旧来のコマンド sc.exe

古くから伝わるコマンドにsc.exe または net start というものがある。
これはサービスの情報を表示するものの、トリガー情報は個別にqtriggerinfo というコマンドで取得する為、コードをループさせてネストさせないとうまく情報が取れない。。

# Powershell
Get-Service というコマンドレットがある。 
これも残念ながら英語表記で　トリガー情報は全く入ってこない。

# Win32 API
仕方なく根幹のAPIを当ってみる。  
なんとWin32のAPI [Win32_Service](https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-service)ですら　トリガー情報を持っていない。

# レジストリ！
HKLMの配下のサービス情報を見てみる。   
あった！TriggerInfo！
えっと！？これがあるとトリガー開始で、ないとトリガー開始ではない…

# 遅延開始情報
遅延開始の情報は[Win32_Service](https://docs.microsoft.com/en-us/windows/desktop/cimwin32prov/win32-service)が　DelayedAutoStartというキーで持っている。
これが１だと遅延開始？
いや、StartTypeがAutoでさらにDelayedAutoStartだと遅延開始となる。

# まとめ
以上の情報をもって組むと下記の通りとなる。
いちいちループ内でレジストリのパス情報を検査するので遅い。
できれば最初に全部持ってきてメモリ上に展開してマッチングしたいが…
そんな暇はない。

```powershell

$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を持っているエントリの名前を配列にしておくというのが秀逸ですね。

```powershell
$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"とパイプでつないで表示を絞っています。  
[そのあたりの詳細](https://qiita.com/Kirito1617/items/bd3937fb26c668eca078)

```powershell
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              自動 (遅延開始、トリガー開始) 停止
```