管理人は会社で200+のVM(90%以上はWindows or Linux)が動作する環境を管理していますが、頻繁にVMのScrap & Buildが発生します。BuildしたらそのVMが通信”できるべき”相手との疎通確認を実施するべきなのですが、それが非常に面倒くさい…
PowerShellといえばWindows限定というイメージがありますが、実はSSH経由でLinuxに接続することも可能です。LinuxはSSH接続さえできれば大抵のタスクが実行できるので、うまくやればWindowsとLinux共通のタスクを単一のスクリプトで実施することさえできます。元々VM自体の自動展開スクリプトをPowerShellで書いていたので、運用環境をPowerShellに統一すべく今回挑戦してみました。
[事前作業] PowerShell 4.0に更新する
今回のクライアント環境(接続元)はWindows 7 SP1ですが、PowerShellのバージョンは以下の通りでした。プロンプトが普通のPowerShellとは異なりますが、これは管理人の環境にVMwareが公開しているPowerCLIが導入されているためです。
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> $Host.version
Major Minor Build Revision
----- ----- ----- --------
2 0 -1 -1
Windows 7のデフォルトのPowerShellのバージョンであるところのVer.2.0でした。今回作成するスクリプトはWindows Server 2012 = PowerShell Ver. 4.0でも動かす予定なので、念のためバージョンを合わせることにします。
PowerShell 4.0はWindows Management Framework 4.0という管理パッケージに同梱されているので、その導入を行います。.NET Framework 4.5が導入の前提条件となっているので、未導入でしたらこちらを先に導入しましょう。
Windows Management Framework 4.0を導入すると、PowerShellのバージョンが4.0に上がっているはずです。
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> $Host.version
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
PowerShellからSSHを利用するためのモジュールの追加
当然ではありますが、デフォルトの状態ではPowerShell(というかWindows)はSSHを話すことができません。PowerShellでSSHを公式にサポートするなんて話も聞こえてきますが、まだ開発初期段階とのことなので現時点ではなんとも言えません。ここでは、PowerShellからSSHしたい@AWS情報ブログを大いに参考にしながら、とりあえず接続ができるところまで作ってみます。
環境構築は、上記ブログの記事そのままで基本的にできるはずです。管理人のモジュールパスは以下の通りで、
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> $env:PSModulePath
C:\Users\starplatinum\Documents\WindowsPowerShell\Modules;C:\Program Files (x86)\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules\
かつC:\Program Files (x86)\WindowsPowerShell\Modulesが存在していたので(空でしたが)、そのディレクトリにダウンロード・解凍したSSH-SessionsPSv3.zipを置いただけで、PowerShellから認識されました。
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> Get-Module -ListAvailable
ModuleType Name ExportedCommands
---------- ---- ----------------
Script SSH-Sessions {Remove-SshSession, New-SshSession, Enter-SshSession, Invoke-SshCommand...}
Manifest BitsTransfer {Complete-BitsTransfer, Add-BitsFile, Remove-BitsTransfer, Suspend-BitsT...
Manifest CimCmdlets {New-CimSessionOption, Get-CimAssociatedInstance, Export-BinaryMiLog, Ne...
Script ISE {Import-IseSnippet, Get-IseSnippet, New-IseSnippet}
Manifest Microsoft.PowerShell.D... {New-WinEvent, Export-Counter, Get-WinEvent, Get-Counter...}
Manifest Microsoft.PowerShell.Host {Start-Transcript, Stop-Transcript}
Manifest Microsoft.PowerShell.M... {Remove-WmiObject, Remove-EventLog, Add-Computer, Set-Location...}
Manifest Microsoft.PowerShell.S... {Get-Credential, Get-Acl, Set-AuthenticodeSignature, Get-AuthenticodeSig...
Manifest Microsoft.PowerShell.U...
Manifest Microsoft.WSMan.Manage... {Test-WSMan, Set-WSManInstance, Get-WSManInstance, Invoke-WSManAction...}
Manifest PSDiagnostics {Set-LogProperties, Enable-WSManTrace, Stop-Trace, Disable-WSManTrace...}
Binary PSScheduledJob {Get-JobTrigger, Add-JobTrigger, Get-ScheduledJobOption, Get-ScheduledJo...
Manifest TroubleshootingPack {Get-TroubleshootingPack, Invoke-TroubleshootingPack}
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> Import-Module SSH-Sessions
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> Get-Module
ModuleType Name ExportedCommands
---------- ---- ----------------
Script SSH-Sessions {Remove-SshSession, New-SshSession, Enter-SshSession, Invoke-SshCommand...}
Linuxホストにpingを打たせてその結果を取得する
サンプルとして、IP reachableなLinuxホストに接続してpingを実行させ、その結果を取得してみます。多少冗長ですが書いてみるとこんな感じになりました。
# 第一引数(-shost) : pingを実行するLinuxホストのIPアドレス/FQDN
# 第二引数(-dhost) : pingの宛先となるIPアドレス/FQDN
# 引数を受け取る
Param(
[parameter(mandatory=$true)][string]$shost,
[parameter(mandatory=$true)][array]$dhost
)
# セッションを生成してコマンドを送信する
$SshSession = New-SshSession -ComputerName $shost -Username xxxxx -Password yyyyy
#実行結果を行単位で分割して$Returnに配列として格納する
$Return = (Invoke-SshCommand -ComputerName $shost -Command "ping -c 4 $dhost" -Quiet) -split "`n"
# コマンド実行結果の行の出力内容を見て実行結果を判断・出力する
Foreach ($i in $Return){
if ($i -match "(?<sent>[\d])\spackets\stransmitted.*(?<recv>[\d])\sreceived.*$") {
if ($matches.sent -eq $matches.recv) {
"All sent packets are successfully received (OK!)."
} elseif ($matches.recv -ne 0) {
"Some sent packet(s) couldn't be received (Unstable network?)."
} elseif ($matches.recv -eq 0) {
"Received no response. (Incorrect target IP? or Packet is filtered?)."
}
} elseif ($i -match ".*unknown\shost.*$") {
"Failed to find host (DNS lookup failed?)."
}
}
# セッションを終了する
Remove-SshSession $shost
たとえばこれをExecute-LinuxPing_single.ps1という名前で保存した場合、
PowerCLI C:\Program Files (x86)\VMware\Infrastructure\vSphere PowerCLI> (path_to_command)\Execute-LinuxPing_single.ps1 -shost 192.168.1.1 -dhost 192.168.1.2
All sent packets are successfully received (OK!).
192.168.1.1 should now be disconnected and disposed.
こんな感じの出力を得られるはずです。最後の条件分岐の部分は、リモートのLinuxの返し方に応じた修正が必要になります(今回の宛先はUbuntu Workstation 12.0.4 LTS)。
ちなみに、上の例では出力結果を受け取る時点で改行文字(PowerShellでは”¥”ではなく”`”でエスケープする)でsplitして$Resultに行単位で格納していますが、配布サイトには接続先ホストが単一の場合は実行結果はSystem.Stringで返されると書かれています。これは厳密に言うと、オブジェクト型の配列にString型で実行結果の文字列が格納されるということのようです。仮にsplitをしないで単純に$RawReturnに実行結果を代入したとしたら、$RawReturn.Countは1に、$RawReturn[0].Countは実行結果の文字数になります。
管理人はヘタレなので、これでしばらく悩みました…