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

IronRubyでWindowsのサービスをコントロールする

2010/7/3追記。このコードではうまく動作しません。「webrickでリダイレクトするときの注意」を参照してweb.rbを修正してください。
Windowsのサービスをコントロールするコマンドでsc.exeってあるじゃないですか?コレを使えばリモートでサーバーのサービスをコントールできるなと思っていたんだけど同一ドメインじゃないとうまく動かないそうな。
ならば、.NetのSystem.ServiceProcessを使ってコントロールしようと思ったのがコトの始まりです。

どうせならWeb経由で動くようにしたいと思いWebrickでコントロールできるようにしてみました。

環境はIronRuby0.9.2+WindowsXP+.Net Framework2.0です。

System.ServiceProcess.rb

require 'C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.ServiceProcess.dll'

System.ServiceProcessはデフォルトではrequireできないのでdllを定義しておきます。
.Net標準のライブラリを使うのにフルパスを記述するのはなんかかっこ悪いなぁ。たぶんこの書き方は間違っている。通常はどのように定義するのでしょうか?
ご自分の環境とパスが異なる場合は変更しておいて下さい。

list.rhtml

<html>
<head>
<title>Window Services</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<% @services.each do |service| %>
  <%= service.display_name %>
  <b>
  <% if service.status == ServiceControllerStatus.Running %>
    実行中
  <% elsif service.status == ServiceControllerStatus.Stopped %>
    停止中
  <% end %>
  </b>|
  <% if service.status == ServiceControllerStatus.Running %>
    開始|
    <a href ="stop/<%= service.service_name %>">終了</a>
  <% elsif service.status == ServiceControllerStatus.Stopped %>
    <a href ="start/<%= service.service_name %>">開始</a>
    |終了
  <% end %>|<br>
<% end %>
</body>
</html>

テンプレートファイルです。UTF-8で保存しておきましょう。

web.rb

require 'webrick'
require 'erb'
require 'System.ServiceProcess'
require 'uri'

include System::ServiceProcess

include WEBrick
server = HTTPServer.new({
  :Port => 8080,
  :BindAddress => '127.0.0.1',
  :MimeTypes => HTTPUtils::DefaultMimeTypes.merge({"rhtml"=>"text/html"}),
  :DocumentRoot => Dir.pwd})

class ListServlet < HTTPServlet::AbstractServlet
  def do_GET(req, res)
    @services = ServiceController.GetServices.sort_by{|x| x.display_name}
    open('list.rhtml') do |f|
      e = ERB.new(f.read)
      res.body = e.result(binding)
    end
    res['Content-Type'] = "text/html"
  end
end

server.mount("/list", ListServlet)
server.mount_proc("/start") { |req, res|
  service = ServiceController.new(File.basename(URI.parse(req.path).path))
  service.start
  service.WaitForStatus(ServiceControllerStatus.Running)
  res.set_redirect(HTTPStatus::MovedPermanently, 'http://localhost:8080/list')
}
server.mount_proc("/stop") { |req, res|
  service = ServiceController.new(File.basename(URI.parse(req.path).path))
  service.stop
  service.WaitForStatus(ServiceControllerStatus.Stopped)
  res.set_redirect(HTTPStatus::MovedPermanently, 'http://localhost:8080/list')
}

trap('INT') { server.shutdown }
server.start

URLはサーブレットを使って分かりやすくしました。

実行

C:\000\WindowsServiceController>ir web.rb
[2009-11-16 18:55:52] INFO  WEBrick 1.3.1
[2009-11-16 18:55:52] INFO  ruby 1.8.6 (2009-03-31) [i386-mswin32]
[2009-11-16 18:55:52] INFO  WEBrick::HTTPServer#start: pid=4312 port=8080

3つのファイルを1箇所にまとめて実行です。Vistaや7の人はコマンドプロンプトを管理者権限で実行してくださいね。
http://localhost:8080/list
にブラウザでアクセスすると。。。

だめじゃん。
サービスの表示用名称に日本語が含まれているためerbのresultメソッドで文字コードに互換性がないよと怒られてます。
けっこうハマったんだけど、-KuオプションでUTF-8を指定することで解決。

実行

C:\000\WindowsServiceController>ir -Ku web.rb
[2009-11-16 19:05:30] INFO  WEBrick 1.3.1
[2009-11-16 19:05:30] INFO  ruby 1.8.6 (2009-03-31) [i386-mswin32]
[2009-11-16 19:05:30] INFO  WEBrick::HTTPServer#start: pid=4812 port=8080

http://localhost:8080/list
にブラウザでアクセスするとこんどはよさそうです。

webブラウザでサービスの開始or終了ができるようになりました。
.Netとはいえ手作業でWindowsフォームを作るのは大変なので、簡単なインターフェースならばhtmlを書いてwebrickに乗せるというアプローチもアリだなと思いました。