PowerShellからSSH経由でLinuxに接続する

管理人は会社で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は実行結果の文字数になります。

管理人はヘタレなので、これでしばらく悩みました…

コメントする