Create  Edit  Diff  Mac OS X Serverで作る 小さな計算機室  Index  Search  Changes  RSS  Login

ユーザーの一括登録、追加と削除

Mac OS X ServerにはWorkgroup Manager(以下WgM)というソフトが付属していて, ユーザー登録やユーザー環境設定をGUIベースで簡単に行なうことができる。 実際に操作してみると,至れり尽せり,といった感じだ。

でも,毎学期ごとに1000人程度のユーザーについて,追加登録と削除を行なう 必要があるので,それをいちいち手作業でやるのは,ちょっと考えたくない。

幸い,WgMには,テキストファイルに記述されたユーザー情報を一括登録する機能が 備わっているので,WgMが認識してくれるようなファイルを吐き出すようなスクリプト を書いてやれば,割合と簡単に一括ユーザー登録ができる。

そのやり方と,テキストファイルの書式はWgMのマニュアル(http://www.apple.com/jp/server/documentation/ のユーザーの管理)に 書かれているので,それを参考にすればよい。 が,実際には試行錯誤の連続で、かなりの時間を費やすことになった。

ユーザーの一括登録

この端末室の場合、作業の流れはざっと以下のような感じになる:

  • 学期の始まりになると、教務担当者はデータベースを操作して、その学期に在学している学生のリストをファイルに保存する。
  • それを元にして、各フィールドが
学籍番号, 氏名, 所属, 在籍状況, 初期パスワード

のエクセルファイルを作成する。

  • それをCSVファイルで保存する。
  • CSVデータを自作のプログラムgen-user-record.rbで処理して得られたデータをWgMで「読み込む」(WgMの「サーバー」メニューから「読み込み」)

▼CSVファイルからWgMが認識するデータを生成するスクリプト:gen-user-record.rb

#!/usr/bin/ruby

$KCODE="EUC"

require "csv"
require "nkf"
require "iconv"

class UserRecord
   def initialize
      @server = 'xxxx.xxxx.ac.jp'
      @volume = 'home'
      @gid = 1000
      @usershell = '/bin/bash'
      @diskquota = '209715200'
   end

   def genuid(str)
      if str.length < 8 then
        uid = str.gsub(/([0-9][0-9])(\w)([0-9]*)/) {
          "19" + $1 + "0" + $3
  }
      else
        uid = str.gsub(/(\w)(\w)(\w)(\w)([0-9]*)/) {
            year = ""
            course = ""

            case $1
            when "9" then
              year = "199" + $2
            when "a" then
              year = "200" + $2
            when "b" then
              year = "201" + $2
            else
      end

            case $4
            when "b" then
              course = "1" 
            when "m" then
              course = "2"
            when "d" then
              course = "3"
            else
              course = "0"
      end
            year + course + $5
        }
      end
      return uid
   end

   def scancsv(csvfile)
     file = File.open(csvfile,'rb') 
#    Excel for Mac generates \r as end of line
     reader = CSV::Reader.create(file, ?, , ?\r )
     reader.each do |row|
          gakuseki = row[0].to_str.downcase
          longname = NKF.nkf("-e",row[1].to_str)
          course = NKF.nkf("-e",row[2].to_str)
          status = NKF.nkf("-e",row[3].to_str)
          password = row[4].to_str

    if /在学/ =~ status then
       self.gen_user_record(gakuseki,longname,course,password)
          end
     end
     reader.close
     file.close
   end

   def gen_user_record(gakuseki,longname,course,password)
          names = longname.split(/[\s ]/)
    homedir = "<home_dir><url>afp\\\://#{@server}/#{@volume}</url><path>#{gakuseki}</path></home_dir>"
          nfshomedir = "/Network/Servers/#{@server}/Volumes/#{@volume}/#{gakuseki}"
    uniqid = genuid(gakuseki)

    print "#{gakuseki}:"
          print 'dsAuthMethodStandard\:dsAuthClearText:'
          print "#{password}:"
          print "#{uniqid}:"
          print "#{@gid}:"
          print Iconv.iconv("UTF-8","eucJP","#{course}:")
    print Iconv.iconv("UTF-8","eucJP","#{longname}:")
    print "#{homedir}:"
          print "#{@diskquota}:"
          print "#{@usershell}:"
          print Iconv.iconv("UTF-8","eucJP","#{names[1]}:")
          print Iconv.iconv("UTF-8","eucJP","#{names[0]}:")
          puts  nfshomedir
   end

   def print_header
      print '0x0A 0x5C 0x3A 0x2C dsRecTypeStandard:Users 13 '
      print 'dsAttrTypeStandard:RecordName '
      print 'dsAttrTypeStandard:AuthMethod '
      print 'dsAttrTypeStandard:Password '
      print 'dsAttrTypeStandard:UniqueID '
      print 'dsAttrTypeStandard:PrimaryGroupID '
      print 'dsAttrTypeStandard:Comment '
      print 'dsAttrTypeStandard:RealName '
      print 'dsAttrTypeStandard:HomeDirectory '
      print 'dsAttrTypeStandard:HomeDirectoryQuota '
      print 'dsAttrTypeStandard:UserShell '
      print 'dsAttrTypeStandard:FirstName '
      print 'dsAttrTypeStandard:LastName '
      puts  'dsAttrTypeStandard:NFSHomeDirectory '
   end

end

def showhelp
print <<-ENDOFHELP 
使い方:
1. 学籍番号,学生氏名漢字,所属,学籍状態,初期パスワード
  の順で保存されたCSVファイルを用意(Mac版エクセルを使用)
   学籍状態のフィールドが「在学」のレコードのみ登録
2. gen-user-record.rb CSVファイル名 > 結果ファイル で保存
3. 結果ファイルを Workgroup Manager.app でインポート

メモ:
・Mac版Excelの出力するCSVファイルは、改行が\\r 文字コードがShiftJIS
・このツールの出力は、改行が\\n 文字コードはUTF-8
・一部の漢字はCSVで保存した時点で化けてしまうことがある(「高」の別字など)
・うまく登録できないときは新規ユーザー分を「既存のレコードを上書きする」で試してみる
ENDOFHELP
end

filename = ARGV.shift

if filename.nil? || /\A\-h/ =~ filename then
   showhelp
   exit
end

if FileTest.exist?(filename) then
   userrec = UserRecord.new
   userrec.print_header
   userrec.scancsv(filename)
end

Macのエクセルは改行コードがCR(だけ)なので、RubyのCSVクラスでフツーに読み込むと ちゃんと動作しない。そこでReaderを定義して、改行コードと区切り文字を明示的に与えている。

ユーザー登録ではまる

このスクリプトの動作は,大体こんな感じである:

  1. この大学の学籍番号は a5sb1234 のような形式になっている。この学籍番号をそのままユーザーID(レコード名)として使う。また、学籍番号から将来も含め重複が生じないようにUniqueIDを生成する。
  2. パスワードはクリアテキストであらかじめ用意しておく。
  3. Mac用のエクセルは,CVSファイルの出力がShiftJISコードで改行コードが?r。WMが認識するのはUTF-8と?nなので,ちゃんと変換する。
  4. その他,AFP用のホームディレクトリやdiskquota, shellなども記述したファイルを,WgMのマニュアルにある書式で出力する。

いくつかの試行錯誤の後,マニュアルに書いてある通り(のはず)の出力が得られるようになったので,WgMにえいやっと「読み込み」をさせてみるも,どうも様子がおかしい。

読み込みが終了しても,登録したはずのユーザーの中にログインできないものが多数ある。うまくいかなかったユーザーのディレクトリをいじろうとしてもエラーが出る。そのエラーは(主に)

eDSAuthFailed エラーコード:-14090

だった。これが出はじめると,どうにもこうにもおかしな状態になって,全くお手上げだ。

困ってしまい,いろいろと調べると,Appleの英語のサイトのdiscussion boardに同じ症状を訴えている記事が見つかった。どうもバグっぽい。そこに書いてあった対処法は以下のとおり:

  • シングルユーザーモードで立ち上げる(Appleキー+Sを押して立ち上げる)
  • mount -uw / (ファイルシステムをマウントする)
  • cd /var/db/authserver
  • rm authserverfree (パスワードサーバーが新しいスロットを割り当てるようにする)
  • rm *overflow (オーバーフローファイルを全て削除)
  • exit でマルチユーザーモードに
  • それでもうまく認証されないユーザーは削除してもう一度作成する

実はこの記事が投稿されたのは,問題に悩ませられはじめてから随分と経ってからで、 問題の発生時には知るよしもなかった。 そのときは、しかたなくディレクトリーサービスを一旦止め、再設定からはじめて、 何とか期限に間に合わせたのだった。 かなりの時間を費やした割には、何かが得られた気分のしない、 とても後味の悪い一件であった。

Mac用エクセルの使いにくさ

当初はユーザー登録用のデータの作成(WgMに読み込ませるまで)は、すべて エクセルのマクロで行うつもりだった。 そして、その開発を某社にお願いしたのだった。

ところが、Mac版のエクセルは

  • セルのデータをファイルに保存する際に、ShiftJISに変換される。 WMが認識するのはUTF-8なのでUTF-8に変換しようと試みたが、その機能が見当たらない。Windows版にはUTF-8への変換用の関数が用意されているのに、Mac版にはそれがない。
  • 上記と関係して、エクセルの中では正しく表示される漢字の中に、保存したとたんに化けてしまうものがあった(ShiftJISに変換できない漢字)。
  • 行末コードがCRのみなので、後からLFに変換する必要があった

など、この目的にはなんだかとても使いにくい代物であることが、後から判ったのだ。そこで、Mac OSには標準でインストールされているRubyを使って、自分でスクリプトを書くことにしたのだった。

ユーザー(学生)の更新

学期が変わると、新入学や卒業などに応じて、アカウントを追加したり削除する必要 が生じる。 そこで、古いユーザー一覧と新しいユーザー一覧を比較して、削除すべきユーザー(つまり、古いデータにはあるが新しいデータには存在しないレコード)だけを抽出するスクリプトも書いてみた。

Rubyには配列の「差」を取る機能が内蔵されているので、プログラムの心臓部はたった1行で済んでしまった。

▼二つのCSVを較べて、消去すべきユーザーを捜すスクリプト:diff-users.rb

ついでに、削除すべきユーザーリストをファイルから読み込んで、ディレクトリから削除し、ついでにホームディレクトリも移動するスクリプトも作っておいた。変数passwordにはrootのパスワードを入れておく。なお、この例は実際は必要なコマンドを画面に出力するだけ。

▼ユーザーを消去する(コマンドをはき出す)スクリプト:delete-users.rb

これら2つのスクリプトは、書いてみただけで、まだ本番で使用していないので、何か深刻な問題を含んでいる可能性があります。使用する場合はそれぞれの責任でお願いします。

▼二つのCSVファイルを比較して新規登録ユーザー分だけCSVではき出すスクリプト:list-new-user.rb

ユーザー更新の作業の流れ

  • 新規ユーザーの登録
    • 事務から登録すべきデータのエクセルファイルが送られてくる
    • Mac用のエクセルでCSV形式で保存する
    • list-new-user.rb 新年度のCSV 前年度のCSV > 新規登録用CSV
    • gen-user-record.rb 新規登録用CSV > 新規登録データ
    • WgMのサーバー/読み込みで新規登録データを選択。重複の処理方法は「重複を確認しない」。確認するとうまく行かないケースあり。ユーザーのプリセットも設定
  • 旧ユーザーの削除
    • diff-users.rb 新年度のCSV 前年度のCSV > 削除IDリスト
    • delete-users.rbのパスワード欄を設定しておく
    • delete-users.rb 削除IDリスト > 削除シェルスクリプト
    • sh 削除シェルスクリプト (1回もログインしていないユーザーにはホームディレクトリが存在しないので、移動の際にエラーが出るが、無視)

その他の注意

ピリオドやドル記号はユーザー名(ショート名)には使えない。

  • Anchor to the attached file is {{attach_anchor(file name [, page name])}}.
  • Indication of the attached file is {{attach_view(file name [, page name])}}.
  • List of the attached pages and files is {{attach_map}}.
Last modified:2007/07/20 07:52:46
Keyword(s):[WorkGroupManager]
References:[frontpage] [Mac OS X Serverで作る 小さな計算機室]