#! ruby -Ks
    ##
    ##  set_path_for_ruby.rb
    ##  set_path_for_ruby.pi
    ##
    ##  A. ruby.exe の所在地にこのスクリプトを置いて実行する。
    ##  B. ruby.exe にドロップして起動する。
    ##
    ##  すること：
    ##  環境変数 path に ruby.exe のディレクトリを追加する。
    ##  所在確認、ダブリの点検・削除を行う。
    ##  System のレジストリに書き込む。System の方が優先されるため。
    ##
    ##  Apollo には依存できない。Apollo を起動可能にするためのスクリプト。
    ##
    ##  GUI である。message_box で表示する。
    ##
    ##  −−−−
    ##
    ##  （01）require "Win32API"
    ##  （02）Regisrty.rb の最小限サブセットを定義する。
    ##  （03）say などのユーティリティを作る。
    ##
    ##  （04）システムを調べて変数にセットする。exe_name から「書込ディレクトリ」を決定する。
    ##
    ##  （05）書込ディレクトリに ruby.exe が《ない》とき：警告を出して、設定せずに終了
    ##
    ##  （06）書込ディレクトリに ruby.exe が《ある》とき：
    ##  （07）書込ディレクトリに windows/winnt の文字が《ある》とき：対象外として、終了。
    ##
    ##  （08）ENV のパスで ruby.exe のあるもの、REG のパスで ruby.exe があるものを抽出する。
    ##  （09）ENV の ruby.exe 抽出リストと、REG のそれとが《異なっている》場合；
    ##          バッチで設定されているか、システムパスにあるか、リブートしていないか。なので中止。
    ##
    ##  （10）ENV の ruby.exe 抽出リストと、REG のそれとが《異なっていない》場合：
    ##  （11）抽出リストのパスに ruby.exe が《ない》とき：書込ディレクトリを設定して終了。
    ##
    ##  （12）抽出リストのパスに ruby.exe が《ある》とき：
    ##  （13）既存のパスに一つだけ見つかり、それが書込ディレクトリなら：ＯＫなので終了。
    ##  （14）複数ある場合、書込ディレクトリ以外がセットされている場合：
    ##  （15）削除してもよいかの問い合わせて、書き込み用データから削除する。ただし、
    ##      （16）書換ディレクトリのとき：だまって削除（後で先頭にいれる＝移動するので）
    ##      （17）そのパスの中に windows/winnt の文字が《ある》とき：無条件で削除対象から外す。
    ##      （18）その他の場合：問い合わせて「はい」なら削除。
    ##  （19）書き換える
    ##
    ##  書き換えるとき：
    ##  （51）path_old.reg がある場合には「削除してよいか？」と警告して終了。
    ##  （52）万一のために現在の設定を path_old.reg に保存する。
    ##  （53）書込予定リストに書込ディレクトリがあれば削除する（先頭に移動する）。
    ##
    ##  path_old.reg は安全とはいえない。サーバに他のマシンの設定が残っている場合がある。
    ##

    ##  （01）require "Win32API"
require 'Win32API'

    ##
    ##  （02）Regisrty.rb の最小限サブセットを定義する。
    ##
    ##  From Registry.rb [ruby-dev:15573] 
    ##
module Win32
  class Registry
    module Constants
      HKEY_CLASSES_ROOT = 0x80000000
      HKEY_CURRENT_USER = 0x80000001
      HKEY_LOCAL_MACHINE = 0x80000002
      HKEY_PERFORMANCE_DATA = 0x80000003
      HKEY_CURRENT_CONFIG = 0x80000004
      HKEY_DYN_DATA = 0x80000005
      
      REG_NONE = 0
      REG_SZ = 1
      REG_EXPAND_SZ = 2
      REG_BINARY = 3
      REG_DWORD = 4
      REG_DWORD_LITTLE_ENDIAN = 4
      REG_DWORD_BIG_ENDIAN = 5
      REG_LINK = 6
      REG_MULTI_SZ = 7
      REG_RESOURCE_LIST = 8
      REG_FULL_RESOURCE_DESCRIPTOR = 9
      REG_RESOURCE_REQUIREMENTS_LIST = 10
      REG_QWORD = 11
      REG_QWORD_LITTLE_ENDIAN = 11
      
      STANDARD_RIGHTS_READ  = 0x00020000
      STANDARD_RIGHTS_WRITE = 0x00020000

      KEY_QUERY_VALUE = 0x0001
      KEY_SET_VALUE = 0x0002
      KEY_CREATE_SUB_KEY = 0x0004
      KEY_ENUMERATE_SUB_KEYS = 0x0008
      KEY_NOTIFY = 0x0010
      KEY_CREATE_LINK = 0x0020
      KEY_READ = STANDARD_RIGHTS_READ |
        KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY
      KEY_WRITE = STANDARD_RIGHTS_WRITE |
        KEY_SET_VALUE | KEY_CREATE_SUB_KEY
      KEY_EXECUTE = KEY_READ
      KEY_ALL_ACCESS = KEY_READ | KEY_WRITE | KEY_CREATE_LINK
      
      REG_OPTION_RESERVED = 0x0000
      REG_OPTION_NON_VOLATILE = 0x0000
      REG_OPTION_VOLATILE = 0x0001
      REG_OPTION_CREATE_LINK = 0x0002
      REG_OPTION_BACKUP_RESTORE = 0x0004
      REG_OPTION_OPEN_LINK = 0x0008
      REG_LEGAL_OPTION = REG_OPTION_RESERVED |
        REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK |
        REG_OPTION_BACKUP_RESTORE | REG_OPTION_OPEN_LINK
      
      REG_CREATED_NEW_KEY = 1
      REG_OPENED_EXISTING_KEY = 2
    end
    include Constants
    class Error < SystemCallError
      def initialize(code)
        @code = code
        fm = Win32API.new('kernel32.dll', 'FormatMessageA', 'LPLLPLP', 'L')
        msg = "\0" * 1024
        len = fm.call(0x1200, 0, code, 0, msg, 1024, 0)
        super "Error \##{code}\n" + msg[0, len].tr("\r", '').chomp
      end
      attr_reader :code
    end
    module API
      [
        %w/RegOpenKeyExA    LPLLP        L/,
        %w/RegQueryValueExA LPLPPP       L/,
        %w/RegSetValueExA   LPLLPL       L/,
        %w/RegCloseKey      L            L/,
      ].each do |fn|
        const_set fn[0].intern, Win32API.new('advapi32.dll', *fn)
      end
      module_function
      def check(result)
        raise Error.new(result) if result != 0
      end
      def packdw(dw)
        [dw].pack('V')
      end
      def unpackdw(dw)
        dw.unpack('V')[0]
      end
      def OpenKey(hkey, name, opt, desired)
        result = packdw(0)
        check RegOpenKeyExA.call(hkey.hkey, name, opt, desired, result)
        unpackdw(result)
      end
      def QueryValue(hkey, name)
        type = packdw(0)
        size = packdw(0)
        check RegQueryValueExA.call(hkey, name, 0, type, 0, size)
        data = ' ' * unpackdw(size)
        check RegQueryValueExA.call(hkey, name, 0, type, data, size)
        [ unpackdw(type), data[0, unpackdw(size)] ]
      end
      def SetValue(hkey, name, type, data, size)
        check RegSetValueExA.call(hkey, name, 0, type, data, size)
      end
      def CloseKey(hkey)
        check RegCloseKey.call(hkey)
      end
    end
    class << self
      alias original_new new
      private :original_new
    end
    def self.new(hkey, subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED)
      newkey = API.OpenKey(hkey, subkey, opt, desired)
      this = original_new newkey, hkey, subkey, REG_OPENED_EXISTING_KEY
    end
    @@final = proc { |hkey| proc { API.CloseKey(hkey[0]) if hkey[0] } }
    def initialize(hkey, parent, keyname, disposition, fin = true)
      @hkey = hkey
      @parent = parent
      @keyname = keyname
      @disposition = disposition
      if fin
        @hkeyfinal = [ hkey ]
        ObjectSpace.define_finalizer self, @@final.call(@hkeyfinal)
      end
    end
    attr_reader :hkey, :parent, :keyname, :disposition
    constants.select { |c| /^HKEY_/ =~ c }.each do |c|
      const_set c, original_new(const_get(c), nil, c,
                                REG_OPENED_EXISTING_KEY, false)
    end
    def open(subkey, desired = KEY_READ, opt = REG_OPTION_RESERVED)
      type.open(self, subkey, desired, opt)
    end
    def close
      API.CloseKey(@hkey)
      @hkey = @parent = @keyname = nil
      @hkeyfinal[0] = nil
    end
    def read(name)
      type, data = API.QueryValue(@hkey, name)
      case type
      when REG_SZ,REG_EXPAND_SZ
        [ type, data.chop ]
      else
        raise TypeError, "sorry, type #{type} is not supported currently."
      end
    end
    def write(name, type, data)
      case type
      when REG_SZ,REG_EXPAND_SZ
        size = data.length + 1
        data << "\0"
      else
        raise TypeError, "unexpected type #{type}"
      end
      API.SetValue(@hkey, name, type, data, size)
    end
  end
end
###########

    ##  （03）say などのユーティリティを作る。

    ##
    ##  string only
    ##
def Win32::reg_read( root_key , sub_key , name )
  Win32::Registry.new( root_key , sub_key ).read( name )[1]
rescue Exception
  ""
end;

def Win32::reg_write( root_key , sub_key , name , val , type=Win32::Registry::REG_EXPAND_SZ )
  Win32::Registry.new( root_key , sub_key , Win32::Registry::KEY_WRITE ).write( name , type , val )
end

####

module Win32Path
  ROOT_KEY_SYS_ENV = Win32::Registry::HKEY_LOCAL_MACHINE
  ROOT_KEY_USR_ENV = Win32::Registry::HKEY_CURRENT_USER
  
  SUB_KEY_SYS_ENV = 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment';
  SUB_KEY_USR_ENV = 'Environment';

  def Win32Path::path_sys
    Win32::reg_read( ROOT_KEY_SYS_ENV , SUB_KEY_SYS_ENV , 'path' )
  end
  
  def Win32Path::path_sys=(v)
    Win32::reg_write( ROOT_KEY_SYS_ENV , SUB_KEY_SYS_ENV , 'path' , v );
  end
  
  def Win32Path::path_usr
    Win32::reg_read( ROOT_KEY_USR_ENV , SUB_KEY_USR_ENV , 'path' )
  end
  
  def Win32Path::path_usr=(v)
    Win32::reg_write( ROOT_KEY_USR_ENV , SUB_KEY_USR_ENV , 'path' , v );
  end
end

####

def Win32API::send_message( handle , msg , wp ,lp )
  Win32API.new( 'user32' , 'SendMessage' , %w(N N N P) , "N" ).
        Call( handle , msg , wp , lp )
end

	##  WM_WININICHANGE     = $001A;
	##  HWND_BROADCAST = $FFFF;
def Win32API::env_changed
  send_message( 0xffff , 0x1a , 0 , "Environment" )
end
  
def Win32API::message_box( handle,text,caption,type=0 )
  Win32API.new( 'user32' , 'MessageBox' , %w(N P P N) , "N" ).
        Call( handle , text , caption ,type )
end
def Win32API::say( msg , caption='' , type=0 )  #   OK
  message_box( 0, msg , caption , type )
end
def Win32API::ask( msg , caption='' , type=4 )  # 6:はい、7:いいえ
  message_box( 0, msg , caption , type ) == 6
end
def Win32API::ask3( msg , caption='' , type=3 ) # 6:はい、7:いいえ、3:キャンセル
  message_box( 0, msg , caption , type )
end

def Win32API::shell_execute( handle , cmd , file , param , dir , show_mode )
  Win32API.new( 'shell32' , 'ShellExecute' , %w(N P P P P N) , "N" ).
        Call( handle , cmd , file , param , dir , show_mode )
end
def Win32API::shell_open( file, param='', dir='', show_mode=5 ) ## 5:SW_SHOW
    shell_execute(0, 'open', file, param, dir, show_mode)
end

def Win32API::get_command_line
  Win32API.new( 'kernel32' , 'GetCommandLine' , nil , "P" ).Call()
end
def Win32API::exe_name
  s = get_command_line
  return ( s[0] == ?" ) ? ( s[1..-1].split('"')[0] ) : ( s.split(' ')[0] )
end

######

def say( msg , caption="set_path_for_ruby.rb" )
  Win32API.say (msg , caption )
end

def ask( msg , caption="set_path_for_ruby.rb" )
  Win32API.ask (msg , caption )
end

    ##  6:yes
    ##  7:no
    ##  2:cancel
def ask3( msg , caption="set_path_for_ruby.rb" , *args )
  Win32API.ask3(msg , caption , *args)
end

def path_fix(path)
  path.tr('\\','/').upcase
end

def path_eq( p1,p2 )
  path_fix(p1) == path_fix(p2)
end

######## subroutines

def bye dmy=nil
  say "じゃ、またね！"
  exit
end

######## get information

def get_information
  @target = "ruby.exe"
  @exe_name = File.expand_path(Win32API::exe_name)
  @path_env_old = ENV["path"].split(';')
  @path_sys_old = Win32Path::path_sys.split(";")
  @path_usr_old = Win32Path::path_usr.split(";")
  @path_sys_new = @path_sys_old.dup
  @path_usr_new = @path_usr_old.dup
  @path_to_write = @path_sys_new
  
    # debug..
  ##begin
  ##  require "phi"
  ##  Phi::SCREEN.FormConsole.show if /apollo/i =~ @exe_name
  ##rescue Exception
  ##end
  
    ##
    ##  $: は捜しても無駄。
    ##
  dir = File.dirname @exe_name
  @target_dir = dir if FileTest.file? "#{dir}/#{@target}"
  
  p "Dir.pwd"          , Dir.pwd
  p "Win32API::get_command_line" , Win32API::get_command_line
  p "@exe_name"   , @exe_name
  p "@target_dir" , @target_dir
  
  p "@path_env_old" , @path_env_old
  p "@path_sys_old" , @path_sys_old
  p "@path_usr_old" , @path_usr_old
end

########

def write_path
  @path_sys_new.delete_if{|dir| path_eq(dir,@target_dir) }
  @path_usr_new.delete_if{|dir| path_eq(dir,@target_dir) }
  @path_to_write.unshift( @target_dir.tr('/','\\') )
  
  if (@path_sys_new == @path_sys_old ) && (@path_usr_new == @path_usr_old)
    say "あれ？、同じになちゃった。\n\n#{@path_new}"
    bye 
  end
  path_old_reg = "#{@target_dir}/path_old.reg"
  path_new_reg = "#{@target_dir}/path_new.reg"

  path_sys_str = @path_sys_new.join(';')
  path_usr_str = @path_usr_new.join(';')
  
  begin
    File.open( path_old_reg ,"w"){|io|
      io.write <<_____
REGEDIT4

[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment]
"Path"="#{Win32Path::path_sys}"

[HKEY_CURRENT_USER\\Environment]
"Path"="#{Win32Path::path_usr}"
_____
    }

    File.open( path_new_reg ,"w"){|io|
      io.write <<_____
REGEDIT4

[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment]
"Path"="#{path_sys_str}"

[HKEY_CURRENT_USER\\Environment]
"Path"="#{path_usr_str}"
_____
  }
  rescue
    say "元のデータを書込めなかったけど、重大な支障ではないので次へ行きます。"
  end

  msg = <<_____
ここで「はい(Y)」で答えると、次のようにレジストリを書き換えます。

[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment]
"Path"="#{path_sys_str}"

[HKEY_CURRENT_USER\\Environment]
"Path"="#{path_usr_str}"

万一のために 
#{path_old_reg} に変更前のレジストリを
#{path_new_reg} に変更後（予\定）のレジストリを
記録してあります。

_____
  bye unless ask msg
  
  if @path_sys_new != [] && @path_sys_new != @path_sys_old
    Win32Path::path_sys = path_sys_str
    ##p "skip actual writing to registry"
    p ("Win32Path::path_sys = #{path_sys_str}" )
  end
  if @path_usr_new != [] && @path_usr_new != @path_usr_old
    Win32Path::path_usr = path_usr_str
    ##p "skip actual writing to registry"
    p ("Win32Path::path_usr = #{path_usr_str}" )
  end
  
  p "sending env_changed message to broadcast"
  Win32API::env_changed
  p "end of send"
  
  msg = <<_____
環境変数 Path を変更しました。

ただし、《既に起動しているプロセス》には反映されません。
反映させたい場合にはそのプログラムを再起動してください。
_____
  exit say msg
end

##################

    ##  （04）システムを調べて変数にセットする。exe_name から「書込ディレクトリ」を決定する。
get_information

MAX_PATH_SIZE = 513

def get_short_path_name(lfn)
  require 'Win32API'
  buff = 0.chr * MAX_PATH_SIZE
  Win32API.new('kernel32', 'GetShortPathName', %w(P P N), "N").
    Call(lfn, buff, buff.size)
  buff.split(0.chr)[0]
end

if @path_sys_old + @path_usr_old == []
  ruby_path = get_short_path_name(@target_dir.tr('/','\\'))
  re_ruby_path =
    Regexp.compile(Regexp.escape(ruby_path)+'\\\\?', Regexp::IGNORECASE)
  autoexec_bat = 'c:\autoexec.bat'
  autoexec_tmp = 'c:\autoexec.tmp'
  msg = "#{autoexec_bat} の 'SET PATH=' 行を調べます...\n"
  append_msg = ''
  defined = false
  found = false
  File.open(autoexec_bat, 'r') {|fbat|
  File.open(autoexec_tmp, 'w') {|ftmp|
  while line = fbat.gets
    ftmp.print line
    if /^\s*(?:SET\s+)?PATH\s*=\s*/i =~ line
      found = true
      if $'.chomp.split(/;/).find{|path| re_ruby_path =~ path}
        defined = true
        append_msg = "#{ruby_path} は設定されています。"
      else
        next if defined
        set_path_line = "SET PATH=#{ruby_path};%PATH%"
        append_msg = <<EOT
#{ruby_path} は設定されていません。
#{autoexec_bat} に追加します。
#{set_path_line}

設定を有効にするには再起動が必要です。
EOT
        ftmp.puts set_path_line
      end
    end
  end
  }}
  unless found
    append_msg = "見つかりませんでした。"
  end
  msg << append_msg
  if found && !defined
    File.rename(autoexec_tmp, autoexec_bat)
  else
    File.delete(autoexec_tmp)
  end
  exit say msg
end


    ##  （05）書込ディレクトリに ruby.exe が《ない》とき：警告を出して、設定せずに終了
exit say "#{@target} が存在するディレクトリで実行してください" unless @target_dir

    ##  （06）書込ディレクトリに ruby.exe が《ある》とき：
    ##  （07）書込ディレクトリに windows/winnt の文字が《ある》とき：対象外として、終了。
if /(windows|winnt)/i =~ @target_dir    
  msg = <<____
パス #{@target_dir} の中に 
「windows」または「winnt」の文字があります。
システムパスをいじると危険なのでスクリプトを中止します。
____
  exit say msg
end    

    ##  （08）ENV のパスで ruby.exe のあるもの、REG のパスで ruby.exe があるものを抽出する。
@env_include_target = @path_env_old.select{|path| FileTest.file? "#{path}/#{@target}" }
@sys_include_target = @path_sys_old.select{|path| FileTest.file? "#{path}/#{@target}" }
@usr_include_target = @path_usr_old.select{|path| FileTest.file? "#{path}/#{@target}" }

p "@env_include_target",@env_include_target
p "@sys_include_target",@sys_include_target
p "@usr_include_target",@usr_include_target
  
    ##  （09）ENV の ruby.exe 抽出リストと、REG のそれとが《異なっている》場合；
    ##          バッチで設定されているか、システムパスにあるか、リブートしていないか。なので中止。
if @env_include_target != @sys_include_target + @usr_include_target
  msg = <<____
#{@target} を含むパスが ENV と REG とで異なっているので中止します。

ENV:
#{@env_include_target.join("\n")}

REG - SYS:
#{@sys_include_target.join("\n")}

REG - USR:
#{@usr_include_target.join("\n")}

原因はいろいろ考えられますのでドキュメントを参照してください。
____
  say msg
  bye
end

    ##  （10）ENV の ruby.exe 抽出リストと、REG のそれとが《異なっていない》場合：
    ##  （11）抽出リストのパスに ruby.exe が《ない》とき：書込ディレクトリを設定して終了。
if (@env_include_target == [])
  bye unless ask "#{@target_dir} を環境変数 Path に追加します。"
  write_path
  exit
end

    ##  （12）抽出リストのパスに ruby.exe が《ある》とき：
    ##  （13）既存のパスに一つだけ見つかり、それが書込ディレクトリなら：ＯＫなので終了。
if (@env_include_target.size == 1 ) && path_eq( @env_include_target[0] , @target_dir )
  exit say "環境変数 Path には問題がないようです。\n\n#{@target} は #{@target_dir} にあります。"
end

    ##  （14）複数ある場合、書込ディレクトリ以外がセットされている場合：
    ##  （15）削除してもよいかの問い合わせて、書き込み用データから削除する。ただし、
    ##      （16）書換ディレクトリのとき：だまって削除（後で先頭にいれる＝移動するので）
    ##      （17）そのパスの中に windows/winnt の文字が《ある》とき：無条件で削除対象から外す。
    ##      （18）その他の場合：問い合わせて「はい」なら削除。
def ask_delete( dir )
  return true if path_eq( dir , @target_dir )
  if /(windows|winnt)/i =~ dir
    say "#{dir}\n\nwindows または winnt が含まれているので削除しません。"
    return false
  end
  return ask "#{dir}\n\nを環境変数 Path から削除します。"
end

if (@env_include_target != [])
  msg = <<_____
次のパスに #{@target} が見つかりました。

#{@env_include_target.join("\n")}

これ(ら)を環境変数 Path から削除してよいですか？
_____
  ans = ask3(msg)
  p ans
  bye if ans == 2   ## cancel
  if ans == 6       ##  yes
    @env_include_target.each{|dir|
      @path_sys_new.delete_if{|sdir| path_eq(sdir,dir) && ask_delete(dir)}
      @path_usr_new.delete_if{|sdir| path_eq(sdir,dir) && ask_delete(dir)}
    }
    p "@path_sys_new",@path_sys_new
    p "@path_usr_new",@path_usr_new
  end
end  

    ##  （19）書き換える
write_path

raise

