[WMI-NetAPI] Win32_NetworkAdapterConfigration の結果が重複する 2/(2)

みなさんごきげんよう。ういこです。

久しぶりに天空の城ラピュタを見ています。私ムスカ様が大好きで、某 SNS のファンコミュニティに勢いで入ってそれっきりなくらいラブです。ちなみにぴろとくんは飲み屋でビニール袋でないと触れない激辛チキン食べた際にリアル「目が、目が~」をやったそうです。グッジョブ!

さて、今回は以下のミッションのシリーズ第二回目です。

第一回目はこちら

[WMI-NetAPI] Win32_NetworkAdapterConfigration の結果が重複する 1/(2) https://blogs.technet.com/jpilmblg/archive/2009/11/20/wmi-netapi-win32-networkadapterconfigration-1.aspx

【今回のミッション】
環境上にあるネットワーク アダプタがある条件下ではレジストリにゴミ情報が残り、その結果 WMI の Win32_NetworkAdapterConfigration クラスの取得結果が重複してしまうことがある。
以前問い合わせをした際、その動作自体は OS の規定の動作ということで GetAdaptersInfo()、GetAdaptersAddresses() などを使ってアクティブなデバイスを取得し、そのデバイスから Win32_NetworkAdapterConfigration の情報をフィルタしたいと思っているが、フラグに使おうと思う変数が思うような値を返さず、フィルタ条件として有効にならない状況である。
Win32_NetworkAdapterConfigration のうち、適切なアクティブな状態のアダプタの情報だけフィルタして取得する方法とは何か!

*****************

前回は同じネットワーク アダプタなのに、重複して情報がレジストリに登録されてしまうという話をお伝えしました。
今回は、対処方法についてご案内します。

1) Win32_NetworkAdapter の Availability プロパティをみたらどうかな?
…残念ながらこの方法ではだめです。

最初私が試したのは、Win32_NetworkAdapterSetting の取得の前にWin32_NetworkAdapter をクエリし、各ネットワーク インターフェースの状態を見るということでした。
Availability プロパティは、デバイスの状態を指します。このとき、3 (0x3) が返されたデバイスは、"Running or Full Power" の状態ですので、それを使えばいいかなと思ったのです。
ちなみにこのとき、物理デバイス以外も 3 を返しますので、さらに PhysicalAdapter プロパティが True、すなわち物理デバイスであるかも条件に含めました。
重複してても、現時点で有効ではないデバイスは状態は 3 ではないと考えたのです。
しかし、結果はだめでした。やはり、重複分も検知されてかえってきてしまうのです。DDK のなおきお~さんに伺ったところ、そもそも OS からすれば、OS 停止中に抜かれたデバイスは復帰する可能性もあるので、Running or Full Power として認識したままになるのはおかしいことではないのではないかということでした。
デバイスのことはあまりよくわかりませんが、そういうものなのでしょうか。

ただ、現実的にうまくいかないことから、次の手を考えなくてはいけなくなってしまいました。

2) GetAdapterAddresses() あるいは GetAdapterInfo() の Index を使ってみたらどうかな
※ 注意 : 以下は今後の OS の設計あるいはサービス パックによって動作が変更される可能性があります ※

できなくはないのですが、手を入れる必要がかなりあります。
これらの API を使うと、GetAdapterInfo() API なら IP_ADAPTER_INFO 構造体の Index、GetAdapterAddresses() API なら IP_ADAPTER_ADDRESSES 構造体の ifIndex にネットワーク アダプタ インターフェースのインデックスとして振られる番号をとることができるのですが、このインデックスの振られ方にくせがあるため、ちょいと工夫が必要になります。

通常の流れはこうなると思います。

1. Win32_NetworkAdapterConfiguration にて、Description と、SettingID を取得
2. SettingID を使用し、レジストリから NTEContextList を取得
3. GetAdaptersAddresses() あるいは GetAdaptersInfo() で Index を取得
4. 上記 2. の NTEContextList と 3. で取得した値がマッチするかチェック

引っかかるのは、3. です。たとえば、Windows XP では、65539、Windows 2000 では、16777219 といった値が返ってきてしまうことがあります。

じつは、この "65539"、"16777219"、10 進数では意味のわからない値ですが、それぞれ 16 進数で見ると 65539 = 0x100003、16777219 = 0x1000003 となります。インデックス番号の順番は必ずしも 1, 2, ... n といったような順序ではなく、0x1000003 (Windows 2000)、0x100003 (Windows XP 以降) といった振られ方をされることがあるのです。

<< なぜインデックスが変な値になってるの? >>
Windows 2000 Service Pack 4 以降から OS の設計上の動作が変更され、ネットワーク アダプタ インターフェースのインデックスとして振られる番号は以下のような構成になりました。

PnP インスタンス + ネットワーク アダプタ インターフェースのインデックス

要は、これらの ifIndex やら Index で取れる値そのままを使っても、WMI の index として取得される下位のインターフェース インデックスとそのまま比較できないのです。
また、"PnP インスタンス + インターフェースのインデックス" のフォーマットは、OS ごとに異なります。

・Windows 2000 … 01 000003 = 0x1000003 = 16777219
(01 が PnP インスタンス、それ以降が NIC インデックスです)

・Windows XP / 2003 … 00 01 0003 = 0x10003 = 65539
(00 が常に 0 でデバイスを表します。01 が PnP インスタンス、0003 が今回でいうところの NIC インデックスになります)

比較の前に、下位ネットワーク アダプタ インターフェースのインデックスのみ抽出してあげる必要があります。
以下は C++ の例です。

例) Windows 2000 の場合

 

PIP_ADAPTER_INFO pAdapterInfo, pAdapt;

//…(中略) …

if ((Err = GetAdaptersInfo(pAdapterInfo, &AdapterInfoSize)) != 0)

//…(中略) …

// シフトしてIndex 取得、以降 WMI の index と組み合わせる

test = (pAdapterInfo->Index << 16) >> 16;

例) Windows XP 以降の場合

 

PIP_ADAPTER_ADDRESSES pAddress;

//…(中略) …

if ((Err = GetAdaptersAddresses(AF_INET, 0, NULL, pAddress,&AdapterInfoSize)) != 0)

//…(中略) …

DWORD test;

//…(中略) …

// シフトしてIndex 取得、以降 WMI の index と組み合わせる

test = (pAddress->IfIndex << 8) >> 8;

シフトではなくて、マスクでもいいかもです。

3) SetupDi にてデバイスの状態確認を行う
上記の方法はシフト計算なので、C# などでもできなくはないですが (API 呼び出すのはちと骨が折れますが) 、なにぶん OS の将来的な設計変更なんかがあるとリンダ困っちゃいます。
重複情報があっても、デバイス マネージャでは常にデバイスはひとつしか出ません。
そこでこれを応用し、さらに敷居は高いのですが、デバイス マネージャの仕組みと同じく、デバイスの状態を見るという手もあります。

厳密に現在 Active なデバイスの状態を判断するために、Windows Driver Kit (WDK) のSetupDi を使い、WMI の Win32_NetworkAdapter で取得できる PNPDeviceID を与えることによって、判別するデバイスを指定しデバイスを列挙することが出来ればその NIC は有効、出来なければ除外するという処理を行うという手段です。

<< コード例 C++ >>
以下コード例です。IsPresentedNetClassDevice という関数を実装しています。使い方は簡単。Win32_NetworkAdapter クラスの PNPDeviceID を取得し、IsPresentedNetClassDevice() の引数に与えてやってください。現在有効なデバイスであれば True、無効(重複したいらない情報)であれば False を返します。

※ 注意 : SetupDi 系関数をご利用いただくには、Windows Driver Kit を開発環境にインストールしていただく必要があります。

WDK の入手方法 : https://www.microsoft.com/japan/whdc/DevTools/WDK/WDKpkg.mspx

// ←-- コード抜粋ここから

#pragma comment(lib, "setupapi.lib")

#include <setupapi.h>

#include <cfgmgr32.h>

#include <devguid.h>

 

BOOL IsPresentedNetClassDevice(LPCTSTR szTrgetDeviceId)

{

    BOOL bFound;

    DWORD index, result;

    HDEVINFO hDevInfo;

    SP_DEVINFO_DATA DevInfoData;

    TCHAR szDeviceID[MAX_PATH];

 

    bFound = FALSE;

    hDevInfo = NULL;

    if(szTrgetDeviceId == NULL)

    goto Done;

 

    hDevInfo = SetupDiGetClassDevsEx(&GUID_DEVCLASS_NET,

                    NULL,

                    NULL,

                    DIGCF_PRESENT,

                    NULL,

                    NULL,

                    NULL);

    if(hDevInfo == NULL)

    goto Done;

 

    index = 0;

    ZeroMemory(&DevInfoData, sizeof(DevInfoData));

    DevInfoData.cbSize = sizeof(DevInfoData);

    while(0 != SetupDiEnumDeviceInfo(hDevInfo, index++, &DevInfoData))

    {

        ZeroMemory(szDeviceID, sizeof(&szDeviceID));

        result = CM_Get_Device_ID(DevInfoData.DevInst, szDeviceID, sizeof(szDeviceID), 0);

        dbgprint(TEXT("%s\n"), szDeviceID);

        if((result != CR_SUCCESS) || (CSTR_EQUAL != CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, szTrgetDeviceId, -1, szDeviceID, -1)))

        continue;

        bFound = TRUE;

        break;

    }

 

    Done:

    if(hDevInfo)

    SetupDiDestroyDeviceInfoList(hDevInfo);

 

    return bFound;

// ←-- コード抜粋ここまで

ただ、これを VBScript やらでやるというと…↑の関数を DLL とかにして、参照するしかないのかなぁと悩んでます。
WMI や VBScript は、色々なテクノロジーで使えるようになっているので、各テクノロジーをそれぞれ勉強しないとわからないこともあり、「WMI」担当というくくりはちょっときっついなぁと思うこともありますが、日々勉強だなあと痛感しております。おいおい、VBScript でのやり方も考えていこうと思っていますが、ご参考まで。

協力 : WDK サポートチームなおきお~さん (Blog : Japan WDK Support Team Blog)

ういこう@アパラチャノモゲータ!(フハッ)