[ADSI] チュパカブラ!! ログオン名から User オブジェクトを引っこ抜く!! の巻
皆さんこんにちは。ういこです。
何だか毎朝すごくすごく寒いので脂肪を溜め込んでしまった生物的には正しすぎる自分の体がいけすかない + 会社の自販機から黒烏龍茶がなくなったことにショックを受けている今日この頃ですが、皆様いかがお過ごしでしょうか。
私は日々成長していて今日(2008/12/03 現在)半そでで朝颯爽と食卓にやってきた息子 6 歳に金髪グラサンの 4 のつくあの人みたいな切なさが炸裂しています。
ちなみに先日息子が人生初めての漢字を書いたのですが、「つき破壊りょく」と書いてありました。
こいつ大丈夫かなとお母さんとっても心配です。ちなみに意味聞いたら、「お母さん破壊ってこわすことだよ、しらないの? (← 何かえらそう)」とか言われてちょっとイラっとしました。
あと、最近ひろとくんが隣で「チュパカブラ!チュパカブラ! 」とうるさいです(※)。男の子はいくつになってもこんなもんなんですか?近況はこんな感じです。
(※「できるだけ邪悪な感じになるようにういこさんも言ってみてくださいよ~」だそうです)
さてそれはともかく、最近 ILM づいているので、たまには趣向を変えて ADSI の記事をひとつご紹介させていただきます。
【今日の御題】
ログオン名 (SAMAccount) から User オブジェクトを引っこ抜く!
ベースはサポート技術情報 (KB) 252490 のコードだよ。(Visual C++ 2005 対応済み、ビルド用設定つき)
ActiveDirectory の User オブジェクトを CN 属性を基にした ADsPath 情報を参照した場合、ログオン ユーザ名 (SAMAccountName 属性値)と CN 属性値が一致している状況下では有効な User オブジェクトの参照、表示名の取得が可能ですが、CN 属性が変更され、ログオンユーザ名と異なってしまった場合はこの方法では対処できません。(Active Directory 上では、CN属性値と SAMAccountName属性値は独立して保持されていますので、直接の Active Direcoty オブジェクトの参照には CN 属性値が判明している必要があります。)
こうした場合はログオン ユーザ名を SamAccountName 属性値とした ActiveDirectory オブジェクトの検索処理により、User オブジェクトを抽出する実装を行う必要があります。
以下、コードサンプルです。元のソースは、Visual C++ 6.0 (※ すでにサポートは延長サポートも終了しております…。) 対応ですので、Visual C++ 2005 対応版にしております。
参考 :
[HOWTO] ADSI を使用してグローバル カタログで UPN を照会する
https://support.microsoft.com/kb/252490/ja
【コード サンプル】
#include "stdafx.h"
#include <wchar.h>
#include <activeds.h>
HRESULT FindUPN(LPOLESTR bstrUPN);
extern const IID IID_IDirectorySearch=__uuidof(IDirectorySearch);
extern const IID IID_IADsContainer = __uuidof(IADsContainer);
int wmain(int argc, wchar_t *argv[])
{
LPOLESTR lpstrUPN=NULL;
//Initialize COM
CoInitialize(NULL);
HRESULT hr = S_OK;
if(argc != 2)
{
wprintf(L"This program will query the LDAP for the existence of sAMAccountName\n");
wprintf(L"Please enter the sAMAccountName to search for\n");
}
else
{
lpstrUPN = argv[1];
hr = FindUPN(lpstrUPN);
if (FAILED(hr))
wprintf(L"Search failed with hr: %d\n", hr);
}
// Uninitialize COM
CoUninitialize();
return 0;
}
HRESULT FindUPN(LPOLESTR pszUPN)
{
HRESULT hr = E_FAIL;
HRESULT hrGC = S_OK;
VARIANT var;
ULONG lFetch;
// Interface Pointers
IDirectorySearch *pGCSearch = NULL;
IADsContainer *pContainer = NULL;
IUnknown *pUnk = NULL;
IEnumVARIANT *pEnum = NULL;
IDispatch *pDisp = NULL;
IADs *pADs = NULL;
//Bind to Global Catalog
hr = ADsOpenObject(L"GC:", //NT 4.0, Win9.x client must include the servername, e.g GC://myServer
//Bind to LDAP
//hr = ADsOpenObject(L"LDAP:",
NULL,
NULL,
ADS_SECURE_AUTHENTICATION, //Use Secure Authentication
IID_IADsContainer,
(void**)&pContainer);
if (SUCCEEDED(hr))
{
hr = pContainer->get__NewEnum( &pUnk );
if (SUCCEEDED(hr))
{
hr = pUnk->QueryInterface( IID_IEnumVARIANT, (void**) &pEnum );
if (SUCCEEDED(hr))
{
// Now Enumerate--there should be only one item.
hr = pEnum->Next( 1, &var, &lFetch );
if (SUCCEEDED(hr))
{
while( hr == S_OK )
{
if ( lFetch == 1 )
{
pDisp = V_DISPATCH(&var);
hr = pDisp->QueryInterface( IID_IDirectorySearch, (void**)&pGCSearch);
hrGC = hr;
}
VariantClear(&var);
hr = pEnum->Next( 1, &var, &lFetch );
};
}
}
if (pEnum)
pEnum->Release();
}
if (pUnk)
pUnk->Release();
}
if (pContainer)
pContainer->Release();
if (FAILED(hrGC))
{
if (pGCSearch)
pGCSearch->Release();
return hrGC;
}
//Create search filter
WCHAR rgSearchFilter[1024];
wsprintf(rgSearchFilter,L"(&(objectClass=user)(sAMAccountName=%s))", pszUPN);
//Search entire subtree from root.
ADS_SEARCHPREF_INFO SearchPrefs;
SearchPrefs.dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
SearchPrefs.vValue.dwType = ADSTYPE_INTEGER;
SearchPrefs.vValue.Integer = ADS_SCOPE_SUBTREE;
DWORD dwNumPrefs = 1;
// COL for iterations
ADS_SEARCH_COLUMN col;
// Handle used for searching
ADS_SEARCH_HANDLE hSearch;
// Set the search preference
hr = pGCSearch->SetSearchPreference( &SearchPrefs, dwNumPrefs);
if (FAILED(hr))
return hr;
// Set attributes(CN,sAMAccountName,displayName) to return
CONST DWORD dwAttrNameSize = 3;
LPOLESTR pszAttribute[dwAttrNameSize] = {L"cn",L"sAMAccountName",L"displayName"};
// Execute the search
hr = pGCSearch->ExecuteSearch(rgSearchFilter,
pszAttribute,
dwAttrNameSize,
&hSearch
);
if ( SUCCEEDED(hr) )
{
if(pGCSearch->GetFirstRow(hSearch) == S_ADS_NOMORE_ROWS )
{
wprintf(L"There is no value\n");
}
else //The value was found
{
do
{
// loop through the array of passed column names,
// print the data for each column
for (DWORD x = 0; x < dwAttrNameSize; x++)
{
// Get the data for this column
hr = pGCSearch->GetColumn( hSearch, pszAttribute[x], &col );
if ( SUCCEEDED(hr) )
{
// Print the data for the column and free the column
// Note the attributes we asked for are type CaseIgnoreString.
wprintf(L"%s: %s\r\n",pszAttribute[x],col.pADsValues->CaseIgnoreString);
pGCSearch->FreeColumn( &col );
}
else
wprintf(L"<%s property is not a string>",pszAttribute[x]);
}
wprintf(L"------------------------------\n");
}
while( pGCSearch->GetNextRow( hSearch) != S_ADS_NOMORE_ROWS );
}
// Close the search handle to clean up
pGCSearch->CloseSearchHandle(hSearch);
}
if (pGCSearch)
pGCSearch->Release();
return hr;
}
【ビルド用設定】
Visual C++ でビルドすると、ヘッダーやライブラリのパスなどをビルド環境でどう設定してるかによって、ビルド通るはずが通らない! ってよくありますよね。
というわけで、私の環境を作り直したときに、Visual C++ 2005 初回起動でもビルドが通る設定を以下にご紹介させていただきます。
でも、ヘッダーのパスなどは、各環境にインストールされたディレクトリに書き換えてくださいね。
Visual Studio 2005 を起動し、下記の動作を行います。実際に確認いたしました、Visual Studio (以下 VS) の設定は下記のとおりとなります。
VC++ ディレクトリ
[ ツール(T)] の [オプション(O)] のダイアログの [ディレクトリを表示するプロジェクト(S)] のドロップダウン リストそれぞれについては以下の通りの設定の環境です。
インクルード ファイル (環境変数 INCLUDE と一致します)
C:\Program Files\Microsoft SDKs\Windows\v6.0\Include
$(VCInstallDir)include
$(VCInstallDir)atlmfc\include
$(VCInstallDir)PlatformSDK\include
$(FrameworkSDKDir)include
ライブラリ ファイル (環境変数 LIB と一致します)
C:\Program Files\Microsoft SDKs\Windows\v6.0\Lib
C:\Program Files\Microsoft SDKs\Windows\v6.0\Lib\x86
$(VCInstallDir)lib
$(VCInstallDir)atlmfc\lib
$(VCInstallDir)atlmfc\lib\i386
$(VCInstallDir)PlatformSDK\lib
$(FrameworkSDKDir)lib
$(VSInstallDir)
$(VSInstallDir)lib
1. [ ファイル(F)] - [ 新規作成(N)] - [ プロジェクト(P)...] を選択します。(※1)
2. "新しいプロジェクト" ダイアログ(小さなウインドウ)が表示されます。
3. このダイアログで以下のように選択します。
左ペイン [プロジェクトの種類(P):] …
"Visual C++"
- "Win32"
右ペイン [テンプレート(T):] …
"Visual Studio にインストールされたテンプレート"
- "Win32 コンソール アプリケーション"
ダイアログ下方
一番上 [プロジェクト名(N)] … 任意の名称(例 : MyProject)
二番目 [場所(L)] (※2) … 保存したい場所 (例 : C:\temp)
三番目 [ソリューション名(S)] …
"新しいソリューションを作成する"
四番目 [ソリューション名(M)] … [プロジェクト名(N)] と連動
※ [ソリューションのディレクトリを作成(D)] のチェックボックスはついたままにしておいてください。
4. [OK] を押します。
5. "Win32 アプリケーション ウィザード" ダイアログが表示されます。
6. そのまま [完了] を押していただくとコンソール アプリケーションを実行していただくのに必要な設定や、ファイルなどがあらかじめ用意されたファイルのセット
(ソリューション) が作成されます。
7. コードを貼り付けます。その際、コメント行が改行されていないか全角半角などが含まれていないか確認ください。
8. [ プロジェクト(P)] - [ プロパティ(P)] を選択します。
9. "<プロジェクト名> プロパティ ページ" ダイアログが表示されますので、左側を以下のように展開してください。
構成プロパティ
- リンカ
- 入力
10. 右ペインの [追加の依存ファイル] の項に
Activeds.lib
を追加してください。(※3)
11. [OK] を押します。
13. 実行の前に、コンパイルエラーやリンク エラーが発生しないか確かめるため、 [ビルド(B)] メニューから [ソリューションのビルド(B)] もしくは [ソリューションのリビルド(R)] を選択します。(※4)
(※1) Ctrl + Shift + N を同時に押しても同じ動作となります。メニューの右にあるのは、同じ動作をするためのショートカットキーです。 [プロジェクト(P)...] と同じことをキーボード操作だけで行いたいときは、 [Ctrl] キーと [Shift] キーと、[N] キーを同時に押下すればよいという意味となります。これを応用しますと、例えば [ソリューションのリビルド(R)] は [Ctrl] キーと [Shift] キーと、[F7] キーを同時に押下すれば実行されるということがわかります。
(※2) 保存場所を変えたいときは、横の [参照(B)...] のボタンを押すと、Visual Studio の開発に必要なファイルの保存場所を変えることができます。
(※3) これを設定しない場合、以下のようなエラーが発生します。
error LNK2019: 未解決の外部シンボル _ADsOpenObject@24 が
関数 "long __cdecl FindUPN(wchar_t *)" (?FindUPN@@YAJPA_W@Z) で
参照されました。
fatal error LNK1120: 外部参照 1 が未解決です。
…もうがっくりですね、こんな風に言われたら…。こうした場合、今回の場合 "ADsOpenObject"、つまり解決できない関数を MSDN ライブラリで検索します。検索すると、以下のようにページが見つかります。
ADsOpenObject Function
https://msdn2.microsoft.com/en-us/library/aa772238(VS.85).aspx
ページの下方を見ますと、以下のような記述があります。
Header Declared in Adshlp.h.
Library Use Activeds.lib
コンパイル エラーが表示された場合は、"Header" にあるヘッダーファイルを宣言したり、VC++ ディレクトリの設定の[インクルード ディレクトリ] もしくは環境変数 INCLUDE にあるパスに該当のヘッダーファイルが存在するか確認してください。
存在しない場合はドライブをヘッダーファイル名で検索し、そのファイルがあるパスを追加してください。
これはライブラリ ファイルも同じです。今回の場合、"LNK" で始まるエラー、すなわちリンクエラーですので、ライブラリの設定を見直します。
そこで、Activeds.lib を設定に追加する必要があると判断できるということになります。
(※4) リビルドとビルドの違いは、再コンパイルが必要なファイルに対しビルドを行うか行わないかです。ビルドの場合、変更されていないファイルはビルドされませんので高速にビルドが終了します。一方、リビルドはファイル変更の有無にかかわらずすべてのファイルに対しビルドを行いますので、速度は遅くなります。
***
以上となります。
あまりイカした新ねたじゃなくてすみません。
次回はひろとくんがスクリプトガイの座を脅かすすごいコードに挑戦したことを書いてくれるはずです。
とっても楽しみですね。
ではまた!
~ ういこう ~