読者です 読者をやめる 読者になる 読者になる

masahirorの気まま記録簿

個人的な出来事や意見、生活などの記録を思うままにブログに記録

デル株式会社
マウスコンピューター/G-Tune

VBScriptでActiveDirectyのユーザ管理を自動化する

プログラム 覚書 Work

新しいActiveDirectory(以下AD)を構築する事になった。
そうなるとアカウント管理、ユーザの登録が必要となる。社内には、メールアドレスや内線電話番号など、全社員のアドレス帳として管理しているDBがすでにあり、このユーザ情報を新規AD構築に活用すると相当便利そうだ。
しかしデータは何千件もあり、これを手動でADに全部登録するのは大変。なので、WSH*1のひとつであるVBScript(以下VBS)を使って自動登録するスクリプトを作ることにした。DBをいったんCSVに落とし(理由は後述)、VBSでCSVをADに追加するといったもの。VBSでADSI*2を操作すると、わりと簡単にADのメンテナンスが出来る。
MicrosoftのTechNetサイトに、AD操作に関するたくさんのサンプルソース・Tipsが公開されているので、スクリプトも組みやすい。

スクリプト一覧 Active Directory
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/default.mspx
Hey, Scripting Guy! - アーカイブ
http://www.microsoft.com/japan/technet/scriptcenter/resources/qanda/default.mspx

これらの資料を元に、今回は以下の要件を満たすVBSを作成することにした。

  1. アドレス帳DBの情報を取得して、AD上にユーザがいなければユーザを作成。
  2. ユーザ区分に応じて、グループに登録する。
  3. 今後、新規のユーザがアドレス帳DBに登録されたら、AD上にユーザを作成。
  4. 今後、既存ユーザの情報がアドレス帳DB上で更新されたら、AD上の情報も更新。
  5. 今後、アドレス帳DBからユーザが削除されたら、AD上のユーザを削除。
  6. ユーザの追加・削除情報、および追加時に自動設定したログオンパスワードをメールで送付。

そして見事プログラミング完了し、自動化達成。


以下、参考になったVBAスクリプトのサンプルや手法を、今回の要件実装に沿って紹介。同じような事をしようとしている人に参考となれば。
<前提>今回の要件ではOUやグループが新規に作成されることは無いので、あらかじめ用意してるものとする。また、キーはユーザIDとする。


ActiveDirectory操作

まず1と3と4の実装。

ユーザの検索

重複登録してエラーになるといけないので、既存のADにユーザIDがあるかどうかチェック。

Active Directory 内のユーザー アカウントの検索
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/list/uslsvb18.mspx
strUserName = "kenmyer"
dtStart = TimeValue(Now())
Set objConnection = CreateObject("ADODB.Connection")
objConnection.Open "Provider=ADsDSOObject;"
 
Set objCommand = CreateObject("ADODB.Command")
objCommand.ActiveConnection = objConnection
 
objCommand.CommandText = _
    "<LDAP://dc=fabrikam,dc=com>;(&(objectCategory=User)" & _
         "(samAccountName=" & strUserName & "));samAccountName;subtree"
  
Set objRecordSet = objCommand.Execute
 
If objRecordset.RecordCount = 0 Then
    WScript.Echo "sAMAccountName: " & strUserName & " does not exist."
Else
    WScript.Echo strUserName & " exists."
End If
 
objConnection.Close

今回はこれをFunction化して、あるかどうかTrue/Falseで返せるようにした。

アカウントの追加
ユーザー アカウントの作成
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/manage/usmgvb05.mspx
Set objOU = GetObject("LDAP://OU=management,dc=fabrikam,dc=com") 
Set objUser = objOU.Create("User", "cn=MyerKen") 
objUser.Put "sAMAccountName", "myerken" 
objUser.SetInfo 
属性の設定・変更

氏名や事業所名なども一緒にセットしておく。全項目設定可能だが、とりあえず「全般」タブの設定サンプル。

ユーザー アカウントの [全般] プロパティの変更
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/modify/usmdvb28.mspx
Set objUser = GetObject _
  ("LDAP://cn=myerken,ou=management,dc=fabrikam,dc=com")
 
objUser.Put "userPrincipalName", "MyerKen@fabrikam.com"
objUser.Put "sAMAccountName", "MyerKen01"
objUser.Put "userWorkstations", "wks1,wks2,wks3"
objUser.SetInfo

変更の場合はこれでよいが、新規の場合は上記「アカウントの追加」のスクリプトにちょっと手を加えれば良い。具体的には、

objUser.Put "sAMAccountName", "myerken"

の下に、他の属性をPutしていけば良いので、わざわざSetInfoしてGetObjectで開きなおす必要は無い。

パスワードの設定
ユーザー パスワードの設定
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/pwds/uspwvb01.mspx
Set objUser = GetObject _ 
 ("LDAP://cn=MyerKen,ou=management,dc=fabrikam,dc=com") 
objUser.SetPassword "i5A2sj*!"

これも、新規の場合は上記と同様GetObjectを再度やる必要は無い。ただし、ユーザ追加・属性設定の後いったんSetInfoを行っておかないとエラーになる。
ちなみに、既存ユーザのパスワードを再設定したい場合は、以下のようにする。

objUser.ChangePassword "i5A2sj*!" 
パスワードの無期限化

ログインドメインとして使わず、ファイルサーバやIISなどの認証のみでADを利用する場合、パスワードの期限切れ通知をする方法が無いので有効期限があると困る場合はこれを設定しておくといい(注:セキュリティを考慮すること)。

無期限パスワードの設定
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/pwds/uspwvb03.mspx
Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000

Set objUser = GetObject _
    ("LDAP://cn=myerken,ou=management,dc=fabrikam,dc=com")
intUAC = objUser.Get("userAccountControl")

If ADS_UF_DONT_EXPIRE_PASSWD AND intUAC Then
    Wscript.echo "Already enabled"
Else
    objUser.Put "userAccountControl", intUAC XOR _
    ADS_UF_DONT_EXPIRE_PASSWD
    objUser.SetInfo
    WScript.Echo "Password never expires is now enabled"
End If 
アカウントの有効化

新規追加の場合、そのままだと使えないので、有効化する必要がある。

ユーザー アカウントの有効化
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/status/usstvb02.mspx
Set objUser = GetObject _ 
  ("LDAP://cn=myerken,ou=management,dc=fabrikam,dc=com") 
objUser.AccountDisabled = FALSE 
objUser.SetInfo 


次に2の実装。

既存グループへのユーザ追加

ちょうどよいサンプルが見つからなかったので、以下を組み合わせてちょっと修正。

ユーザー、グループ、および OU の作成
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/manage/usmgvb06.mspx
ローカルの Administrators グループにドメイン ユーザーを追加する方法はありますか
http://www.microsoft.com/japan/technet/scriptcenter/resources/qanda/oct04/hey1008.mspx
Set objUser = GetObject _ 
  ("LDAP://cn=myerken,ou=management,dc=fabrikam,dc=com") 
Set objGroup = GetObject _ 
  ("LDAP://cn=Group1,ou=management,dc=fabrikam,dc=com") 
objGroup.Add(objUser.ADsPath)

ただ、グループにユーザがすでに登録されている場合上記を行うとエラーになってしまうので、グループに指定ユーザがいるかどうかのチェック。

お前ら、wsh使ってますか? Part2
http://pc2.2ch.net/win/kako/1022/10222/1022248379.html

の974で書かれている「objGroup.IsMember」を利用して、Addするかどうか判定する。

If Not objGroup.IsMember(objUser.ADsPath) Then 
  objGroup.Add(objUser.ADsPath) 
End If 

もしグループがあるかどうか判定する必要がある場合は、「ユーザの検索」で使用したスクリプトを流用して、その一部↓

objCommand.CommandText = _
    "<LDAP://dc=fabrikam,dc=com>;(&(objectCategory=User)" & _
         "(samAccountName=" & strUserName & "));samAccountName;subtree"

にある「objectCategory=User」を「objectCategory=Group」と書き換えたものを作成すると良い。この場合「strUserName」に検索するグループ名を代入する。



5の実装。

ユーザの削除
Active Directory からのユーザー アカウントの削除
http://www.microsoft.com/japan/technet/scriptcenter/scripts/ad/users/manage/usmgvb07.mspx
Set objOU = GetObject("LDAP://ou=hr,dc=fabrikam,dc=com")
objOU.Delete "user", "cn=MyerKen"

ユーザを削除すると、所属するグループからも自動的に削除されるので、グループからユーザを削除する処理は不要。



最後に6の実装。

メール送信

追加・削除したユーザの一覧は、これまでのサンプルに書かれた処理で追加・削除処理を実行したタイミングに記録すれば可能なので割愛。
メールでの送信は、BASP21を利用すれば簡単に送付できる。

BASP21 DLL
http://www.hi-ho.ne.jp/babaq/basp21.html


<2016/9追記>
この記事を書いた頃は知らなかったのだが、Windows標準機能を使用することで、ランタイム等無しで送信も可能な様子(一部OSに制約あり)。

CDO を使用してメール送信
https://gallery.technet.microsoft.com/scriptcenter/e7dfcfb7-1f64-48ca-8d16-107091be99cc
Set objEmail = CreateObject("CDO.Message") 
 
objEmail.From = "helpdesk@fabrikam.com" 
objEmail.To = "administrator@fabrikam.com" 
objEmail.Subject = "Server down"  
objEmail.Textbody = "Server1 is no longer accessible over the network." 
 
objEmail.Configuration.Fields.Item _ 
    ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2 
objEmail.Configuration.Fields.Item _ 
    ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = _ 
        "smtpmailer"  
objEmail.Configuration.Fields.Item _ 
    ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25 
objEmail.Configuration.Fields.Update 
 
objEmail.Send 

パラメータの詳細やOSによる制約等は、下記サイトを参照。
www.atmarkit.co.jp
serialty.blog117.fc2.com




パスワードの自動生成

上記「パスワードの設定」で初期パスワードを設定する必要がある。これを全て同じものにするか、別ファイルから読み込ませるか、パスワードを生成するスクリプトを作成するか色々やり方はあるだろう。
今回は以下のソースを参考に「プログラムでのパスワード自動生成」を行った。

ASPの公園 【パスワード】パスワード生成
http://www.f-store.net/asp/parts.asp?MODE=ITIRAN&key=4823
genPassword = ""
Randomize
For i = 1 to 8
  intNum = Int(10 * Rnd + 48)
  intUpper = Int(26 * Rnd + 65)
  intLower = Int(26 * Rnd + 97)
  intRand = Int(3 * Rnd + 1)
  Select Case intRand
    Case 1
      strPartPass = Chr(intNum)
    Case 2
      strPartPass = Chr(intUpper)
    Case 3
      strPartPass = Chr(intLower)
    End Select
  genPassword = genPassword & strPartPass
Next
response.write(genPassword & "<br>")

ASP用のソースだが、「response.write」などを除けば基本的にはVBSでも利用可能。
もちろん、これだけではAD上のセキュリティポリシーを満たさないだろうし、長さも設定できなかったり、「0(ゼロ)」と「O(オー)」といった似たような文字を使用したりするんで、上記を参考に改良が必要。
(ちなみに、パスワード設定のスクリプトでポリシーに満たさないパスワードを設定しようとすると、Windowsのシステムからエラーダイアログが表示されてしまう。タスクで処理する場合は中断してしまうので注意。)



これらの組み合わせで出来た「hoge.vbs」を、Windowsのタスクに登録する事で毎日決まった時間に、自動で処理してくれるようになる。


手動で行ってたら莫大な時間がかかる上、今後のユーザメンテが非常に面倒になるので、受けれる恩恵は計り知れない。





参考までに

本来はDBから直接ADへ反映させるのが理想だが、DB直接接続の実装に時間がかかりそうだったため、今回はCSVからADへ反映させることにした。DB側でCSVを生成し、AD側からそれをFTPで取得するような仕組みにしている。
そこで得た、VBスクリプトによる「FTP接続」と「CSVをDBとして使用する」サンプルを紹介。特にCSVやEXCELのデータを使ってADを更新するパターンは多いと思うので、参考になれば。

FTP接続

FTPの操作は以下を参考にした。

教えて!goo VBSについて
http://oshiete1.goo.ne.jp/qa3119812.html?ans_count_asc=20

Windows標準のFTPクライアントをShellで呼び出して実行する方法である。上記をデータ取得する内容へ修正。

「ftpCommandList.txt」にftpコマンドを記述。
-----
ユーザ名
パスワード
cd ディレクトリ移動
get address.csv
bye
-----

hoge.vbsに下記のVBSを記述。
-----
set WshShell = WScript.CreateObject("WScript.Shell")
WshShell.Run "ftp -s:c:\ftpCommandList.txt ホスト名",1,1
-----

「WsgShell.Run」の引数3番目は、「1」だとFTPの終了を待ち、「0」か「指定無し」だと終了を待たない。CSVがないと次の処理に進めないので「1」した。

CSVをDBとして使用する

CSVを単なるテキストファイルとして使うと不便なので、DBとして扱かえるようにし、データの取得・更新にSQL文が使えるようにする。

8.1 エクセルやCSVをDB感覚で! - VBScript & JScript(JavaScript) Tips for WSH
http://www.happy2-island.com/vbs/cafe02/capter00801.shtml
8.3 データの読み込み - VBScript & JScript(JavaScript) Tips for WSH
http://www.happy2-island.com/vbs/cafe02/capter00803.shtml
Dim objADO
Dim objRS

'ADOオブジェクトを作成します
Set objADO = CreateObject("ADODB.Connection")

'ADOを使いCSVファイルを扱う準備(オープン)を行います
objADO.Open "Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
            "DBQ=c:\happy;" & _
            "ReadOnly=1"

'SQLを実行し、店舗コードがT5144のデータを抽出します
Set objRS = objADO.Execute("select * from ADO_EXCEL.csv where 店舗コード='T5144'")

'SQLの実行結果をデータが無くなるまで表示します
Do Until objRS.Eof = True

    'フィールド値の表示
    WScript.echo "店舗コード:" & objRS("店舗コード") & " 価格:" & objRS("価格")

    'カーソルを次の行へ
    objRS.MoveNext

Loop

'レコードセットをクローズします
objRS.Close

'ADOオブジェクトをクローズします
objADO.Close

'オブジェクトの破棄
Set objRS = Nothing
Set objADO = Nothing

テーブル名に、CSVのファイル名(フォルダパス無し・拡張子有り)を使用するのがポイント。
上記ページには、EXCELファイルを使った方法も掲載されている。






Windowsサーバー Hacks ―管理者必携のテクニック & WSHスクリプト 100選

Windowsサーバー Hacks ―管理者必携のテクニック & WSHスクリプト 100選

  • 作者: Mitch Tulloch,高橋基信,占部優希
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2005/05/26
  • メディア: 単行本(ソフトカバー)
  • 購入: 2人 クリック: 35回
  • この商品を含むブログ (23件) を見る
Windowsサーバークックブック ―ネットワーク管理者のためのレシピ集

Windowsサーバークックブック ―ネットワーク管理者のためのレシピ集

  • 作者: Robbie Allen,マイクロソフト株式会社ITプロエバンジェリストチーム,株式会社クイープ
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2006/05/01
  • メディア: 大型本
  • 購入: 1人 クリック: 25回
  • この商品を含むブログ (14件) を見る
ADSI ASPプログラミング―次世代標準ディレクトリサービスAPI ADSI入門 (Programmer’s SELECTION)

ADSI ASPプログラミング―次世代標準ディレクトリサービスAPI ADSI入門 (Programmer’s SELECTION)

*1:Windows Script Host

*2:Active Directory Service Interfaces