Windows PowerShell開発でMacユーザーが知っておくべきTips集

1.5ヶ月ほどPowerShellをさわる機会がありまして、そのとき気付いたことを書きます。

MacユーザーであればPowerShellを知らない方と思いますので簡単に説明するとWindows版のshellですw
Windowsのshellだとコマンドプロンプトが有名ですが、Windowsに依存した記述のコマンドが多く
Macユーザーからしたら覚えるべきことが多く敷居が高いです。
PowerShellは、Macで利用する基本コマンドはほぼ用意されてます(ls, cd, mkdir, cp..etc)
あまり環境の変化を意識しないので簡単に利用できると思います。
.Net FrameworkのLibraryも利用できps1という拡張子でスクリプトを作成することができます。

## PowerShellの開発スタイルについて
僕は開発に関するオペレーションをかなりカスタマイズしていて
開発環境がMacからWindoswsに変ってしまうとか一気にパフォーマンスが落ちてしまい
MacでPowerShellを実行まで出来たらと考えてました。
調査するとMono Frameworkで動作するPashというものがありましたが、
まだ絶賛開発中で動作確認するとPowershellとは似て非なるものでした。
とりあえず、Dropboxを利用してMacでコード書きつつ、
WindowsではPowerShell実行する開発スタイルが一番良さげでした。

## PowerShell実行権限を付与する
最初はPowerShellのスクリプト実行権限がないので実行するとエラーになります。
そのため、システムの実行権限を変更する必要があります。

ローカルスクリプトのみの実行権限を付与するRemoteSignedに設定します

```ps1
PS> Set-ExecutionPolicy RemoteSigned
```

## PoserShellのプロファイル設定
bashrcやzshrcのようにshell起動時に実行Pathやエイリアス、プロンプトなどの環境設定ができます。

プロファイルを場所確認

```ps1
PS> $profile
C:\Users\<UserName>\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
```

デフォルトではファイルが存在してないので作成する必要があります

```ps1
PS> notepad $profile
```

## ssh設定
Mac同様、Windowsもユーザーディレクトリ直下に.sshを作成すると鍵認証として動作できる

## パッケージ管理マネージャー
MacのHomeBrewやports同様にWindowsにも
パッケージ管理マネージャーのChocolateyというものがあります。
https://chocolatey.org/

以下でインストールする

```ps1
PS> iex ((new-object net.webclient).DownloadString("http://bit.ly/psChocInstall"))
```

| コマンド                 | 説明                                         |
| :----------------------: | :------------------------------------------: |
| choco list               | インストール出来るパッケージ一覧             |
| choco list hoge          | インストール出来るパッケージを検索           |
| choco list -lo           | 今入っているパッケージとそのバージョンが表示 |
| choco install hoge       | パッケージインストール                       |
| choco uninstall hoge     | アンインストール                             |

ただ、PKGを入れたからといってPATHが通るものと通らないものがあったので
確認して自分でプロファイルなどに設定する必要があります。

## pipは一部インストールに失敗する
Pythonのパッケージ管理ツールpipは一部インストールに失敗します。

MySQL-pythonのインストールで失敗した例

```ps1
    :
    creating build\temp.win32-2.7\Release
    C:\Users\ryo.fujimoto\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\cl.exe /c /nologo /Ox
 /MD /W3 /GS- /DNDEBUG -Dversion_info=(1,2,5,'final',1) -D__version__=1.2.5 "-IC:\Program Files (x86)\MySQL\MySQL Connec
tor C 6.0.2\include" -IC:\tools\python2-x86_32\include -IC:\tools\python2-x86_32\PC /Tc_mysql.c /Fobuild\temp.win32-2.7\
Release\_mysql.obj /Zl
    _mysql.c
    _mysql.c(42) : fatal error C1083: Cannot open include file: 'config-win.h': No such file or directory
    error: command 'C:\\Users\\ryo.fujimoto\\AppData\\Local\\Programs\\Common\\Microsoft\\Visual C++ for Python\\9.0\\VC
\\Bin\\cl.exe' failed with exit status 2

    ----------------------------------------
Command "C:\tools\python2-x86_32\python.exe -c "import setuptools, tokenize;__file__='c:\\users\\ryo~1.fuj\\appdata\\loc
al\\temp\\pip-build-kmsg7q\\MySQL-python\\setup.py';exec(compile(getattr(tokenize, 'open', open)(__file__).read().replac
e('\r\n', '\n'), __file__, 'exec'))" install --record c:\users\ryo~1.fuj\appdata\local\temp\pip-7uc1jj-record\install-re
cord.txt --single-version-externally-managed --compile" failed with error code 1 in c:\users\ryo~1.fuj\appdata\local\tem
p\pip-build-kmsg7q\MySQL-python
```

このエラーではconfig-win.hが無いためですが、他にも別の理由でよく失敗します。
一つ一つ解消して行くのは骨が折れる作業なので非公式ですが以下のサイトから
バイナリをダウンロードしてインストールすると楽です
http://www.lfd.uci.edu/~gohlke/pythonlibs/

```ps1
PS> C:\Python27\Scripts> .\pip.exe install ~/Desktop/MySQL_python-1.2.5-cp27-none-win32.whl
```

## PowerShell内で日本語を利用した場合は差分管理できない(かも)
ps1ファイルの文字コードがShift-jis, UTF-8で保存すると、
gitのファイル差分がbinary扱いされ変更差分を見ることができません。
たぶんASCII(CRLF or LF)の形式で書くしかなさそう

## PowerShell内のコマンド実行は終了を待ってくれない
PowerShellスクリプト内でコマンド実行した場合はマルチタスクで実行されるため
コマンド実行終了を待たずに次のステートメントの読み込みが行われます。
コマンド実行終了を待つ場合は「コマンド | out-null」にしておく必要があります

## エラー時のダイアログを非表示にする
スクリプト実行でエラーが発生した場合、エラー報告のダイアログ表示され
OKボタンなどを押さないと処理が進めず例外処理などで困ります。
try..catch文ではボタンを押さない限りcatchに移れないので。

エラー報告のダイアログを非表示にする手順

1. グループ・ポリシー・エディタを起動
（［スタート］－［ファイル名を指定して実行］を選択し「gpedit.msc」と入力）

2. 以下を有効にする
コンピューターの構成 > 管理用テンプレート > Windowsコンポーネント > Windowsエラー報告
・ Windowsエラー報告を無効にする 
・ 重大なエラーが発生したユーザーインターフェイスを表示しないようにする

## Windows起動時にスクリプトを実行したい
Windows起動時スクリプトを実行したい場合は
以下のStartupフォルダにスクリプトを配置します。

C:\Users\\\<UserName>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

## スクリプトのLogの取り方
run.ps1スクリプトを実行時に標準出力、標準エラーをコンソール表示しながらファイルに書き込みできます。

```ps1
PS> run.ps1 2>&1 | tee -FilePath ("./Log/run_{0}.txt" -F (Get-Date -Format "yyyy-MM-dd")) -Append
```

## ショートカットの扱いについて
Windowsのショートカットの扱いはシンボリックリンクと異なり
ショートカットを含むPathではPathとして認識させることはできません

```ps1
C:\current.lnk
C:\test1\server.exe
C:\test2\server.exe
C:\test3\server.exe
```

上記のようなディレクトリ構成があって、
test1のショートカットがcurrent.lnkとした場合
/current/server.exe を実行しても存在しないPath扱いになります。
実際は、C:\currentのpathからショートカットオブジェクトを作成して、
そのメタ情報のTargetPathから実際のpathが取得します。

## ショートカットの作成、取得

- ショートカットの作成

```ps1
$wsh = New-Object -ComObject Wscript.Shell
$shortCut = $wsh.CreateShortCut("/current.lnk")
$shortCut.TargetPath = "/test1"
$shortCut.Save()
```

- ショートカット先の取得

```ps1
$wsh = New-Object -ComObject WScript.Shell
$shortCut = $wsh.CreateShortcut("/current.lnk")
$path = $shortCut.targetpath
```

## リモート接続
WindowsにもSSHに近い機能としてPowerShellのPSRemotingというものが標準サービスであります。
外部Windowsサーバーへ接続しコマンド実行することができ、コマンド実行時のエラーも
接続元のサーバーで例外catch出来たりするので結構便利です。

### 接続準備

接続元と接続先の両方に設定が必要になります。

- [接続元] リモート接続先ホストを信頼する

```ps1
PS> Set-Item WSMan:\localhost\Client\TrustedHosts -Value *
```

- [接続先] リモート接続を有効にする

```ps1
PS> Enable-PSRemoting
```

### 接続してコマンド実行

```ps1
function Get-RemoteSession($username, $passwd)
{
    # Password encryption
    $secStr = ConvertTo-SecureString $passwd -AsPlainText -Force

    # Create Object that contains a ID and Password
    $psc = New-Object System.Management.Automation.PsCredential($username, $secStr)

    # Create Session
    $session = New-PSSession -ComputerName $hostname -Credential $psc
    return $session
}

$exeCommand = {
    $Name1 = $args[0]
    $Name2 = $args[1]

    test.ps1 $Name1 $Name2
}

$session = Get-RemoteSession "user Name" "password"
try {
    Invoke-Command -Session $session -ScriptBlock $exeCommand -ArgumentList "hoge", "fuge"
} finally {
    Remove-PSSession -Session $session
}
```

## TCP接続

localhost:8888にTCP接続して非同期でメッセージ受信を処理する

```ps1
$client = New-Object System.Net.Sockets.TcpClient("localhost", 8888)
$stream = $client.GetStream()
$buffer = New-Object System.Byte[] $client.ReceiveBufferSize
$enc = New-Object System.Text.UTF8Encoding
try {
    $sendMsg = "Hello world PowerShell";
    $asyncRead = $stream.BeginRead($buffer, 0, $buffer.length, $NULL, $NULL)
    $data = $enc.GetBytes($sendMsg)
    $stream.Write($data, 0, $data.length)
    while ($TRUE) {
        if ($asyncRead.IsCompleted) {
            $bytes = $stream.EndRead($asyncRead)
            $result = $enc.GetString($buffer, 0, $bytes)
            Write-Host ("response message: {0}" -F $result)
            break
        }
    }
} catch [System.IO.IOException] {
    Write-Host "TCP Connect Failed" -ForegroundColor Red -BackgroundColor Black
} finally {
    $stream.Close()
    $client.Close()
}
```

## まとめ
以前はGUIメインなWindowsを利用することに抵抗があったのですがChocolateyやPowerShellを知ったことで、
ほぼCUIベース作業できるし、抵抗意識はあまりなくなった気がします。
PowerShellを覚えてWindowsを見直す良いきかっけになりました。