LoginSignup
7
7

More than 5 years have passed since last update.

ServerspecでWinRMクライアントをwinrs.exeとPsExec.exeに置き換えてみた

Last updated at Posted at 2016-01-07

はじめに

serverspecのwinrmクライアントとして「winrs.exe」を試してみました。その結果処理時間がかなり短縮されることがわかりました。
また、Sysinternalsに含まれる「PsExec.exe」も試してみましたが、こちらは実行するコマンドの文字列数に制限があるようでServerspecでは利用できないことがわかりました。

今回は「winrs.exe」の置き換えについて記載します。

バージョン

記事作成で用いたOS・モジュールのバージョンはこちらです。

  • Windows 7 Pro SP1 32bit 日本語版
  • ruby 2.2.3p173 (2015-08-18 revision 51636) [i386-mingw32]
  • rspec (3.4.0)
  • rspec-core (3.4.1)
  • rspec-its (1.2.0)
  • serverspec (2.26.0)
  • specinfra (2.47.0)

※rubyスクリプトは全て文字コードを「UTF-8」で保存しています。
※バッチファイルは「Shift-JIS」で保存しています。

WinRMクライアントの作成

winrmバックエンドからwinrs.exeを実行するためのクラスを作成します。
※Open3.capture3を使うと固まってしまうのでsystemuを使いました。

C:\serverspec\sample\lib\CustomWinRs.rb
require 'rubygems'
require 'open3'
require 'systemu'
require 'kconv'

class CustomWinRs

  @my_name = 'CustomWinRs'

  def initialize(args = {})
    @user_name = args['user']     if args.key?('user')
    @user_pass = args['pass']     if args.key?('pass')
    @host_name = args['host']     if args.key?('host')
    @winrm_client = args['winrm'] if args.key?('winrm')
    @run_mode = args['mode']      if args.key?('mode')
  end

  def run_command(command)
    retcode = -1
    if @run_mode == "ps1"
      exec_cmd = "powershell -NoProfile -encodedCommand #{command}"
    else
      command  = "#{command}".gsub("\\\"","\"").gsub("\"","\\\"")
      exec_cmd = "powershell -NoProfile -command \"#{command}\""
    end
    exec_cmd     = "#{@winrm_client} -r:#{@host_name} -u:#{@user_name} -p:#{@user_pass} #{exec_cmd}"
    # systemuの不具合回避
    case "#{exec_cmd}".length
      when 21,282,3866 then
        exec_cmd = "#{@winrm_client} -remote:#{@host_name} -u:#{@user_name} -p:#{@user_pass} #{exec_cmd}"
    end
    begin
      # stdout, stderr, status  = Open3.capture3(exec_cmd)
      status, stdout, stderr = systemu "#{exec_cmd}"
      retcode = status.exitstatus   if ! status.nil?
      retcode = -1                  if retcode.nil? || "#{retcode}" == ''
    rescue Exception => excptn
      puts Kconv.tosjis( "EXCEPTION : #{excptn.message}" )
      retcode = -1
      stdout  = ""
      stderr  = excptn.message
    end
    return {:exitcode => retcode, :stdout => stdout, :stderr => stderr}
  end

  def run_cmd(command)
    @run_mode = "cmd"
    return run_command(command)
  end
  alias :cmd :run_cmd

  def run_powershell_script(command)
    @run_mode = "ps1"
    return run_command(Base64.strict_encode64("#{command}".encode('UTF-16LE')))
  end
  alias :powershell :run_powershell_script

end

spec_helper.rbでWinRMクライアントを変更します。

C:\serverspec\sample\spec\spec_helper.rb
require 'serverspec'
require 'yaml'
WINRM_CLIENT_GEM_WINRM = 1
WINRM_CLIENT_PS1_WINRM = 2
WINRM_CLIENT_CSP_WINRM = 3
WINRM_CLIENT_EXE_WINRS = 4
WINRM_CLIENT_EXE_PSEXE = 5
winrm_client = WINRM_CLIENT_EXE_WINRS
params = {}
params['hostname'] = ENV['TARGET_HOST']
params['run_env'] = "development"
puts ""
puts "##################################################"
puts "HOST: #{params['hostname']} : #{params['run_env']} : winrm_client = #{winrm_client}"
puts "##################################################"
properties      = YAML.load_file("env/properties.#{params['run_env']}.yml")
set_property    = properties
set :backend, :winrm
set :os, :family => 'windows'
Specinfra.configuration.customwinrm = true
case winrm_client
  when WINRM_CLIENT_GEM_WINRM
    require 'winrm'
    endpoint = "http://#{ENV['TARGET_HOST']}:5985/wsman"
    winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => "#{properties[:winrm_user]}", :pass => "#{properties[:winrm_pass]}", :basic_auth_only => true)
    winrm.set_timeout 300
  when WINRM_CLIENT_PS1_WINRM
    require 'CustomWinRm'
    winrm   = CustomWinRm.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => ".\\lib\\WinRm.ps1"})
  when WINRM_CLIENT_CSP_WINRM
    require 'CustomWinRm'
    winrm  = CustomWinRm.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => ".\\lib\\rmtcmd.exe"})
  when WINRM_CLIENT_EXE_WINRS
    require 'CustomWinRs'
    winrm   = CustomWinRs.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => "C:\\Windows\\system32\\winrs.exe"})
    Specinfra.configuration.os_encoding = "Windows-31J"
    Specinfra.configuration.file_encoding = "Shift-JIS"
    Specinfra.configuration.script_encoding = "Windows-31J"
  when WINRM_CLIENT_EXE_PSEXE
    require 'CustomPsExec'
    winrm   = CustomPsExec.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => "C:\\Tool\\SysinternalsSuite\\PsExec.exe"})
end
Specinfra.configuration.winrm = winrm

SpecInfraの修正

以前書いた記事Serverspecで使うWinRMクライアントをInvoke-commandに置き換えてみたで修正した「lib\specinfra\backend\winrm.rb」以外に以下のファイルを修正します。

lib\specinfra\command\windows\base\file.rb
46c46,51
<       %Q!Get-Content("#{file}") | Out-String!
---
>       # MODIFIED
>       if Specinfra.configuration.file_encoding.nil?
>         %Q!Get-Content("#{file}") | Out-String!
>       else
>         %Q![Io.File]::ReadAllText("#{file}", [System.Text.Encoding]::GetEncoding("#{Specinfra.configuration.file_encod
ing}"))!
>       end
91c96,101
<         exec %Q!(Get-Content("#{file}") | Out-String) -match '#{convert_regexp(pattern)}'!
---
>         # MODIFIED
>         if Specinfra.configuration.file_encoding.nil?
>           exec %Q!(Get-Content("#{file}") | Out-String) -match '#{convert_regexp(pattern)}'!
>         else
>           exec %Q![Io.File]::ReadAllText("#{file}", [System.Text.Encoding]::GetEncoding("#{Specinfra.configuration.fil
e_encoding}")) -match "#{convert_regexp(pattern)}"!
>         end
100c110,115
<         exec %Q!(CropText -text (Get-Content("#{file}") | Out-String) -fromPattern '#{convert_regexp(from)}' -toPatter
n '#{convert_regexp(to)}') -match '#{pattern}'!
---
>         # MODIFIED
>         if Specinfra.configuration.file_encoding.nil?
>           exec %Q!(CropText -text (Get-Content("#{file}") | Out-String) -fromPattern '#{convert_regexp(from)}' -toPatt
ern '#{convert_regexp(to)}') -match '#{pattern}'!
>         else
>           exec %Q!(CropText -text ([Io.File]::ReadAllText("#{file}"), [System.Text.Encoding]::GetEncoding("#{Specinfra
.configuration.file_encoding}")) -fromPattern "#{convert_regexp(from)}" -toPattern "#{convert_regexp(to)}") -match "#{pa
ttern}"!
>         end
lib\specinfra\backend\powershell\script_helper.rb
40c40,44
<             script_text = script_text.encode('ASCII-8BIT')
---
>             if Specinfra.configuration.script_encoding.nil?
>               script_text = script_text.encode('ASCII-8BIT')
>             else
>               script_text = script.strip.encode('UTF-16LE', Specinfra.configuration.script_encoding)
>             end

Serverspecの修正

Serverspec側も少しだけ修正を加えていきます。

lib\serverspec.rb
61a62,80
>     # ADD
>     # rspec-core-3.4.1/lib/rspec/core/formatters/exception_presenter.rb:77:in `join': incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)
>     def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
>       lines = fully_formatted_lines(failure_number, colorizer)
>       # lines.join("\n") << "\n"
>       tmp = ""
>       lines.each {|line| tmp << "#{line}\n".encode('UTF-8')}
>       tmp
>     end
>   end
> end
>
> # ADD
> # rspec-core-3.4.1/lib/rspec/core/source.rb:23:in `split': invalid byte sequence in Windows-31J (ArgumentError)
> if defined?(RSpec::Core::Source)
>   class RSpec::Core::Source
>     def lines
>       @lines ||= source.scrub('?').split("\n")
>     end
lib\serverspec\type\command.rb
0a1
> require "kconv"     # ADD
4c5,8
<       command_result.stdout
---
>       # MODIFIED
>       # command_result.stdout
>       # Kconv.toutf8( "#{command_result.stdout}" ).encode("Windows-31J")
>       return_result(command_result.stdout)
8c12,15
<       command_result.stderr
---
>       # MODIFIED
>       # command_result.stderr
>       # Kconv.toutf8( "#{command_result.stderr}" ).encode("Windows-31J")
>       return_result(command_result.stderr)
18a26,35
>
>     # MODIFIED
>     def return_result(result)
>       if Specinfra.configuration.os_encoding.nil?
>         result
>       else
>         Kconv.toutf8( "#{result}" ).encode(Specinfra.configuration.os_encoding)
>       end
>     end
>
lib\serverspec\type\file.rb
1a2
> require "kconv"     # ADD
113c114,119
<       @content
---
>       # MODIFIED
>       if Specinfra.configuration.os_encoding.nil?
>         @content
>       else
>         Kconv.toutf8("#{@content}").encode(Specinfra.configuration.os_encoding)
>       end

テストの実行

それでは実行してみます。

PS C:\serverspec\sample> rake

##################################################
HOST: REMOTEHOST : development : winrm_client = 4
##################################################

Command "ipconfig /all | Select-String ノード"
  stdout
    should match /ハイブリッド/

File "C:\serverspec\test.cmd"
  should be file
  should contain "あいうえお"
  should not contain "かきくけこ"
  content
    should match /あいうえお/
  content
    should not match /かきくけこ/

Finished in 6.5 seconds (files took 1.29 seconds to load)
6 examples, 0 failures

PS C:\serverspec\sample>

かなり処理時間が短縮されました。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7