mipmip/ievms-ruby

View on GitHub
lib/ievms/windows_guest.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require "ievms/version"
require 'open3'
require 'fileutils'
require 'tempfile'
require 'timeout'

# VM admin username
USERNAME = 'IEUser'
# VM admin user password
PASSWD = 'Passw0rd!'

# IEVMS directory
IEVMS_HOME = ENV['HOME']+'/.ievms'

# ievms interface
module Ievms
  class WindowsGuest
    attr_accessor :verbose
    attr_accessor :headless
    attr_accessor :timeout_secs

    def initialize(vbox_name)
      @vbox_name = vbox_name

      check_virtualbox_version
      is_vm?

      @verbose = false
      @headless = true
      @timeout_secs = 600 #default timeout for a single task 5 minutes
    end

    # copy file from guest (win) machine to host machine
    def download_file_from_guest(guest_path, local_path, quiet=false)

      log_stdout "Copying #{guest_path} to #{local_path}", quiet
      # 1 run cp command in machine
      guestcontrol_exec "cmd.exe", "cmd.exe /c copy \"#{guest_path}\" \"E:\\#{File.basename(local_path)}\""

      # 2 copy to tmp location in .ievms
      FileUtils.cp File.join(IEVMS_HOME,File.basename(local_path)), local_path

      # 3 remove tmp file in .ievms
      FileUtils.rm File.join(IEVMS_HOME,File.basename(local_path))
    end

    # Upload a local file to the windows guest
    def upload_file_to_guest(local_path, guest_path, quiet=false)

      # 1 copy to tmp location in .ievms
      FileUtils.cp local_path, File.join(IEVMS_HOME,File.basename(local_path))

      # 2 run cp command in machine
      log_stdout "Copying #{local_path} to #{guest_path}", quiet
      guestcontrol_exec "cmd.exe", "cmd.exe /c copy \"E:\\#{File.basename(local_path)}\" \"#{guest_path}\""

      # 3 remove tmp file in .ievms
      FileUtils.rm File.join(IEVMS_HOME,File.basename(local_path))
    end

    # return string from remote file
    def download_string_from_file_to_guest( guest_path, quiet=false)
      log_stdout "Copying #{guest_path} to tempfile.txt", quiet
      guestcontrol_exec "cmd.exe", "cmd.exe /c copy \"#{guest_path}\" \"E:\\tmpfile.txt\""

      tmpfile = File.join(IEVMS_HOME,'tmpfile.txt')
      if File.exists? tmpfile
        string = IO.read(tmpfile)
        FileUtils.rm tmpfile
      else
        string = ''
      end
      string
    end

    # make remote file from string
    def upload_string_as_file_to_guest(string, guest_path, quiet=false)
      tmp = Tempfile.new('txtfile')
      tmp.write "#{string}\n"
      path = tmp.path
      tmp.rewind
      tmp.close

      upload_file_to_guest(path, guest_path, true)
      FileUtils.rm path
    end

    # Upload a local file to the windows guest as Administator
    def upload_file_to_guest_as_admin(local_path, guest_path, quiet=false)

      log_stdout "Copying #{local_path} to #{guest_path} as Administrator", quiet

      upload_file_to_guest(local_path, 'C:\Users\IEUser\.tempadminfile',true)
      run_command_as_admin('copy C:\Users\IEUser\.tempadminfile '+ guest_path,true)
      run_command 'del C:\Users\IEUser\.tempadminfile', true
    end

    # execute existing batch file in Windows guest as Administrator
    def run_command_as_admin(command,quiet=false, dont_wait=false)
      log_stdout "Executing command as administrator: #{command}", quiet

      run_command 'if exist C:\Users\IEUser\ievms.bat del C:\Users\IEUser\ievms.bat && Exit', true
      upload_string_as_file_to_guest('C:\Users\IEUser\ievms_cmd.bat > C:\Users\IEUser\ievms.txt', 'C:\Users\IEUser\ievms.bat', true)

      run_command 'if exist C:\Users\IEUser\ievms.bat del C:\Users\IEUser\ievms_cmd.bat && Exit', true
      upload_string_as_file_to_guest(command, 'C:\Users\IEUser\ievms_cmd.bat', true)

      if(dont_wait)
        guestcontrol_exec "schtasks.exe", "schtasks.exe /run /tn ievms"
        return
      end

      _ = Timeout::timeout(@timeout_secs) {

        guestcontrol_exec "schtasks.exe", "schtasks.exe /run /tn ievms"

        unless quiet
          print "..."
          while schtasks_query_ievms.include? 'Running'
            print "."
            sleep 1
          end
          print "\n"
        end
      }

      run_command 'if exist C:\Users\IEUser\ievms.bat del C:\Users\IEUser\ievms_cmd.bat && Exit', true
      print download_string_from_file_to_guest 'c:\Users\IEUser\ievms.txt' unless quiet
      download_string_from_file_to_guest 'c:\Users\IEUser\ievms.txt', true
    end

    # execute existing batch file in Windows guest
    def run_command(command, quiet=false)
      log_stdout "Executing command: #{command}", quiet
      out, _, _ = guestcontrol_exec "cmd.exe", "cmd.exe /c \"#{command}\""
      out
    end


    #def run_powershell_cmd(command, quiet=false)
    #  run_command '@powershell -NoProfile -Command "' + command +'"', quiet
    #end

    def run_powershell_cmd_as_admin(command, quiet=false)
      run_command_as_admin  '@powershell -NoProfile -ExecutionPolicy unrestricted -Command "' + command + '"', quiet
    end

    # shutdown windows machine
    def shutdown(quiet=false)
      log_stdout "Trying to shutdown ...", false if @verbose
      if powered_off?
        log_stdout "Already powered off.", false
      else
        log_stdout "shutting down ...", quiet
#        run_command_as_admin "shutdown.exe /s /f /t 0", quiet
        run_command "shutdown.exe /s /f /t 0", quiet
        wait_for_shutdown
      end
    end

    # start windows, finish as soon as boot is complete
    def start(quiet=false)
      if powered_off?
        log_stdout "starting ...", quiet

        if(@headless)
          type = 'headless'
        else
          type = 'gui'
        end
        `VBoxManage startvm "#{@vbox_name}" --type #{type}`
        wait_for_guestcontrol
      else
        log_stdout "Already started ...", quiet
      end
    end

    # reboot windows machine
    def reboot(quiet=false)
      log_stdout "rebooting...", quiet
      shutdown(true)
      start(true)
    end

    def restore_clean_snapshot(quiet=false)
      log_stdout "Restoring Clean Snapshot ...", quiet
      shutdown
      cmd = "VBoxManage snapshot \"#{@vbox_name}\" restore clean"
      log_stdout cmd, false if @verbose
      stdout,_,_ = Open3.capture3(cmd)
      print stdout
    end

    # show status of administrative ievms task.
    def schtasks_query_ievms
      out, _, _ = guestcontrol_exec "schtasks.exe", "schtasks.exe /query /tn ievms"
      out
    end

    # install choco package(s)
    def choco_install(pkg, quiet=false)
      if ! chocolatey?
        log_stdout "First time.. installing Chocolatey first", quiet
        install_chocolatey
      end

      log_stdout "Installing with choco: #{pkg} \n", quiet
      run_powershell_cmd_as_admin("choco install -y #{pkg}", false)
    end

    # uninstall package(s)
    def choco_uninstall(pkg,quiet=false)
      if chocolatey?
        log_stdout "Uninstalling with choco: #{pkg} \n", false
        run_powershell_cmd_as_admin("cuninst -y #{pkg}", false)
      else
        log_stdout "Chocolatey is not installed, guess you don't need to uninstall anything.", quiet
      end
    end

    # is chocolatey installed
    def chocolatey?
      out = run_command_as_admin('@powershell -Command "choco"',false)
      if out.include?("CommandNotFoundException")
        return false
      else
        return true
      end
    end

    # install the Chocolatey Package Manager for Windows
    def install_chocolatey(quiet=false)
      log_stdout "Installing Chocolatey", quiet
      run_command_as_admin('@powershell -NoProfile -ExecutionPolicy unrestricted -Command "(iex ((new-object net.webclient).DownloadString(\'https://chocolatey.org/install.ps1\'))) >$null 2>&1" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin')
      reboot
    end

    # end the administrative task
    def end_ievms_task(quiet=true)
      run_command('schtasks.exe /End /TN ievms', quiet)
    end

    # true when machine powered off
    def powered_off?
      cmd = "VBoxManage showvminfo \"#{@vbox_name}\" | grep \"State:\""
      print cmd if @verbose
      out = `#{cmd}`
      print out if @verbose
      if out.include?('powered off') || out.include?('aborted')
        return true
      end
    end

    # return true when run level is 3 boot complete
    def boot_complete?
      out = `VBoxManage showvminfo "#{@vbox_name}" | grep 'Additions run level:'`
      print out if @verbose
      if out[-2..-2].to_i == 3
        return true
      end
    end

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

    private

    def log_stdout(msg, quiet=true)
      quiet = false if @verbose
      print "[#{@vbox_name}] #{msg}\n" unless quiet
    end

    # execute final guest control shell cmd
    # returns [stdout,stderr,status] from capture3
    def guestcontrol_exec(image, cmd)

      _ = Timeout::timeout(@timeout_secs) {
        wait_for_guestcontrol
        cmd = "VBoxManage guestcontrol \"#{@vbox_name}\" run --username \"#{USERNAME}\" --password '#{PASSWD}' --exe \"#{image}\" -- #{cmd}"

        log_stdout cmd, false if @verbose

        return Open3.capture3(cmd)
      }

    end

    # Pause execution until guest control is available for a virtual machine
    def wait_for_guestcontrol
      until boot_complete? do
        print "Waiting for #{@vbox_name} to be available for guestcontrol...\n" if @verbose
        sleep 3
      end
    end

    # Pause execution until guest machine has shut down
    def wait_for_shutdown
      until powered_off? do
        print "Waiting for #{@vbox_name} to be finished shutdown...\n" if @verbose
        sleep 3
      end
    end

    # raise when version is not compatible
    def check_virtualbox_version
      if Gem::Version.new(`VBoxManage -v`.strip.split('r')[0]) < Gem::Version.new('5.0.6')
        raise "VirtualBox >= 5.0.6 is not installed"
      end
    end

    # raise when name is not a virtual machine in VirtualBox
    def is_vm?
      cmd = "VBoxManage showvminfo \"#{@vbox_name}\""
      _, stderr, _ = Open3.capture3(cmd)
      if stderr.include? 'Could not find a registered machine named'
        raise "Virtual Machine #{@vbox_name} does not exist"
      end
    end


  end
end