Hey, Scripting Guy!あなたはだれ

Microsoft Scripting Guys

この記事で使用しているコードのダウンロード: HeyScriptingGuy2007-082007_08.exe (152KB)

それほど前のことではありませんが、このコラムを執筆している Scripting Guy が、TV で野球を観戦していました。試合が終わり、雑誌を読むことにしましたが、TV はつけたままでした。雑誌を読み終えたとき、TV に目をやると、昔の第 2 次世界大戦の映画が放映されていました。実際目にしたのは、役者たちが演じるお決まりの 1 シーンでした。若いアメリカ兵が夜の見張りに立っていたときに、物音がするというシーンです。

「だれだ。」その見張りが怒鳴ります。

「私です。スミス軍曹です。」声が返ります。

「スミス軍曹? この部隊にはスミス軍曹なんていないぞ。」

「新しく配属されました。A 中隊からの転属です。」

「そうか。A 中隊から。よかろう、スミス軍曹。ところで、1934 年のワールド シリーズに勝ったのはどこだ。」

「ニューヨーク ヤンキースです。」

残念ながら不正解です。「おい、スミス。」 (お察しのとおり、その人物は即座に捕らえられ、檻の中に放り込まれました)。良識ある真のアメリカ人ならば、1934 年のワールド シリーズにはセントルイス カーディナルスが勝利したことを知っています。あのディジィ ディーンとガス ハウス ギャングが活躍した年です。こんなことも知らないなんて、スパイとしか考えられません。

今月のコラムの読者の中でセントルイス カーディナルスが 1934 年のワールド シリーズに勝利したことを知らない人がいるなら、言えることは 1 つです。「もう逃げられませんよ。スパイであることがわかってしまいました。最寄りの FBI オフィスに出頭してください。少なくとも FBI に電話をかけてください。スパイだとわかれば、FBI が捕まえに来るでしょう。」

この映画を見ていた Scripting Guy は、見張りの 2 つ目の質問には他にもよいのがあったことに気づきました。「1966 年のワールド シリーズで勝ったのは」というものです。答えはボルティモア オリオールズです。「1960 年のワールド シリーズは」ピッツバーグ パイレーツです。「1994 年のワールド シリーズは」はは。これは、ひっかけですね。1994 年にはワールド シリーズが行われませんでした。

しかし現在では、1 つ目の質問の「お前はだれだ」に答えることは非常に難しくなっています。1940 年代ほど簡単に答えられる質問ではありません。結局のところ、Active Directory® だけを考えてみても、ユーザーには多くの異なる ID が割り当てられる可能性があります。たとえば、次のような ID が考えられます。

  • ファースト ネーム (givenName)
  • ラスト ネーム (sn)
  • 表示名 (displayName)
  • ユーザー プリンシパル名 (userPrincipalName)
  • ログオン名 (samAccountName)
  • 識別名 (distinguishedName)

どの名前を使っても同じ人物が特定されます。状況にもよりますが、すべての名前を把握しておくことは重要です。ここで問題が生じます。大半のユーザーは、自分のファースト ネームとラスト ネームは知っています。しかし、あるユーザーに「あなたの識別名は」とたずねた場合に、「そんなのは簡単です。私は CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com です。しかし、友人は私のことを CN=Ken.Myer と呼びます。」と答えられる人はほとんどいないでしょう。

システム管理者やヘルプ デスクのスタッフならば、これらの名前も把握しておく必要があります。では、どうすればこうした情報を入手できるでしょう。そう、1 つの方法は、そのユーザーが自分の識別名を白状するまでフックにつるしておくことです。おそらくうまくいくとは思いますが、最近では、ほとんどの人事部からこの種の方法はとらないように指示されます。ですから、計画 B に移行しなくてはならないかもしれません。そう、スクリプトを使う計画です。では、どのような種類のスクリプトでしょう。初心者向けに、図 1 のスクリプトはどうでしょう。

Figure 1 ADSystemInfo によるユーザー属性の取得

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

Set objUser = GetObject("LDAP://" & strUser)
WScript.Echo "First Name: " & objUser.givenName
WScript.Echo "Last Name: " & objUser.sn
WScript.Echo "Display Name: " & objUser.displayName
WScript.Echo "User Principal Name: " & objUser.userPrincipalName
WScript.Echo "SAM Account Name: " & objUser.sAMAccountName
WScript.Echo "Distinguished Name: " & objUser.distinguishedName

スミス軍曹が VBScript を勉強していればよいのですが。このスクリプトでは、ADSystemInfo というあまり知られていない (ただし非常に役立つ) ADSI オブジェクトを使用しています。これは、現在ローカル コンピュータにログオンしているユーザーに関するあらゆる種類の情報のほか、ローカル コンピュータ自体に関する情報やローカル コンピュータが所属しているドメインに関する情報を返すことができる、気の利いた小さなオブジェクトです。たとえば、図 2 を見てください。

Figure 2 あらゆる種類のドメイン情報の表示

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

Wscript.Echo "User name: " & objSysInfo.UserName
Wscript.Echo "Computer name: " & objSysInfo.ComputerName
Wscript.Echo "Site name: " & objSysInfo.SiteName
Wscript.Echo "Domain short name: " & objSysInfo.DomainShortName
Wscript.Echo "Domain DNS name: " & objSysInfo.DomainDNSName
Wscript.Echo "Forest DNS name: " & objSysInfo.ForestDNSName
Wscript.Echo "PDC role owner: " & objSysInfo.PDCRoleOwner
Wscript.Echo "Schema role owner: " & objSysInfo.SchemaRoleOwner
Wscript.Echo "Domain is in native mode: " & objSysInfo.IsNativeMode

ただし、今回は UserName プロパティのみに注目します。UserName のどこがそれほど特別なのでしょう。それは、たまたま distinguishedName プロパティと対応している点にあります。では、distinguishedName のどこがそれほど特別なのでしょう。それは、distinguishedName が UNC ファイル パスに似ている点にあります。つまり、UNC パスを使用してネットワーク上のどこかにあるファイルを一意に識別できるのとまったく同様に、distinguishedName (たとえば、CN=Ken.Myer, OU=Finance, DC=fabrikam, DC=com) を使用すると、Active Directory 内のユーザー アカウントを一意に識別できます。その後、そのユーザー アカウントにバインドすることができます。さらに、いったんそのバインドが行われると、ユーザーに関するすべての情報を入手できるようになります。つまり、そのユーザーがだれなのかという情報も含まれます。

これこそが、先ほどお見せした 1 つ目のスクリプトで行っていることです。まず、次に示すように、ADSystemInfo オブジェクトのインスタンスを作成して、UserName プロパティ値を strUser という変数に代入します。

Set objSysInfo = CreateObject("ADSystemInfo")
strUser = objSysInfo.UserName

ここで必要な処理はそれほど多くありません。たとえば、ユーザー名、ユーザーのドメイン、ユーザー アカウントが所属する OU などは指定する必要はありません。すべて ADSystemInfo によって行われます。ユーザーの distinguishedName を入手したら、次のコード行を使用してそのユーザーのユーザー アカウントにバインドできます。

Set objUser = GetObject("LDAP://" & strUser)

ここでも同様に、バインドを行ったら、そのアカウントのすべての Active Directory 属性値をエコー バックできます。このサンプル スクリプトでは、ユーザーの名前に関するプロパティをいくつか単純にエコー バックしているだけですが、電話番号、オフィスの場所、電子メール アドレスなども簡単に取得できます。

すばらしいですね。必要なのは、すべてのユーザーにこのスクリプトを渡すことです。そうすれば、ユーザーは「自分はだれなのか」と自問しなくて済みます (また、自問したとしても、簡単な方法ですぐに答えがわかります)。しかし、似たような質問でも「あなたはだれ」の場合はどうでしょう。ユーザーがローカル コンピュータにログオンしたことは識別できます。しかし、どのようにすれば、リモート コンピュータにログオンしたユーザーを特定できるでしょう。ご想像のとおり、これはかなり難しい問題です。

そのことも考えましたが、残念ながら、この ADSystemInfo スクリプトでは、単純にリモート コンピュータを指定することはできません。これは、ADSystemInfo オブジェクトはローカルにしか作成できないためです。ただし、次の 3 つの可能性が残されています。

  • ログオンしたユーザーの名前をアクセスできる場所に記録するログオン スクリプトを作成する。
  • WMI の Win32_ComputerSystem クラスと UserName プロパティを使用する。
  • その他。

1 つ目の方法はかなり期待できます。既にお気づきのように、ADSystemInfo を使用すれば、ユーザーの識別名だけでなく、コンピュータの識別名 (ComputerName プロパティ) も返せます。図 3 に示したサンプル スクリプトを考えてみましょう。

Figure 3 ログオン スクリプト

On Error Resume Next

Set objSysInfo = CreateObject("ADSystemInfo")

strUser = objSysInfo.UserName
strComputer = objSysInfo.ComputerName

Set objUser = GetObject("LDAP://" & _
    strUser)
strUserName = objUser.displayName

Set objComputer = GetObject("LDAP://" & _
    strComputer)
objComputer.Description = strUserName
objComputer.SetInfo

ここでは何をしているのでしょうか。まず、UserName プロパティと ComputerName プロパティの値を取得して、変数のペア (strUser と strComputer) に格納しています。次に、先ほどと同様に Active Directory のユーザー アカウントにバインドして、displayName 属性値を取得し、strUserName という変数に格納します。実に簡単ですね。

その後、次のコード行を使用して、Active Directory コンピュータ アカウントに接続できます。

Set objComputer = GetObject("LDAP://" & _
    strComputer)

接続したら、ユーザーの displayName をコンピュータの Description プロパティに割り当ててから、SetInfo メソッドを呼び出して Active Directory に変更を書き込みます。

objComputer.Description = strUserName
objComputer.SetInfo

なぜ、これを実行するのでしょうか。それは簡単です。たとえば、Ken Myer がコンピュータ atl-ws-01 にログオンするとします。atl-ws-01 の Description プロパティの値はどうなるでしょう。そのとおりです。現在このコンピュータにログオンしている Ken Myer になります。atl-ws-01 にログオンしたユーザーを知りたい場合は、Description プロパティを確認するだけです。

このシナリオはかなりうまくできていて、ユーザーがログオフするたびに Description プロパティを消去するログオフ スクリプトを使用すればさらにうまくいきます。ただし、万能の解決策ではありません。なぜでしょうか。ログオン スクリプトが必ず実行されるとは限らないためです。たとえば、ユーザーが RAS を使用してログオンすると、通常、ログオン スクリプトは実行されません。同様に、コンピュータをネットワークから切り離し、キャッシュされた資格情報を使用してコンピュータにログオンしてから、そのコンピュータをネットワークに接続した場合も、ログオン スクリプトは実行されません。また、ログオフしないでコンピュータの電源を落とした場合を考えてください。つまり、ログオフ スクリプトは実行されません。この場合、コンピュータ atl-ws-01 が動作していないにもかかわらず、Ken Myer が依然としてログオンしていることになります。便利なテクニックではありますが...。

では、Win32_ComputerSystem クラスを使用する 2 つ目の方法はどうでしょう。多くの場合、この方法もうまくいきますが、Win32_ComputerSystem クラスではログオンしたユーザーの名前が返されないことがある点が問題です。特にユーザーに管理者権限がない場合にこのことがあてはまります (また、Windows® 2000 を実行しているコンピュータの場合もそうです)。図 4 のスクリプトを使用すると、多くの場合に、コンピュータにログオンしたユーザーを識別できますが、保証はありません。

Figure 4 WMI による、ログオンしているユーザーの検出

strComputer = "."

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * From Win32_ComputerSystem")

For Each objItem in colItems
  Wscript.Echo objItem.UserName
Next

ちなみに、この場合、UserName プロパティは domain\username 形式で示されます。つまり、FABRIKAM\kenmyer になります。

そうでした。忘れるところでした。名前が返されたとしても、ユーザーが実際にコンピュータにログオンしていることを意味するわけではありません。Ken Myer が atl-ws-01 からログオフしても、彼の名前は UserName プロパティ値に保持され、別のだれかが新たにログオンするまで置き換えられません。

うへぇ。

でもちょっと待ってください。まだスパイと決まったわけではありません。可能なアプローチがもう 1 つあります。だれかがコンピュータにログオンすると、Explorer.exe プロセスがバインドされて実行されます。一般に、Explorer.exe プロセスが実行されていない場合は、コンピュータにはだれもログオンしていません。また、Explorer.exe プロセスはログオンしたユーザーの資格情報で実行されるので、図 5 のようなスクリプト使用すれば、コンピュータにログオンしたユーザーをほぼ確実に特定できます。

Figure 5 Explorer.exe プロセスの所有者の特定

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser, strDomain
    Wscript.Echo strDomain & "\" & strUser
  Next
End If

ご覧のとおり、この場合、リモート コンピュータ (正確には atl-ws-01) 上の WMI サービスに接続しています。続いて、次のコード行を使用して、Win32_Process クラスのインスタンスの中から Explorer.exe という名前のインスタンスすべてから成るコレクションを取得します。

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where " & _
  "Name = 'explorer.exe'")

これでどうでしょう。既に述べたように、Explorer.exe プロセスが実行されていなければ、そのコンピュータにはだれもログインしていない可能性が高くなります。Explorer.exe プロセスが実行されているかどうかを判断する場合、コレクションの Count プロパティ値を確認するのが非常に簡単な方法です。Count が 0 に等しければコレクションは空であり、コレクションが空になるのは、atl-ws-01 上で Explorer.exe プロセスのインスタンスが 1 つも実行されていない場合のみです。この場合、コンピュータにはだれもログオンしていない、というメッセージをエコー バックします。

Wscript.Echo "No one is logged on " & _
"to the computer."

Count が 0 以外であれば、Explorer.exe という名前のプロセスのコレクションを順番に処理するために For Each ループを設定します (なお、このコレクションには必ずアイテムが 1 つは含まれていると想定しています)。次に、Explorer.exe プロセスのインスタンスごとに GetOwner メソッドを呼び出して、Explorer.exe プロセスを実行しているアカウントを特定します。

objProcess.GetOwner strUser, strDomain

GetOwner に 1 組の "出力パラメータ" を渡していることに注意してください。つまり、strUser と strDomain を渡しています。出力パラメータは、名前を指定してメソッドに渡す単なる変数です。その後、メソッドが出力パラメータに値を代入します。この場合、strUser にはログオンしたユーザーのログオン名 (kenmyer) が代入され、strDomain にはログオンしたユーザーのドメイン名 (FABRIKAM) が代入されます。あとは、次のようにこれら 2 つの出力パラメータの値をエコー バックするだけです。

Wscript.Echo strDomain & "\" & strUser

ほら、かなりうまくいきます。しかし、このスクリプトは一歩進めることができます。GetOwner メソッドを使用すれば、コンピュータにログオンしているユーザーのログオン名 (samAccountName) を取得できます。これはすばらしいことですが、先に説明したように、ユーザーには samAccountName 以外にもさまざまな種類の名前があります。「あなたはだれ」という質問に本当に答えるには、ユーザーの displayName を知っておくのも悪くないでしょう。しかし、GetOwner を使用しても、他の名前を特定できませんよね。

できません。ただし、samAccountName を取得して Active Directory の検索スクリプトにプラグインし、そのログオン名のユーザー アカウントを見つけてバインドすることができます (samAccountNames はドメイン内で一意でなければならないので、この作業は簡単です)。ユーザー アカウントにバインドしたら、displayName を含め、Active Directory の任意のプロパティ値をエコー バックできます。

時間の都合上、図 6 のスクリプトを詳しく説明することはできません。Active Directory の検索に関する詳細については、「プリンタはどこ」を参照してください。このスクリプトでは、ログオンしたユーザーのログオン名を特定し、そのログオン名 (samAccountName) を持つユーザーを Active Directory から検索し、対象のユーザー アカウントにバインドしてからユーザーの displayName をエコー バックしていると言えば十分でしょう。簡単ですね。

Figure 6 ログオンしたユーザーへのバインド

strComputer = "atl-ws-01"

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

Set colItems = objWMIService.ExecQuery _
  ("Select * from Win32_Process Where Name = 'explorer.exe'")

If colItems.Count = 0 Then
  Wscript.Echo "No one is logged on to the computer."
Else
  For Each objProcess in colItems
    objProcess.GetOwner strUser,strDomain
  Next
End If

Const ADS_SCOPE_SUBTREE = 2

Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADsDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Page Size") = 1000
objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE 

objCommand.CommandText = "SELECT displayName FROM " & _
  "'LDAP://DC=wingroup,DC=fabrikam,DC=com' WHERE " & _
    "objectCategory='user' " & _
    "AND samAccountName = '" & strUser & "'"
Set objRecordSet = objCommand.Execute

objRecordSet.MoveFirst

Do Until objRecordSet.EOF
  Wscript.Echo objRecordSet.Fields("displayName").Value
  objRecordSet.MoveNext
Loop

では、本日お持ち帰りいただくものは何でしょう (マイクロソフトではこんな風に話します。Scripting Guy を完全にパニックに陥れるなら、近づいて、「すべての読者に主に何を持ち帰らせることができるか、やるべきことがどの程度あるか、目的に見合っているかどうかなどについてランクを付ける必要があるんだけど」と言ってみるだけです)。1 つは、ローカル コンピュータとリモート コンピュータの両方について、コンピュータにログオンしているユーザーに関する情報の入手方法がわかりました。さらに重要なことは、第 2 次世界大戦中にタイムスリップした場合に何をすべきかがわかったことです。このコラムを 1 部所持しておくことです (「お前はだれだ」という質問に答えられます)。また、何をするにも、常に、ワールド シリーズの勝者の一覧を持ち歩くことです。結局のところ、いつだれから「1903 年のワールド シリーズに勝ったのは」とたずねられるかわかりません。

注 : ボストン レッドソックスです。このチームは、最初のワールド シリーズの勝者でした。さて、1904 年のワールド シリーズで勝ったのはどのチームだと思いますか。知らないとはどういうことでしょう。失礼、ちょっと席を外します。電話をかけなくては....

Microsoft Scripting Guys は、マイクロソフトの仕事をしています、というよりもマイクロソフトにより雇われています。野球をプレイしたり監督したり観戦したり (または他のさまざまな活動を) しているのでない限り、彼らは TechNet スクリプト センターを運営しています。詳細については、www.scriptingguys.com を参照してください。

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; 許可なしに一部または全体を複製することは禁止されています.