【IDM】MSMQ を使って確実なユーザー登録を行う その6 ~ ADにユーザーを登録する

MSMQでユーザーを登録するシリーズです。もうすこしで完結します。

前回はキューに蓄積されたメッセージを受信する方法について解説しました。

今回は、受信した情報をActive Directoryに登録します。

「ADに登録するスクリプトなんて、いまさら解説されても...」という方、いらっしゃいますよね。まぁ、そうおっしゃらずに、ちょっとご覧ください。

まずは、スクリプトの全体像から。長くてすいません。

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

Err.Clear Const MQ_RECEIVE_ACCESS  = 1  Const MQ_SEND_ACCESS     = 2  Const MQ_PEEK_ACCESS     = 32  Const MQ_ADMIN_ACCESS    = 128 Const MQ_DENY_NONE           = 0  Const MQ_DENY_RECEIVE_SHARE  = 1  Set objRootDSE = GetObject("LDAP://RootDSE") strDomainPath = objRootDSE.Get("DefaultNamingContext") strContainerADsPath = "LDAP://" & "CN=Users," & strDomainPath Set objContainer = GetObject(strContainerADsPath) Set objConnection = CreateObject("ADODB.Connection") objConnection.Open "Provider=ADsDSOObject;" Set objCommand = CreateObject("ADODB.Command") objCommand.ActiveConnection = objConnection set objQueueInfo_Input = CreateObject("MSMQ.MSMQQueueInfo") set objMsgQueue_Src = CreateObject("MSMQ.MSMQQueue") set objMessage = CreateObject("MSMQ.MSMQMessage") set objManagement = CreateObject("MSMQ.MSMQManagement") objManagement.Init "Junichia03",,"DIRECT=OS:junichia03\private$\Input" objQueueInfo_Input.Formatname = "DIRECT=OS:junichia03\private$\Input" Set objMsgQueue_Src  = objQueueInfo_Input.Open(MQ_Receive_ACCESS ,MQ_DENY_NONE) If objManagement.MessageCount > 0 then      On Error Resume Next      wscript.echo objManagement.MessageCount & " 件のメッセージがあります"      Set objMessage = objMsgQueue_Src.Receive()      arrBody = Split(objMessage.Body,",")     strUserName = Trim(arrBody(0))     strPassword = Trim(arrBody(1))     strHomeDir  = Trim(arrBody(2))      Wscript.Echo "ユーザー名:" & strUserName     Wscript.Echo "パスワード:" & strPassword     Wscript.Echo "ホームパス:" & strHomeDir      objCommand.CommandText = _ "<LDAP://" & strDomainPath & ">;" & _ "(&(objectCategory=person)(objectClass=user)" & _ "(sAMAccountName=" & strUserName & "));" & _ "DistinguishedName, sAMAccountName;" & _ "subtree" Set objRecordSet = objCommand.Execute If NOT objRecordSet.EOF then Set objNewUser = GetObject("LDAP://" & objRecordSet.Fields("DistinguishedName")) Else Set objNewUser = objContainer.Create("user", "CN=" & strUserName) objNewUser.Put "sAMAccountName", strUserName objNewUser.SetInfo() End If       Call ErrorHandle(1,err.Number, err.Description)      objNewUser.HomeDirectory = strHomeDir     objNewUser.SetInfo()       Call ErrorHandle(2, err.Number, err.Description)     objNewUser.SetPassword(strPassword)       Call ErrorHandle(3, err.Number, err.Description)

Else      Wscript.echo "メッセージがありません" End If Sub ErrorHandle(errPosition, errNumber, errDescription) Select Case errNumber Case 0 Case Else Wscript.Echo errPosition & "," & errNumber & "," & errDescription Wscript.Quit(1) End Select End Sub

ADSIを使用したユーザー登録スクリプトは巷にあふれているので、さほど苦労することは無いでしょう。

ただ、エラーハンドリングまで考えていることは意外と少ないようです。

今回の目的を思い出して下さい。登録に失敗した場合には Errorキューにメッセージを移動しなければなりません。つまり、エラーハンドリングをきちんとしないとならないのです。

しかしながら、誠に残念ですが VBScript はエラー処理が苦手です。苦手などというレベルでは無いかもしれません。ほとんど出来ないと言っても過言ではないでしょう。使えるエラー処理といえば、On Error Resume Next と Err オブジェクトくらいです。

だからといって、「PowerShell に移行します」なんて暴挙を許してよいのでしょうか?

もちろん良いのです。が、あえて苦労してみるのも楽しいもんです。

さて、今回のような「ユーザー登録に関する一連の処理」をスクリプト化する場合、考慮事項がたくさんあります。経験されている方は良くご存じでしょう。代表的な考慮事項を以下に示します。

  1. 既にユーザーがいる場合にはどうしたらよいか?(ID重複かどうかの判断)
  2. 属性の設定過程でエラーが発生した場合にはどうしたらよいか?(例えば値の形式不一致やパスワードポリシー不一致等)

1. については、よーく考えておきましょう。まじですよ。本当に考えておかないとひどい目にあいます。というのも、ユーザーを新規に登録したいタイミングと、ユーザーの属性を上書きしたいタイミングってのは同じ場合が多いのです。

人事異動の季節、「新規ユーザー一覧」と「既存ユーザー一覧」を分けて処理すれば確実ですが、実際のところ、そんなリストが人事部から出てくるかどうか。たいていは、同じリストに新旧ユーザーが混在してシステム部門に提示されるのではないでしょうか。下手すれば、ユーザーIDさえ人事部門から出てこない場合もあります(この場合にはシステム部門でIDを付加する作業が必要です)。

となると、処理は一通りじゃ済まないことが容易に想像できます。

既にユーザーが存在するかどうか調べ、存在しない場合に「新規に作成する」という処理を行わなければエラーになることは見えています。

さらに、既に存在する場合、リストに書かれた属性で上書きしてしまっても良いかどうかを判断する必要もあります。もしかしたらIDが重複してしまっているかもしれませんので、重複では無いことを保証する処理も必要になるかもしれません(フルネームや入社年度、社員番号等のチェックで回避する例も多いです)。

さらに、さらに、既に存在する原因が「登録処理をやり直したから」なんて場合も考えられます。

2. についてですが、入力となるCSVファイルの作成ミス等で間違えた値が入っている可能性があります。日付形式が全角で入力されていたり、数字にアルファベットが含まれていたり...。完全にミスを防ぐことはできませんから、登録時にはそれなりの対策が必要です。

対策といっても「空白をトリミング」したり「全角を半角に変換」したりするのは、正直、スクリプトで対応すべきではないと考えています。やはり、入力データに正しい値を入れてもらうよう、ソース側を修正すべきです。

じゃ、どんな対策を考えるのかというと、エラーが起きたときに、何をするかという点です。次に進むのか、そこで処理を止めるのか。

これ、ルールをしっかり決めておかないと、かなーり、やっかいです。

なんとなく処理が全部終わったからといって安心していたら、途中の「ホームディレクトリを設定している行」だけがエラーになっていた!なんてことはよくあります。

入力データの問題で再度処理を流すにしても、エラーが発生したユーザーを抽出して...なんてことをやると、とてもじゃありませんが、二次的なミスを犯す可能性が高くなります。ましてや、別の新しいスクリプトを作って対応なんて言語道断...ってこともありませんが、極力避けたいですね。

「1種類のスクリプトを何度流しても問題が無いような作り」であることが望ましいです。

以上のような考慮事項をすべて洗い出すと、きりがありません。パッケージを適用する場合にはこんな心配は必要ないのですが、今回のようにスクリプトを自作する場合には、以下のようなルールにしておくとよいでしょう。

  • スクリプトは1種類しか使わない
  • クラス化などを考慮せず、常に上から下に処理が流れるように
  • 処理の流れを事前に決めておき、エラーが発生したらそこで処理終了
  • どこでエラーが発生したか、わかるようにログを残す

とにかくシンプルにすることが重要です。

ということで、スクリプトを見てみましょう。

34行目。 言わずと知れた、Resume Next です。エラーが起きてもマイペンライ!っていう意味です(あれ?違うか?)。もしエラーが発生した場合には、これを捕捉しなければなりません。そのためには、エラーが発生した瞬間に処理が止まってしまっては困ります。

65,70,74行目。 エラーが発生したら処理をそこで止めてしまうために、エラーを判断する部分をFunction(82ー92行目)として共通化しています。Function の中では、、エラー番号が0であれば処理を継続し、そうでなければ スクリプトを終わらせています。このスクリプトでは、「ユーザーオブジェクトの取得」「ホームディレクトリ属性の設定」「パスワードの設定」それぞれの処理の後でエラー番号を判定しています。

88行目では、エラーの内容を Wscript.Echo でコンソール上に出力していますが、FileSystemObjectを使用してログファイルに吐き出すと後から確認できてよいでしょう。

で、ほんとは...こういう外部関数で処理を抜けてしまうというのは美しくないのですがね...わかっちゃいるんですが、メインに処理をごちゃごちゃ書くと混乱するしメンテも大変だし、スクリプトなので大目に見てほしいなぁと...。

49ー63行目。 キューから読み取ったユーザーIDを使ってADを検索し、

  • ヒットすれば当該ユーザーを取得
  • ヒットしなければ新規にユーザー作成

しています。

このスクリプトでは、「ユーザーIDの重複」=「ユーザー属性の上書き」であるとしました。

 

ということで、これでほぼスクリプトが完成しました。

次回(たぶん最終回)は、「エラーが発生したらキューを移動する」処理を実装し、MSMQのルールにスクリプトを登録しましょう。