tech.guitarrapc.cóm

Technical updates

PowerShell で Chocolatey を利用して SSH 接続をする + Capistrano コマンドを Windows から発行/実行する

はじめに言っておきます。相当環境特化しているのでマニアックです。需要ないかと思います。

が、Windows でも Linux コマンドを PowerShell から実行できるんだ!

Capistarno実行に 一々 Linux へログインしなくていいゆとりができるんだ記事です。 それでもよければどうぞ。(海外では以外と同じようなことしてますけどね)

Windows に純正の SSH 接続が出来るモジュール・・・・・PowerShell では出来ないのです。

しかし、chocolatey パッケージ管理モジュールをインストールして、パッケージとして msysgit をインストールすることで Git が利用できます。 git パスには sshがあるので、これを利用すればPowerShell コンソール上で SSH が出来るわけです。

所謂 PoshGit も同様の仕組みな訳ですが、それでは面白くありません。 今回は、以下の3点を行ってみたいと思います。

  • Windows PowerShell に chocolatey をインストールし msysgit をパッケージインストールし ssh を入手する
  • Windows PowerShell から SSH 経由で Linux にコマンドを飛ばして結果を取得する
  • Windows PowerShell から SSH 経由で Capistrano がインストールされたLinux サーバーに Cap コマンドを送信し、結果を取得する

要は何をしたいか? ワンポチ Windows から Linux Deploy です。 Linux 大好きですが、Windows からバッチ一発で実行できる方が嬉しいですよねー。ということです。 ====

全体像

少し長いので先に全体像を。 利用者が実際に利用するのは 2つだけです。

  • Invoke-SshCommand
  • Invoke-CapistranoDeploy

コード

Git-Hub で公開しておきます。

https://github.com/guitarrapc/PowerShellUtil/tree/master/PS-SshConnection

モジュール設置

モジュールに同梱された install.bat を実行すれば module path にモジュールがコピーされます。 後は PowerShell で利用できるはずです。 自分で好きなパスに起きたい場合は、各セッションでそのパスに移動して Import-Module PS-SshConnection でモジュールをインポートしてください。

chocolatey や ssh のインストール

明示的なインストールをする必要はありません。 各ssh接続時に、 自動的にchocolatey と msygit パッケージをインストールします。

Invoke-SshCommand

本コマンドで、 ssh コマンドを対象のサーバーに送信します。 利用する際のサンプルフォーマットです。

Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド"

# Optionがある場合は 2つまで設定可能
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション

# Optionがある場合は 2つまで設定可能
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション -option2 sshコマンドオプション2

利用する際は、例えばこのようにします。

 Invoke-SshCommand -rsaKey c:\ssh\rsa -user lunuxuser -hostip 192.168.1.1 -command "hostname"

実行するとこのようなログと結果が出力されます。 (最終行の Linux-ServerName が結果。他は write-host コマンドレットで表示のみなので、変数で受けるとコマンド実行結果の Linux-ServerName のみ格納されます。)

Adding Git path for PowerShell Command. git path "C:\Program Files (x86)\Git\bin\" had been added to PATH. Adding UserProfilepath for ssh-Keygen. UserProfile path had already been added to HOME. nothing will do. Linux-ServerName

簡単ですね。

余談 なお、コマンドレットを解釈して実際実行されているのは、以下の ssh コマンドです。 (めんどうなので限定していますが、広げれば各種 ssh コマンドが実行可能です。)
# オプションが2つ指定されている場合
ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command

# オプションが1つ指定されている場合
ssh -i $rsaKey $user@$hostip -o $option -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command

# オプションがない場合
ssh -i $rsaKey $user@$hostip -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=quiet $command
上記のコマンドの うちハードコードされた3つ
StrictHostKeyChecking=no
UserKnownHostsFile=/dev/null
LogLevel=quiet
と ssh -i は、known_host にないホストへの接続で コマンド実行がとまることを回避するためのオプションです。 これがないと ssh できないので大事です。

Invoke-CapistranoDeploy

本コマンドで、 ssh コマンド経由で、 対象のCapistranoサーバーへ cap コマンドを送信します。 つまり Windows から Capistrano Server に cap コマンドを送って deploy できます。

利用する際のサンプルフォーマットです。

Invoke-CapistranoDeploy `
        -deploygroup $deploygroup `
        -captask "deploy" `
        -deploypath CapistranoFullPath `
        -rsakey RSAKeyForSSH `
        -user SSHUser `
        -hostip IpAddress

実行するとCapistranoパスに移動して、 capコマンドが送信されます。 実行されるのは、このようなフォーマットの cap コマンドです。

cap デプロイグループ デプロイタスク

続いて実行経過と結果が出力されます。(通常文は白字) failed など capistrano のエラーキーワードを赤字ハイライトしているので、 エラーが起こった場合も分かります。

前提 capistrano での ssh deploy において、 bash_profile で ssh-add key と ssh-agent などで rsaキーを読むようにしています。 Private管理を徹底して外部接続ができないからやっていますが、実際にやるときにセキュリティに気を付けてください。 なお、 ssh 実行中に ssh-agent ~/.bashrc などをすると bash 実行空間が変わるため 送信した sshコマンドが実行できなくなります。ご注意ください。
自分サーバの構築その14:ssh-agentでノンパスワードを実現

以下は余談となります。 興味のある方のみどうぞ。

Windows PowerShell に chocolatey をインストールし msysgit をパッケージインストールし ssh を入手する

まずは、 chocolatey と msysgit をインストールする部分を見てみましょう。 とは言っても、インストーラーを明示的に実行することはありません。 ssh コマンドの実行時にインストール状況を確認し、なければ自動的にインストールします。

Chocolatey とは

そもそも Chocolatey とは何でしょうか?

Chocolatey Gallery

簡単にいうと、 Windows PowerShell を利用した apt-get あるいは yum とでもいうべきものです。

Nuget をベース にしており、 Chocolatey Gallery | Packages に公開されている膨大な数のパッケージを コマンド経由でインストールできます。

勿論パッケージ管理システムなので インストール時の依存関係や更新、アンインストールも管理できます。 Windows 8 x64やWindows Server 2012 x64 でも動作が確認済みで、今後 Nuget と共に主流になる可能性を秘めています。

Windowsアプリをコマンド一発で導入できるパッケージ管理システム「Chocolatey」

Chocolatey のインストール

以下のコマンドを PowerShell 上で実行することでインストール可能です、なんて事は言いません。

@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex *1" && SET PATH=%PATH%;%systemdrive%\chocolatey\bin - See more at: http://chocolatey.org/#sthash.h1KBAENo.dpuf

今回公開するコードにchocolatey インストールも含まれているので忘れて大丈夫です。

function New-ChocolateryInstall {

    param(
    [bool]$ShowMan=$false
    )

    Write-Host "Checking for chocolatery installation."

    try
    {
        Import-Module C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1
    }
    catch
    {
        Invoke-Expression *2
    }

    if (!(Get-Module chocolateyInstaller))
    {
        Invoke-Expression *3
    }
    else
    {
        Write-Host "chocolatery had already been installed. nothing will do." -ForegroundColor Green
    }

    switch ($true){
    $ShowMan {Get-ChocolateryInstructions}
    default{ Write-Host "    - If you want to check simple chocolatery usage, add -ShowMan $true." -ForegroundColor Yellow}
    }

}

今回紹介する ssh モジュールの実行経過で、chocolateyがインストールされていない場合は自動的にインストールします。 経過でこのように表示されます。

The specified module 'C:\Chocolatey\chocolateyinstall\helpers\chocolateyInstaller.psm1' was not loaded because no valid module file was found in any module directory. Downloading http://chocolatey.org/api/v2/package/chocolatey/ to C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\chocInstall\chocolatey.zip Extracting C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\chocInstall\chocolatey.zip to ... Installing chocolatey on this machine Creating ChocolateyInstall as a User Environment variable and setting it to 'C:\Chocolatey' We are setting up the Chocolatey repository for NuGet packages that should be at the machine level. Think executables/application packages, not library packages. That is what Chocolatey NuGet goodness is for. The repository is set up at 'C:\Chocolatey'. The packages themselves go to 'C:\Chocolatey\lib' (i.e. C:\Chocolatey\lib\yourPackageName). A batch file for the command line goes to 'C:\Chocolatey\bin' and points to an executable in 'C:\Chocolatey\lib\yourPackageName'. Creating Chocolatey NuGet folders if they do not already exist. bin lib chocolateyinstall Copying the contents of 'C:\Users\Administrator\AppData\Local\Temp\2\chocolatey\chocInstall\tools\chocolateyInstall' to 'C:\Chocolatey'. Creating 'C:\Chocolatey\bin\chocolatey.bat' so you can call 'chocolatey' from anywhere. Creating 'C:\Chocolatey\bin\cinst.bat' so you can call 'chocolatey install' from a shortcut of 'cinst'. Creating 'C:\Chocolatey\bin\cinstm.bat' so you can call 'chocolatey installmissing' from a shortcut of 'cinstm'. Creating 'C:\Chocolatey\bin\cup.bat' so you can call 'chocolatey update' from a shortcut of 'cup'. Creating 'C:\Chocolatey\bin\clist.bat' so you can call 'chocolatey list' from a shortcut of 'clist'. Creating 'C:\Chocolatey\bin\cver.bat' so you can call 'chocolatey version' from a shortcut of 'cver'. Creating 'C:\Chocolatey\bin\cwebpi.bat' so you can call 'chocolatey webpi' from a shortcut of 'cwebpi'. Creating 'C:\Chocolatey\bin\cwindowsfeatures.bat' so you can call 'chocolatey windowsfeatures' from a shortcut of 'cwindowsfeatures'. Creating 'C:\Chocolatey\bin\ccygwin.bat' so you can call 'chocolatey cygwin' from a shortcut of 'ccygwin'. Creating 'C:\Chocolatey\bin\cpython.bat' so you can call 'chocolatey python' from a shortcut of 'cpython'. Creating 'C:\Chocolatey\bin\cgem.bat' so you can call 'chocolatey gem' from a shortcut of 'cgem'. Creating 'C:\Chocolatey\bin\cpack.bat' so you can call 'chocolatey pack' from a shortcut of 'cpack'. Creating 'C:\Chocolatey\bin\cpush.bat' so you can call 'chocolatey push' from a shortcut of 'cpush'. Creating 'C:\Chocolatey\bin\cuninst.bat' so you can call 'chocolatey uninstall' from a shortcut of 'cuninst'. User PATH already contains either 'C:\Chocolatey\bin' or '%DIR%..\bin' Processing ccygwin.bat to make it portable Processing cgem.bat to make it portable Processing chocolatey.bat to make it portable Processing cinst.bat to make it portable Processing cinstm.bat to make it portable Processing clist.bat to make it portable Processing cpack.bat to make it portable Processing cpush.bat to make it portable Processing cpython.bat to make it portable Processing cuninst.bat to make it portable Processing cup.bat to make it portable Processing cver.bat to make it portable Processing cwebpi.bat to make it portable Processing cwindowsfeatures.bat to make it portable Chocolatey is now ready. You can call chocolatey from anywhere, command line or powershell by typing chocolatey. Run chocolatey /? for a list of functions. You may need to shut down and restart powershell and/or consoles first prior to using chocolatey. If you are upgrading chocolatey from an older version (prior to 0.9.8.15) and don't use a custom chocolatey path, please find and delete the C:\NuGet folder after verifying that C:\Chocolatey has the same contents (minus chocolateyinstall of course). Ensuring chocolatey commands are on the path - If you want to check simple chocolatery usage, add -ShowMan True.

なお、chocolatey からモジュールをインストールする場合は、cinst モジュール名 となります。

cinst モジュール名

msysgit を chocolatey 経由でインストール

msysgit は、数ある git client の一つで、 chocolatey 経由でインストールが可能です。 git をインストールすることで、 同時に ssh も入るのでこれでサクッとやってしまいましょう。 chocolatey からmsygit をインストールする場合は、このようになります。

cinst msysgit

今回公開するコードに msysgit インストールも含まれているので忘れて大丈夫です。

function New-ChocolateryMsysgitInstall {

    Write-Host "Checking for msysgit installation."

    if (!(Get-ChildItem -path "C:\Chocolatey\lib" -Recurse -Directory | ? {$_.Name -like "msysgit*"}))
    {
        cinst msysgit
    }
    else
    {
        Write-Host "msysgit had already been installed. nothing will do." -ForegroundColor Green
    }

}

今回紹介する ssh モジュールの実行経過で、msygit がインストールされていない場合は自動的にインストールします。

Chocolatey (v0.9.8.20) is installing msysgit and dependencies. By installing you accept the license for msysgit and each dependency you are installing. ______ git.install v1.8.3 ______ Downloading git.install (http://msysgit.googlecode.com/files/Git-1.8.3-preview20130601.exe) to C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\git.install\git.installInstall.exe Installing git.install... Elevating Permissions and running C:\Users\ADMINI~1\AppData\Local\Temp\2\chocolatey\git.install\git.installInstall.exe /VERYSILENT . This may take awhile, depending on the statements. git.install has been installed. git.install has finished succesfully! The chocolatey gods have answered your request! git.install has finished succesfully! The chocolatey gods have answered your request! ______ git v1.8.3 ______ ______ msysgit v1.7.10.20120526 ______ Finished installing 'msysgit' and dependencies - if errors not shown in console, none detected. Check log for errors if unsure.

簡単ですね! これで ssh を使う準備が出来ました。 (といっても、ここまで一切入力する必要はありません、中で何をやっているか説明しただけですしやらなくても良きように計らいます)

ssh 接続で準備しておくもの

これだけ準備してください。

  • 接続する linux サーバーの ssh key(RSA)
  • ssh 接続ユーザー名
  • 接続先サーバーの ip address か hostname
※今回は、 ssh key のパスワードを省略していますが、ちょちょいとさわればそれも出来ます。 (今回はやらないけど)

次はいよいよ ssh 経由でコマンドを実行します。

Windows PowerShell から SSH 経由で Linux にコマンドを飛ばして結果を取得する

至って簡単でこのようなモジュールで ssh コマンドをラップしています。

function Invoke-SshCommand{

    [CmdletBinding()]
    param(
        [parameter(
            position = 0,
            mandatory = 1,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $rsaKey,

        [parameter(
            position = 1,
            mandatory = 1,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $user,

        [parameter(
            position = 2,
            mandatory = 1,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $hostip,

        [parameter(
            position = 3,
            mandatory = 1,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $command,

        [parameter(
            position = 4,
            mandatory = 0,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $option="",

        [parameter(
            position = 5,
            mandatory = 0,
            ValueFromPipeLine = 1,
            ValueFromPipelineByPropertyName = 1)]
        [string]
        $option2=""
              
    )

    begin
    {
        Write-Verbose "Check Log Folder is exist or not"
        New-PSSshLogFolder

        Write-Verbose "Check Ssh Status"
        Test-SshInstallationStatus

        Write-Verbose "Set GitPath"
        Set-EnvGitPath

        Write-Verbose "Set User Profile Path"
        Set-EnvUserProfilePath

        Write-Verbose "prefix command to avoid trusted option"
        $prefixoption1 = "StrictHostKeyChecking=no"
        $prefixoption2 = "UserKnownHostsFile=/dev/null"
        $prefixoption3 = "LogLevel=quiet"
    }

    process
    {
        if*4
        {
            Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command"
            ssh -i $rsaKey $user@$hostip -o $option -o $option2 -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command
        }
        elseif ($option -ne "")
        {
            Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $option -o $prefixoption1 -o$prefixoption2 -o $prefixoption3 $command"
            ssh -i $rsaKey $user@$hostip -o $option -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command
        }
        else
        {
            Write-Verbose "実行コマンド : ssh -i $rsaKey $user@$hostip -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command"
            ssh -i $rsaKey $user@$hostip -o $prefixoption1 -o $prefixoption2 -o $prefixoption3 $command
        }
    }

    end
    {
        
    }
}

chocolatey と msygit がインストールされていれば git パスが定まるので、環境変数に入れたりもしています。 全てmodule に入っているのでよろしければご覧ください。 利用する際のサンプルフォーマットです。

Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド"

# Optionがある場合は 2つまで設定可能
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション

# Optionがある場合は 2つまで設定可能
Invoke-SshCommand -rsaKey "RSAキーパス" -user sshユーザー名 -hostip 接続先サーバーIP -command "ssh先で実行するコマンド" -option sshコマンドオプション -option2 sshコマンドオプション2

利用する際は、例えばこのようにします。

 Invoke-SshCommand -rsaKey c:\ssh\rsa -user lunuxuser -hostip 192.168.1.1 -command "hostname"

簡単ですね。

Windows PowerShell から SSH 経由で Capistrano がインストールされたLinux サーバーに Cap コマンドを送信し、結果を取得する

先ほどのssh 接続、コマンド実行のコマンドレットを利用して、 利用する capistrano deploy コマンドを送っています。

function Invoke-CapistranoDeploy{

    [CmdletBinding()]
    param(

        [parameter(
        position = 0,
        mandatory = 1
        )]
        [string]
        $deploygroup,

        [parameter(
        position = 1,
        mandatory = 1
        )]
        [string]
        $captask,

        [parameter(
        position = 2,
        mandatory = 1
        )]
        [string]
        $deploypath,

        [parameter(
        position = 3,
        mandatory = 1
        )]
        [string]
        $rsakey,

        [parameter(
        position = 4,
        mandatory = 1
        )]
        [string]
        $user,

        [parameter(
        position = 5,
        mandatory = 1
        )]
        [string]
        $hostip
    )

    begin
    {

        # define cap command
        Write-Verbose "define basecommand : source .bash_profile; cd $deploypath;"
        $basecommand = "source .bash_profile; cd $deploypath;"

        Write-Verbose "define cap command : cap $deploygroup $captask;"
        $capcommand = "cap $deploygroup $captask;"

        Write-Verbose "define ssh command : $basecommand + $capcommand"
        $command = $basecommand + $capcommand


        # define splating for ssh command
        $sshparam = @{
            rsakey = $rsakey
            user = $user
            hostip = $hostip
            command = $command
        }    

        # Show define result
        Write-Warning "Set -Verbose switch to check ssh command variables and Detail"
        Write-Verbose "rsakey   : $rsakey"
        Write-Verbose "user     : $user"
        Write-Verbose "hostip   : $hostip"
        Write-Verbose "command  : $command"
    }

    process
    {
        # runcommand
        Invoke-SshCommand @sshparam -ErrorAction Continue 2>&1 | %{
            
            # Host Display
            if *5
            {
                Write-Host $_ -ForegroundColor Red
            }
            elseif ($_ -like "*the task * does not exist*")
            {
                Write-Host $_ -ForegroundColor Yellow
            }
            else
            {
                Write-Host $_
            }
            

            # Log Output
            if ($_.Message -eq $null)
            {
                $_ | Out-File -FilePath $psssh.Log.path -Encoding utf8 -Append
            }
            else
            {
                $_.Message | Out-File -FilePath $psssh.Log.path -Encoding utf8 -Append
            }

        }

    }

    end
    {       
    }
}

利用する際のサンプルフォーマットです。

Invoke-CapistranoDeploy `
        -deploygroup $deploygroup `
        -captask "deploy" `
        -deploypath CapistranoFullPath `
        -rsakey RSAKeyForSSH `
        -user SSHUser `
        -hostip IpAddress

実行するとCapistranoの実行結果が出力されます。 failed など capistrano のエラーキーワードを赤地ハイライトしているので、 エラーが起こった場合も分かります。

はい

実は、 Windows外部コマンドや ssh などを PowerShell から実行すると、 Native Command Error や RemoteException という例外が出ることがあります。 例えば wget コマンドを送った場合がそうです。

Why am I getting a "Native Command Error"

おおよそ PowerShell のバグと言い切っていいのですが、 通常の $ErrorPreference は continue なので継続実行されます。 が、 エラーとして全て赤字で出力されてしまいます.... これでは正常実行かどうか判断できません.....

そこで、capistrano実行部分でほにょってます。

        Invoke-SshCommand @sshparam -ErrorAction Continue 2>&1 | %{
            
            # Host Display
            if *6
            {
                Write-Host $_ -ForegroundColor Red
            }
            elseif ($_ -like "*the task * does not exist*")
            {
                Write-Host $_ -ForegroundColor Yellow
            }
            else
            {
                Write-Host $_
            }

原因と、問題ないことが確認できているので、ここで例外を捉えて(各 capistrano コマンド出力一回毎にErrorになる。) 通常の表示を エラーの赤字から白字 に変換しています。

かつ、特定の文字を含む場合は 赤字や黄色字としています。 意外とこのやり方は StackOverflow でもリクエストがありますが、回答が乏しいので参考になれば幸いです。

まとめ

「ssh 認証で rsa キーのパスワード指定をしていない」

「capistrano ssh認証に bash_profile での自動読み込みを利用している」

など、特異な設定でのコマンドにはなっています。

が、上手く利用すれば Windows から PowerShell でAutomation に組み込める事が分かります。

参考程度に見ていただければ幸いです。

*1:new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'

*2:new-object Net.Webclient).DownloadString("http://bit.ly/psChocInstall"

*3:new-object Net.Webclient).DownloadString("http://bit.ly/psChocInstall"

*4:$option -ne "") -and ($option2 -ne ""

*5:$_ -like "*error*") -or ($_ -like "*failed*") -or ($_ -like "*fatal*"

*6:$_ -like "*error*") -or ($_ -like "*failed*") -or ($_ -like "*fatal*"