なんの記事?
上記のようなWindowsサービスの実行ファイルパスを取得する方法を紹介します。
サービスアプリケーション自身からではなく、外部のアプリケーションから取得します。
サービス名 / 表示名は簡単に取得可能
コンピューターにインストールされたサービスの一覧の列挙は.NETの名前空間「System.ServiceProcess」のクラス「ServiceController」で提供される機能で簡単に行うことが可能となっています。
◯DOBON.NET様の記事
ローカルコンピュータのすべてのサービス(またはデバイスドライバサービスのみ)を取得する – .NET Tips (VB.NET,C#…)
しかし、この方法では、サービスに関する情報はサービス名と表示名しか取得ができないようです。
実行ファイルパスを取得するには?
方法①:Win32APIを使用する
1つ目は、Win32APIを使用する方法です。
◯サンプルコード
private const string ADVAPI32 = "advapi32.dll"; [DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr OpenSCManager(string lpMachineName, string lpDatabaseName, int dwDesiredAccess); [DllImport(ADVAPI32, ExactSpelling = true, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseServiceHandle(IntPtr handle); [DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr OpenService(IntPtr hSCManager, string lpServiceName, int dwDesiredAccess); [DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] private static extern Boolean QueryServiceConfig(IntPtr hService, IntPtr intPtrQueryConfig, UInt32 cbBufSize, out UInt32 pcbBytesNeeded); private const int SERVICE_QUERY_CONFIG = 0x0001; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct QUERY_SERVICE_CONFIG { internal int dwServiceType; internal int dwStartType; internal int dwErrorControl; internal string lpBinaryPathName; internal string lpLoadOrderGroup; internal int dwTagId; internal string lpDependencies; internal string lpServiceStartName; internal string lpDisplayName; } public string GetExePath_Win32(string name) { string exePath = ""; IntPtr scManager = OpenSCManager(null, null, SERVICE_QUERY_CONFIG); if (scManager == IntPtr.Zero) { return exePath; } IntPtr service = OpenService(scManager, name, SERVICE_QUERY_CONFIG); if (service == IntPtr.Zero) { return exePath; } UInt32 cbBufSize = 0; QueryServiceConfig(service, IntPtr.Zero, 0, out cbBufSize); IntPtr intPtrQueryConfig = Marshal.AllocHGlobal((int)cbBufSize); QueryServiceConfig(service, intPtrQueryConfig, cbBufSize, out cbBufSize); QUERY_SERVICE_CONFIG qsc = (QUERY_SERVICE_CONFIG)Marshal.PtrToStructure(intPtrQueryConfig, typeof(QUERY_SERVICE_CONFIG)); exePath = qsc.lpBinaryPathName; CloseServiceHandle(service); CloseServiceHandle(scManager); return exePath; }
中々長いですね…手軽に使える感じはありません。
実行ファイルパスなどのサービスの構成情報を取得するには、
- コンピューターのサービスコントロールマネージャーに接続(OpenSCManager)
- サービスを開く(OpenService)
- サービスの情報を取得(QueryServiceConfig)
の3ステップが必要になります。
すべでWin32APIの関数なので、それぞれDLLImportでの宣言と関連する型定義が必要になります。
動作要件
今回使用するAPIの動作要件は以下
OS | クライアント:Windows XP ~ サーバー:Windows Server 2003 ~ |
.NET | Core1.0~ |
.NET Framework | 1.1~ |
Win32APIは歴史の古いものですが、途中で追加になったAPIもありAPIごとに要件が異なります。
念のため確認しましょう。(令和の時代にほぼほぼ問題ならないかな?)
方法②:WMIを使用する ※オススメ
2つ目は、WMIを使用する方法です。
WMIは様々なプログラミング言語で使用可能で、.NETでも機能が提供されています。
Microsoft.Management.Infrastructure 名前空間
◯サンプルコード
public string GetExePath_WMI_New(string name) { string exePath = ""; using (CimInstance services = new CimInstance("Win32_Service", @"root\cimv2")) { services.CimInstanceProperties.Add(CimProperty.Create("Name", name, CimFlags.Key)); using (CimSession mySession = CimSession.Create("localhost")) using (CimInstance searchInstance = mySession.GetInstance(@"root\cimv2", services)) { exePath = searchInstance.CimInstanceProperties["PathName"].Value.ToString(); } } return exePath; }
上記記述の場合、GetInstanceで対象が存在しない場合例外がスローされるようです。
◯サンプルコード(クエリ版)
WMIクエリ(WQL)を使用することも可能です。
public string GetExePath_WMI_Query(string name) { string exePath = ""; using (CimSession mySession = CimSession.Create("localhost")) { IEnumerable<CimInstance> queryInstance = mySession.QueryInstances(@"root\cimv2", "WQL", $"SELECT * FROM Win32_Service WHERE Name = '{name}'"); CimInstance cimInstance = queryInstance.FirstOrDefault(); if(cimInstance != null) { exePath = cimInstance.CimInstanceProperties["PathName"].Value.ToString(); } } return exePath; }
System.Management 名前空間 ※現在非推奨
前述の「Microsoft.Management.Infrastructure」 の他、「System.Management」にも機能が提供されています。しかし、現在では非推奨であり「Microsoft.Management.Infrastructure」 を使用するように推奨されています。
◯サンプルコード
public string GetExePath_WMI(string name) { string exePath = ""; ConnectionOptions option = new ConnectionOptions(); ManagementScope scope = new ManagementScope("\\root\\cimv2", option); ObjectQuery query = new ObjectQuery($"SELECT * FROM Win32_Service WHERE Name = '{name}'"); using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query)) using (ManagementObjectCollection services = searcher.Get()) { ManagementObjectCollection.ManagementObjectEnumerator enumerator = services.GetEnumerator(); if (enumerator.MoveNext()) { ManagementBaseObject service = enumerator.Current; exePath = service["PathName"].ToString(); service.Dispose(); } } return exePath; }
動作要件
今回取得対象のWin32_Serviceクラスの要件は以下
OS | クライアント:Windows Vista 以降 サーバー:Windows Server 2008 以降 |
.NET | Core1.0~ |
.NET Framework | 4.5.1~ ※System.Managementは1.1~ |
アクセス対象のWMIクラスによって要件が異なります。
使用する際は要確認。
動作結果
簡単なフォームを作って、前述の処理で取得したファイルパスと処理にかかった時間を表示させてみました。
無事、①②とも同じ結果が得られました!
おわりに
今回はローカルコンピューターから情報を取得しましたが、①②もリモートコンピューターからも取得可能なようです。認証やファイアウォールなどのハードルはあるとは思いますが…
参考
- OpenServiceW 関数 (winsvc.h) – Win32 apps | Microsoft Learn
- Win32_Service クラス – Win32 apps | Microsoft Learn
- Windowsサービスのサービス名を列挙するには? – @IT
- Win32 APIやDLL関数を呼び出すには?:.NET TIPS – @IT
- Windows Management Instrumentation (Windows Management Instrumentation) – Win32 apps | Microsoft Learn
- NuGet Gallery | Microsoft.Management.Infrastructure 3.0.0