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

素敵なおひげですね

PowerShellを中心に気分で書いているブログです。

Vagrant 1.8.0で追加されたvagrant powershellコマンドについて

Vagrant Windows PowerShell

先日Vagrant 1.8.0がリリースされたのでサクッと手元の環境を更新しました。

本エントリではVagrant 1.8.0で追加された新機能のうち、vagrant powershellコマンドについて詳しく触れていきたいと思います。

Vagrant 1.8.0の新機能について

Vagrant 1.8 - HashiCorpで公式にアナウンスされています。
チェンジログここです。

結構な数のバグフィックスといくつかの機能改善、機能追加がなされています。
機能追加は、

  • vagrant powershellコマンドの追加
  • vagrant portコマンドの追加
  • vagrant snapshotコマンドの追加
  • ansible_localプロビジョナーの追加
  • Linked Cloneサポートの追加(VirtualBox/VMware)
  • IPV6ネットワークサポートの追加(VirtualBox/VMware)

がされているとの事です。
vagrant snapshotコマンドもかなり気になりますが、今回はvagrant powershellコマンドに絞った話をします。

Vagrant 1.8.0のインストール

インストール手順はこれまでのバージョンと特に変わりませんでした。
Windowsであれば、

stknohg.hatenablog.jp

のエントリの内容そのままの手順で問題ないと思います。
本バージョンからVagrantのインストール時にVirtualBoxの自動インストール*1もサポートされたそうなのですが今回は試せませんでした。

ちなみに今回の環境は、

になります。

既知の問題について

stknohg.hatenablog.jp

のエントリで書いたネットワークアダプタ名が日本語名称("イーサネット"等)の場合にIPアドレスに失敗する件については本バージョンで解決した様です。
パッチを当てなくても問題なくIPアドレスの設定ができました。

vagrant up --debugコマンドでデバックログを出力してみると

# デバッグログを一部抜粋

# 日本語ネットワークアダプタ名がUnicodeエスケープされた形で渡されている。
#   "\u30A4\u30FC\u30B5\u30CD\u30C3\u30C8" => イーサネット
DEBUG configure_networks: nic: {:index=>10, :mac_address=>"08:00:27:86:80:4E", :net_connection_id=>"\u30A4\u30FC\u30B5\u30CD\u30C3\u30C8", :interface_index=>12}
DEBUG configure_networks: nic: {:index=>13, :mac_address=>"08:00:27:1B:D1:29", :net_connection_id=>"\u30A4\u30FC\u30B5\u30CD\u30C3\u30C8 2", :interface_index=>15}
DEBUG configure_networks: vm_interface_map: {1=>{:net_connection_id=>"\u30A4\u30FC\u30B5\u30CD\u30C3\u30C8", :mac_address=>"08002786804E", :interface_index=>12, :index=>10}, 2=>{:net_connection_id=>"\u30A4\u30FC\u30B5\u30CD\u30C3\u30C8 2",:mac_address=>"0800271BD129", :interface_index=>15, :index=>13}}

 INFO guestnetwork: Configuring NIC イーサネット 2 using static ip 192.168.1.100

# netsh interface ipコマンドに日本語ネットワークアダプタ名(イーサネット 2)が文字化けせず渡されて実行されている。
DEBUG winrmshell: powershell executing:
$ProgressPreference='SilentlyContinue';
netsh interface ip set address "イーサネット 2" static 192.168.1.100 255.255.255.0
if ($?) { exit 0 } else { if($LASTEXITCODE) { exit $LASTEXITCODE } else { exit 1 } }

な感じのログが出力され、文字化けすることなくnetsh interface ipコマンドが実行されてIPアドレスが設定されているのがわかります。

vagrant powershellコマンドの基本

ここから本題に入ります。

コマンドの基本

vagrant powershellコマンドは一言でいってしまうとvagrant sshコマンドのWindows版です。
ホストからPSRemotingでWindowsゲストに接続することができます。

公式なドキュメントはこちらになります。

コマンドのヘルプを見てみると以下の様な感じです。

PS C:\> vagrant powershell --help
Usage: vagrant powershell [-- extra powershell args]

Options:

    -c, --command COMMAND            Execute a powershell command directly
    -h, --help                       Print this help

vagrant powershell [VM名(複数VMある場合)]が基本で、単一のコマンドを実行させるだけの場合は--commandオプションを指定します。
[-- extra powershell args]の部分はこのコマンドで起動するホスト側のpowershell.exeに追加のオプションを指定する場合に使います。

コマンド実行例

適当なWindowsゲストVMを作成しvagrant powershellを実行すると以下の図の様になりゲストVM(127.0.0.1)にvagrantユーザーで接続していることがわかります。

f:id:stknohg:20151222185036p:plain

--commandオプションを指定した場合はこんな感じで指定されたコマンドを実行して直ちに終了しているのがわかります。

f:id:stknohg:20151222195401p:plain

ゲストのVagrantfileは以下。特に特別な設定は必要ありません。

# -*- mode: ruby -*-
# vi: set ft=ruby :

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure(2) do |config|
  config.vm.box = "Win2012R2"
  config.vm.guest = :windows
  config.vm.communicator = "winrm"
  #
  config.vm.network "private_network", ip: "192.168.1.100"
  #
  config.vm.provider "virtualbox" do |vb|
    # Display the VirtualBox GUI when booting the machine
    vb.gui = true
    # Customize the amount of memory on the VM:
    vb.memory = "4096"
    vb.customize ["modifyvm", :id, "--cpus", "2"]
    vb.customize ["modifyvm", :id, "--paravirtprovider", "hyperv"]
  end
end

vagrant powershellコマンドの詳細

コマンドの実体は[インストール先]\embedded\gems\gems\vagrant-1.8.0\plugins\commands\powershell\command.rbになります。

--commandオプションを指定した場合

--commandオプションを指定した場合は、WinRM Communicator([インストール先]\embedded\gems\gems\vagrant-1.8.0\plugins\communicators\winrm\communicator.rb)の機能を直接使って単一のコマンドを実行します。

まだ未検証なのですが、WinRMの通信のみを行うので、--commandオプションを使う場合は非Windowsホストでも実行可能だと思います。


【2015/12/23追記】
Mac OS X(Yosemite)にVagrant 1.8.0をインストールしてvagrant powershell --command "ほげ"なコマンドを試してみましたが以下のエラーメッセージが出てしまい実行できませんでした。

f:id:stknohg:20151223043939p:plain

vagrant powershellコマンドはWindowsホスト専用でした。

ただ、command.rbファイルの41行目でコマンドの実行ホストをチェックしている部分をコメントアウトしてしまうと、

# command.rb 
# 41行目の以下の部分をコメントアウトして実行ホストのチェックをしない様にしてしまう

# Check if the host even supports ps remoting
raise Errors::HostUnsupported if !@env.host.capability?(:ps_client)
    ↓
# Check if the host even supports ps remoting
#raise Errors::HostUnsupported if !@env.host.capability?(:ps_client)

以下の図の様にvagrant powershell --command "ほげ"なコマンドを無理やり実行することはできますw
当然オススメはできませんが自己責任でやるのはアリかもしれません。

f:id:stknohg:20151223043636p:plain

また、Vagrantで使っているWinRMライブラリロケールen-US、コードページが65001(UTF8)で固定*2されているため、--commandオプションを指定した場合はシェルの言語が英語になります。

このため日本語の取り扱いに難があるっぽく、コマンドの実行結果に日本語が混じると即エラーとなったりしました...

【追記ここまで】


--commandオプションを指定しない場合

--commandオプションを指定しない場合、ゲストVMに対してEnter-PSSesssionコマンドレットを使いリモート接続する流れとなります。
リモート接続の前にPSRemotingが有効か否かのチェックなどを行います。

こちらはホストからpowershell.exeを別途起動するため、非Windowsホストでは実行できません。


【2015/12/23追記】
例えばMacから実行した場合、--commandオプションを指定した場合と同じエラーになります。

f:id:stknohg:20151223125903p:plain

【追記ここまで】


最初に[インストール先]\embedded\gems\gems\vagrant-1.8.0\plugins\commands\powershell\scripts\enable_psremoting.ps1を呼び出し、ここでゲストVMに対してPSRemotingが有効になっているかのチェックとホスト側のTrustedHostの設定変更*3を行っています。

ゲストにPSRemotingで接続できる様になったのを確認した後で、[インストール先]\embedded\gems\gems\vagrant-1.8.0\plugins\hosts\windows\cap\ps.rbを呼び出し、Enter-PSSesssionを呼ぶスクリプトブロックを組み立て、powershell.exeを-NoProfile -ExecutingPolicy Bypass -NoExitで呼び出しています。

ps.rbから該当する部分を抜粋すると以下になります。

# ps.rbより抜粋

# powershell.exeで実行するコマンド(BASE64エンコードされる)
command = <<-EOS
  $plain_password = "#{ps_info[:password]}"
  $username = "#{ps_info[:username]}"
  $port = "#{ps_info[:port]}"
  $hostname = "#{ps_info[:host]}"
  $password = ConvertTo-SecureString $plain_password -asplaintext -force
  $creds = New-Object System.Management.Automation.PSCredential ("$hostname\\$username", $password)
  function prompt { kill $PID }
  Enter-PSSession -ComputerName $hostname -Credential $creds -Port $port
EOS

# powershell.exe呼び出し
args = ["-NoProfile"]
args << "-ExecutionPolicy"
args << "Bypass"
args << "-NoExit"
args << "-EncodedCommand"
args << ::WinRM::PowershellScript.new(command).encoded
if ps_info[:extra_args]
  args << ps_info[:extra_args]
end

# Launch it
Vagrant::Util::Subprocess.execute("powershell", *args)

これをPowerShellのコードに直すとこんな感じになります。(あくまで疑似コードです)

# powershell.exeで実行するコマンド(BASE64エンコードされる)
$Command = {
    $plain_password = "平文パスワード"
    $username = "接続ユーザー名(vagrant)"
    $port = "接続ポート"
    $hostname = "接続先ホスト(127.0.0.1)"
    $password = ConvertTo-SecureString $plain_password -asplaintext -force
    $creds = New-Object System.Management.Automation.PSCredential ("$hostname\\$username", $password)
    function prompt { kill $PID }
    Enter-PSSession -ComputerName $hostname -Credential $creds -Port $port
}.ToString()
$EncodedCommand = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($Command))

# powershell.exe呼び出し
powershell.exe -NoProfile -ExecutingPolicy Bypass -NoExit -EncodedCommand $EncodedCommand

接続先がゲストVMなだけでよくあるEnter-PSSessionの呼び出しです。

ちょっと面白いのがfunction prompt { kill $PID }の部分で、Enter-PSSessionが終了した次のプロンプト呼び出し時点で自分自身を殺してpowershell.exeを終了させているところでしょうか。
苦労のあとが見られます。

とりあえずこんなところです。
まだ軽く使った程度ですが、かなり便利で良い感じです。

*1:WindowsOSXのみ

*2:ちなみに過去のWinRMライブラリではコードページは437で固定されていました

*3:TrustedHostにゲストを追加