VirtualBoxをWindowsサービス化する (Windows 7)


VirtualBoxで作成した仮想マシンをWindowsのサービスとして動作させてみた。

  • 環境
    • OS: Windows 7 x64
    • PowerShell: PowerShell V3
    • VirtualBox: VirtualBox 4.3.26

  • 使用したソフト
    • WinSW: WinSW 1.16


はじめに

VirtualBoxをWindowsサービスとして動作させたかった。
Windowsサービスとして動作させたい要件は下記の通り。
  • 要件
    • ホストOSの起動時に仮想マシンも起動すること
    • ホストOSの停止時に仮想マシンも停止すること
    • ホストOSのスリープから復帰しても継続して仮想マシンを使えること
    • (可能な限り)他のソフトをインストールせずに使えること
      • 私はソフトをインストールするという行為が好きじゃないので。レジストリが汚れるのが嫌だ。

まずはじめに、Google先生から先輩諸氏のやり方を教えてもらった。

VBoxManage.exeをコマンド実行してみたところ、ホストOSのスリープでゲストOSが落ちてしまった。
スリープが使えるのは絶対条件なので、VBoxManage.exeを使うセンはないな。

VBoxVmServiceは良さそうなんだけど、インストールしないと使えないのが嫌だ。
クリーンなソフトを使いたい。
他に方法がなければ検討することにして後回し。

いろいろ調べてみるうちに、VirtualBox.exeの引数で.vboxファイルを渡してやると仮想マシンを起動してくれることが分かった。
GUIのコンソールも一緒に起動してしまうが、この方法で起動するとホストOSのスリープ時に自動でゲストOSを一時停止し、スリープ復帰時に再開してくれるようだ。
停止は上記のVBoxManage.exeでできる。

Windowsサービスとして動かしてしまえば、GUIコンソールは裏で動いていて見えないし、要件全部クリアじゃね?ってことで、任意のプログラムをサービス化する素晴らしいソフト WinSWを使うことにした。
このソフトなら、ローカルに置いた.exeをサービス登録するだけで使える。
使い方のチュートリアルは winsw - 任意のプログラムをWindowsのサービスに を参考にした。

起動・停止のスクリプト化

VirtualBox.exeは非同期に動作するプログラムのようで、コマンド実行するとすぐに制御が返ってきてしまう。
そのままではWinSWに登録できないので、起動~状態監視と、停止をスクリプトで書くことにした。
最近使い出したPowerShellで書いてみる。

起動スクリプト

  • Start-VM.ps1


################################################################################
# @file
# @author leo
# @brief 仮想マシンを起動する
# @details
# @param vmfile VMの構成ファイル (*.vbox)。パイプライン入力可。
# @param synchronousMode (オプション)同期モード。trueを指定すると同期モード。デフォルトはfalse。
# @param timeout (オプション)同期モードのときの監視を開始するまでのタイムアウト時間 [msec]。デフォルトは 60000 [msec]。
# @param monitorInterval (オプション)同期モードのときのVM起動状態を監視する間隔 [msec]。デフォルトは 1000 [msec]。
# @return なし
#
# @version 1.0.0 新規作成
################################################################################
param (
[parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]$vmfile,
[bool]$synchronousMode=$false,
[int]$timeout=60000,
[int]$monitorInterval=1000
)

begin {
pushd (Split-Path $MyInvocation.MyCommand.Definition -Parent)
echo "[$($MyInvocation.MyCommand.Name)] [start] $(Get-Date -Format "yyyy/MM/dd HH:mm:ss")"

function Get-RunningVM {
return (VBoxManage.exe list runningvms) -join "`n" |
Select-String -Pattern '"(?<name>.*?)"\s+?{(?<uuid>.*?)}' -AllMatches |
foreach { $_.Matches } |
foreach { [PSCustomObject]@{ name = $_.Groups["name"].Value; uuid = $_.Groups["uuid"].value } }
}
}

process {
# 名前を特定
if (!((VBoxManage.exe showvminfo "$vmfile" --machinereadable) -join "`n" -match 'name="(?<name>.*)"')) {
Write-Error "name not found."
return -1
}
$name = $matches["name"]

# UUIDを特定
if (!((VBoxManage.exe showvminfo "$vmfile" --machinereadable) -join "`n" -match 'UUID="(?<uuid>.*)"')) {
Write-Error "UUID not found."
return -1
}
$uuid = $matches["uuid"]

# 既に起動しているかどうかのチェック
if ((Get-RunningVM).uuid -contains $uuid) {
Write-Error "$name {$uuid} already running."
return -1
}

echo "Starting ... $name {$uuid}"
VirtualBox.exe "$vmfile"

# 起動待ち
[System.Diagnostics.Stopwatch]$timer = New-Object System.Diagnostics.Stopwatch
$timer.Start()
while ($synchronousMode) {
sleep -Milliseconds $monitorInterval
if ((Get-RunningVM).uuid -contains $uuid) {
$timer.Stop()
echo "$name {$uuid} started."
echo " starting time = $($timer.ElapsedMilliseconds) [msec]"
break
}

# タイムアウトの検出
if ($timeout -le $timer.ElapsedMilliseconds) {
$timer.Stop()
Write-Error "$name {$uuid} cannot start."
Write-Error " starting time = $($timer.ElapsedMilliseconds) [msec]"
return -1
}
}

# 監視開始
while ($synchronousMode) {
sleep -Milliseconds $monitorInterval
if (!((Get-RunningVM).uuid -contains $uuid)) {
echo "$name {$uuid} stopped."
break
}
}
}

end {
echo "[$($MyInvocation.MyCommand.Name)] [end] $(Get-Date -Format "yyyy/MM/dd HH:mm:ss")"
popd
}

停止スクリプト

  • Stop-VM.ps1


################################################################################
# @file
# @author leo
# @brief 仮想マシンを停止する
# @details
# @param vmfile VMの構成ファイル (*.vbox)。パイプライン入力可。
# @return なし
#
# @version 1.0.3 新規作成
################################################################################
param (
[parameter(Mandatory=$true, ValueFromPipeline=$true)]
[string]$vmfile
)

begin {
pushd (Split-Path $MyInvocation.MyCommand.Definition -Parent)
echo "[$($MyInvocation.MyCommand.Name)] [start] $(Get-Date -Format "yyyy/MM/dd HH:mm:ss")"
}

process {
# UUIDを特定
if (!((VBoxManage.exe showvminfo "$vmfile" --machinereadable) -join "`n" -match 'UUID="(?<uuid>.*)"')) {
Write-Error "UUID not found."
return -1
}
$uuid = $matches["uuid"]

$result = (VBoxManage.exe controlvm "$uuid" acpipowerbutton)
}

end {
echo "[$($MyInvocation.MyCommand.Name)] [end] $(Get-Date -Format "yyyy/MM/dd HH:mm:ss")"
popd
}

WinSWの設定ファイル

今回は、"vm-test"とう名前の仮想マシンをサービス化してみる。
  • vm-test.xml


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<service>
<id>vm-test</id>
<name>vm-test</name>
<description>仮想マシン</description>
<startmode>Automatic</startmode>
<depend></depend>
<serviceaccount>
<domain>.</domain>
<user>username</user>
<password>password</password>
<allowservicelogon>true</allowservicelogon>
</serviceaccount>
<executable>powershell</executable>
<startargument>-NoLogo</startargument>
<startargument>-ExecutionPolicy</startargument>
<startargument>RemoteSigned</startargument>
<startargument>Start-VM.ps1</startargument>
<startargument>F:\VirtualMachines\test\test.vbox</startargument>
<startargument>$true</startargument>
<stopexecutable>powershell</stopexecutable>
<stopargument>-NoLogo</stopargument>
<stopargument>-ExecutionPolicy</stopargument>
<stopargument>RemoteSigned</stopargument>
<stopargument>Stop-VM.ps1</stopargument>
<stopargument>F:\VirtualMachines\test\test.vbox</stopargument>
<logpath>log\vm</logpath>
<log mode="roll-by-time">
<pattern>yyyyMMdd</pattern>
</log>
</service>

サービス化

あとは、vm-test.xmlと同じディレクトリに
  • vm-test.exe (WinSW.exeのコピー)
  • Start-VM.ps1
  • Stop-VM.ps1
を配置して
vm-test.exe install
でサービスインストール。

起動、停止、スリープからの復帰も問題なくできる。

最終更新:2018年08月17日 23:45