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

素敵なおひげですね

主にWindowsな技術ブログ。最近はPowerShellなネタが多めです。

符号化処理芸人たちのシェル芸をPowerShellで再現する

PowerShell

元ネタはこちら。

papiro.hatenablog.jp

はじめに

私はシェル芸人ではないので大したことも面白いこともできませんのでご了承ください。

本エントリはシェル芸人たちの匠の技をPowerShellで再現するにはどうするかという点だけに注力しています。
PowerShell(というかWindows)には残念ながら元ネタで使われている各種コマンドが無い*1ため、その部分をPowerShellおよび.NET Frameworkの機能をいかに使って補うかがキモになるかと思っています。

それでは元ネタのお題の順に進めていきます。
特にバージョン依存の処理は書いていないはずですが、Windows 10PowerShell(5.1)で動作確認をしています。

1. Use Xamarin.

お題

最初はちょまどさんのこちら。

元ネタの回答はこちら。

echo 01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110 | tr -d ' ' | sed 's/^/obase=16;ibase=2;/' | bc | xxd -p -r

PowerShell

PowerShellだとこんな感じで書けます。
長くなるのが嫌だったので適当なところで改行しています。

# PowerShell
-split "01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110" `
    | % { $o = "" } `
        { $o += [Char]([Convert]::ToByte($_, 2)) } `
        { $o }

実行結果はこんな感じ。

f:id:stknohg:20160923215425p:plain

解説

簡単に解説を入れていきます。
最初の行の、

-split "01010101 01110011 01100101 00100000 01011000 01100001 01101101 01100001 01110010 01101001 01101110 00101110"

は、お題の文字列がスペース区切りなので-split演算子を使って配列化しています。

2行目以降は配列化された二進表記の文字列をForEach-Object(%)を呼んで、3つのスクリプトブロックを使って変換しています。
ForEach-Object(%)では-Begin-Process-Endのパラメータで最大3つのスクリプトブロックを指定することができ、それぞれ、

  • -Begin : 最初のオブジェクトがパイプされる直前に呼ばれる処理
  • -Process : 各オブジェクトがパイプされる毎に呼ばれる処理
  • -End : 最後のオブジェクトがパイプされた後に呼ばれる処理

となっています。

今回はパイプラインには配列化した各要素("01010101", "01110011", ...)が順に渡されていきます。
最初のスクリプトブロック(-Begin)で最終出力用の変数$oを定義し、二番目のスクリプトロック(-Process)で入力文字列の変換処理を行っています。

[Char]([Convert]::ToByte($_, 2))

で入力文字列$_("01010101"など)をByte型に変換し、Char型にキャストしてASCII文字列に戻しています。
最後のスクリプトブロック(-End)で連結された$oを出力ストリームに出力しています。

ちなみに、

$o

Write-Output $o

は同義です。
今回はそれっぽくするためWrite-Outputを端折ってみました。

2. 届けiOS10

お題

次はぱぴろんさんのこちら。

元ネタの方ではRubyPerlを使った回答が提示されていました。

PowerShell

PowerShellだとこんな感じです。

# PowerShell
"111001011011000110001010111000111000000110010001011010010100111101010011001100010011000000001010" `
    | % { $n = 8; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } } `
    | % { $b = @() } `
        { $b += [Convert]::ToByte($_, 2) } `
        { [Text.Encoding]::UTF8.GetString($b) }

実行結果はこんな感じ。

f:id:stknohg:20160923222547p:plain

解説

今回はお題の文字列に区切り文字が無いので、2行目のForEach-Object(%)

% { $n = 8; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } }

で、8文字ごとに区切って後続にパイプしています。
スクリプトブロックを1つだけにした場合は-Processブロックになります。

3行目以降のForEach-Object(%)につては基本的に最初のお題と同じですが、最終的な答えがUTF8の文字列であったため、Byte[]列をUTF8に変換する様になっている部分が異なっています。

3. 焼肉

お題

次はぐれさんのこちら。

こちらの回答は自分の環境(Bash on Ubuntu on WindowsおよびVM上のUbuntuBash)ではうまく動作せず、以下のコードであれば動作しました。

echo $(echo 1302140411021101140213011103110511011103110211 | fold -w 2 | awk '{for(i=1;i<=$2;i++){printf $1}}' FS= | sed 's/^/obase=16;ibase=2;/') | bc | xxd -p -r

シェル芸人ではないので細かいところはよくわかりません...

PowerShell

PowerShellだとこんな感じです。

# PowerShell
"1302140411021101140213011103110511011103110211" `
    | % { $n = 2; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } } `
    | % { $s = "" } `
        { $s += "".PadRight([string]$_[1], $_[0]) } `
        { $s } `
    | % { $n = 8; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } } `
    | % { $b = @() } `
        { $b += [Convert]::ToByte($_, 2) } `
        { [Text.Encoding]::UTF8.GetString($b) }

実行結果はこちら。

f:id:stknohg:20160923225340p:plain

解説

だいぶつらくなってきました...
文字列をランレングス圧縮しているとの事ですので、そのあたりの処理が入ってきます。

PowerShellには良い感じのコマンドがないのでひたすらForEach-Object(%)スクリプトブロックで頑張るしかありません。

2行目の

% { $n = 2; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } }

はお題の文字列を2文字区切りにしています。
3~5行目のForEach-Object(%)でそれぞれ二進表記に変換(13111などへ)しています。

% { $s = "" } `
  { $s += "".PadRight([string]$_[1], $_[0]) } `
  { $s } `

6行目の

% { $n = 8; for( $i = 0; $i -lt $_.Length; $i += $n ){ -join $_[$i..$($i+$n-1)] } }

で二進表記の文字列を8文字区切りにし、7~9行目の、

% { $b = @() } `
  { $b += [Convert]::ToByte($_, 2) } `
  { [Text.Encoding]::UTF8.GetString($b) }

でUTF8の文字列に変換しています。

4. YES

最後のお題です。

元ネタの回答はこちら。

echo 'In48FEBACHw8CEACCEBCCH48' | base64 --decode | xxd -b -c 3 | awk '$1="";$NF="";1' | sed 'y/01/ y/'

PowerShell

PowerShellだとこんな感じ。

# PowerShell
"In48FEBACHw8CEACCEBCCH48" `
    | % { [Convert]::FromBase64String($_) } `
    | % { $s=@(); $map=@{ "0"=" "; "1"="y" } } `
        { $s += ([Regex]($map.Keys -join "|")).Replace([Convert]::ToString($_, 2).PadLeft(8, "0"), {param($m) $map[$m.Value] }) } `
        { for( $i = 0; $i -lt $s.Length; $i+=3 ){ -join $s[$i..($i+2)] } }

実行結果はこちら。

f:id:stknohg:20160923230555p:plain

解説

PowerShellBASE64を処理するのは[Convert]::FromBase64String()メソッドを呼ぶだけですので楽勝です。
3~4行目の、

% { $s=@(); $map=@{ "0"=" "; "1"="y" } } `
  { $s += ([Regex]($map.Keys -join "|")).Replace([Convert]::ToString($_, 2).PadLeft(8, "0"), {param($m) $map[$m.Value] }) }

の部分でBASE64文字列をデコードしたByte値を二進表記に変換し、加えて0→" "1→"y"への置換を行っています。
文字列の置換には[Regex]クラスを使用しています。
最後の5行目

  { for( $i = 0; $i -lt $s.Length; $i+=3 ){ -join $s[$i..($i+2)] } }

の部分で、3Byteごとに区切っています。

まとめ

とりあえずこんな感じです。

シェル芸に対してPowerShellの圧倒的なコマンド不足が露呈しつつも、.NET Frameworkのもつ強力な機能のおかげでかろうじて何とかなっている感じといったところです。
良くも悪くも「コマンドが無いならスクリプトブロックで何とかするしかないじゃん!」といった体になってしまいました。

とはいえ、実際の業務でPowerShellを利用する場合であれば、足りないコマンドは自作の関数で補うことができますのでそこまで苦労することは無いかと思います。

割と思いつくままにコードを書きましたので、もっと良い方法は他にもたくさんあると思います。
より良い方法がありましたらフィードバック頂けると嬉しいです。

*1:いちおう補足しておくとBash on Ubuntu on Windowsにはありますよ

PowerShell on Linuxに普通にPSRemotingしてみる - その2

PowerShell Linux

以前のエントリ、

stknohg.hatenablog.jp

でソースからのビルドは面倒だからやらないと言ったのですが、気が変わりました。
というのも、

github.com

のIssueがCloseされmasterブランチに取り込まれたとのことで、次のバージョンのリリース前にその結果を確認したくなったからです。

動作環境

今回は普段使い慣れてるCentOS 7.2(1511)でやります。
Virtualbox上の仮想マシン環境です。

PowerShell on LinuxにPSRemotingしてみる

今回は非rootユーザーで試しているので必要に応じてsudoしています。

1. PowerShellのインストール

github.com

GitHubからPowerShellrpmファイルをインストールするだけです。

# Install Powershell on Linux
sudo yum install -y https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.10/powershell-6.0.0_alpha.10-1.el7.centos.x86_64.rpm

2. OMI Serverのインストール

github.com

こちらもrpmをインストールするだけです。

# Install OMI
sudo yum install -y https://github.com/Microsoft/omi/releases/download/v1.1.0-0/omi-1.1.0.ssl_100.x64.rpm

3. PowerShell on Linux OMI Providerのビルド

ここからが今回独自の作業になります。

基本的な手順はGitHubに記載されています。
PowerShell on Linux OMI ProviderはC++製ですのでmakeするための環境と、pam-developenssl-devel*1をインストールします。

そして最新のソースをクCloneしてビルドスクリプトbuild.shを実行します。
今回は/tmp/omiディレクトリを作成し、そこにCloneしてみました。
(ビルドスクリプト/tmp/omi/psl-omi-provider/build.shに存在します)

# Build latest psrp-omi-provider
sudo yum install -y cmake make gcc gcc-c++ git
sudo yum install -y pam-devel openssl-devel
# 
mkdir /tmp/omi
cd /tmp/omi/
git clone --recursive https://github.com/PowerShell/psl-omi-provider.git
cd psl-omi-provider/
./build.sh

エラー無くビルドが完了しlibpsrpomiprov.soが作成されれば成功です。

ちなみにビルドが完了した状態でbuild.shと同じディレクトリにあるrun.shを実行するとOMI Serverをデバッグ実行しコンソールにログを表示させることができます。*2

これでもPSRemotingすることができますが、今回はさらにRPMパッケージの作成まで行っていきます。

4. installBuilder(pal)のインストール

GitHub上の手順

Current OMI (Open Management Infrastructure) products use the InstallBuilder tool to create installation packages.

とある様にパッケージの作成にはInstallBuilderのインストールが必要です。
InstallBuilderと言っておきながらリンク先はPlatform Abstraction Layer(pal)というライブラリを指しており、最初はわけがわからなかったのですが、どうやらビルド環境の判定(OSの種類やパッケージツールの有無など)のためにこのライブラリの一部機能を利用しているということがわかりました。

まずはrpmパッケージを作るということでrpm-develrpm-buildをインストールしておきます。*3

sudo yum install -y rpm-devel rpm-build

続けて先ほどのPlatform Abstraction Layer(pal)のソースをCloneします。
Clone先は必ずPowerShell on Linux OMI Providerと同じディレクトリ(今回であれば/tmp/omi/)にしてください。

そしてpal/build/ディレクトリに移動し、configureコマンドを実行します。
このconfigureでビルド環境の判定を行い、同じディレクトリにconfig.makファイルを作成します。

# Install installBuilder(pal)
cd /tmp/omi/
git clone https://github.com/Microsoft/pal.git
cd pal/build/
# Make config.mak
./configure

config.makファイルが次の手順で必要になります。

5. rpmパッケージの作成とインストール

最後にrpmパッケージを作成してインストールまで行います。

PowerShell on Linux OMI Providerのinstallbuilderディレクトリに移動してmakeを実行します。
先ほどの手順で正しくconfig.makファイルが作成されてれば、omi/Unix/output/release/ディレクトリにRPMファイルが作成されます。

# Build rpm package
cd ../../psl-omi-provider/installbuilder/
make

作成されたrpmファイルをインストールすれば完了です。

# Install rpm package
cd ../omi/Unix/output/release/
sudo rpm -i ./psrp-1.0.0-0.universal.x64.rpm

6. PSRemotingしてみる

後の作業は前回と同じでNew-PSSessionOptionEnter-PSSessionしてやればOKです。

# サーバーのIPが 192.168.33.209 の場合
$o = New-PSSessionOption -SkipCACheck -SkipRevocationCheck -SkipCNCheck
Enter-PSSession -ComputerName 192.168.33.209 -Credential root -Authentication basic -UseSSL -SessionOption $o

下図の様にCentOSでもエラー無くPSRemotingできました。

f:id:stknohg:20160921002702p:plain

これで完了です。

当たり前ですが作成したrpmファイルは再利用できますのでバックアップしておくと次回以降楽ができるでしょう。

補足

今回の一連の手順は以下のGistにまとめていますのでこちらも参考にしてください。

最新のPowerShell on Linux OMI Providerをインストールするサンプル(CentOS7) · GitHub

*1:apt-getだとlibpam0g-dev、libssl-dev

*2:OMI Providerと同時にOMI Serverもビルドしているため

*3:rpm-buildがインストールされているか次で判定しているため必ずこの手順を先にしてください

PowerShell DSC for Linuxを試す

PowerShell Linux Windows

そろそろ試しておかないといけないかなと思ったので。

正直PowerShell DSCはまだまだ勉強不足なのでおかしなところがあるかもしれませんがそこはご容赦ください。

PowerShell DSC for Linuxとは

github.com

名前の通りLinuxPowerShell Desired State Configuration(DSC)を利用するための基盤です。
利用可能なOSは以下。

こいつ自体は、去年PowerShell on Linuxが公開される以前から利用可能になっています。

下回りにOMIを使い、LCMや各DSC ResourceはOMI Providerとして提供されています。
そしてDSCの管理にはPythonスクリプトを使用します。
PowerShell on Linuxが出たことでPowerShellによる管理に切り替わっていくかどうかはまだよくわかりません。

組み込み済みのDSC Resourceの一覧は

Linux 用 Desired State Configuration の組み込みリソース

で確認できます。
Linux用のリソース名はnxで始まる規約となっています。

PowerShell DSC for Linuxを試す

それでは早速試していきます。

検証環境

検証環境は

stknohg.hatenablog.jp

の時と同じにして、Windows Server 2012 R2からCentOS 7.2に対してPushでPowerShell Remoting over SSHの環境を構築するというのをやってみました。
この内容にした理由は、単純すぎるConfigurationだと面白くないのと、あと個人的に便利かなと思ったからです。

ただしPowerShell 4.0のDSCだといろいろつらかったのでWindowsPowerShellは5.0に上げています。

1. CentOSの初期設定

最初にCentOSPowerShell DSC for Linuxをインストールします。
OMI ServerとPowerShell DSC for Linuxrpmファイルを順にyum installするだけでOKです。

これだけでCentOS側の準備は完了です。
(いちおう補足しておくと、予めglibcopensslpythonpython-ctypeslibcurlのパッケージが必要*1となりますのでインストールされていない場合はインストールしておいてください。)

2. Windowsの初期設定

以降はWindows側の作業になります。
Configurationを実行可能にするためにLinux向けDSC ResourceのMOFモジュール(nx)をインストールします。

-ScopeはとりあえずCurrentUserにしていますが必要に応じて変えて構いません。

3. Configurationの作成

続けてPowerShell Remoting over SSHの環境構築のためのConfigurationを書きます。
ざっと以下の様な感じになります。

おおまかな流れとしては、

  1. nxPackageリソースでPowerShell on Linuxのインストール
  2. nxPackagenxServiceリソースでSSHdのインストール+設定*2
  3. nxFileLineリソースで/etc/ssh/sshd_configにSubSystemの記述を追加
  4. nxScriptリソースでSSHdをRestart

となっています。

内容としては割とシンプルなものですが、1.に関してなぜかYumでFilePathを指定した場合に依存パッケージのインストールに失敗するため依存パッケージを個別にインストールする様にしています。
また、4.に関してはnxServiceリソースでサービスのReloadやRestartができないためnxScriptリソースで無理やりRestartさせています。
もっとスマートにできる方法があるとうれしいのですが...

4. Push実行

先のConfigurationを実行するとC:\temp[対象ノード名(centos.local)].mofが作成されます。
これをStart-DscConfigurationCentOSに流し込んでやれば完了です。

OMIはTCP 5986番ポート(HTTPS)で待ち受けをしますのでNew-CimSessionOptionで接続オプションの指定を行っています。

実行結果

Start-DscConfigurationした結果はこんな感じになります。

f:id:stknohg:20160916211109p:plain

-Verbose指定しているのですがあまり詳細ログが出てくれません...
CentOS側の/var/opt/omi/log/dsc.logにより詳細なログが出るので、エラーが出た場合はこちらを見ると良いでしょう。

この後PowerShell Remoting over SSHCentOSに接続できる様になり、その結果は以下の様な感じとなります。*3

f:id:stknohg:20160916211240p:plain

きちんと構成されています。

最後に

とりあえずこんな感じです。

公式な情報としてはMSDNLinux 用 Desired State Configuration (DSC) の概要 があります。
こちらも参考になりますのでぜひご覧ください。

*1:https://github.com/Microsoft/PowerShell-DSC-for-Linux#requirements 参照

*2:大抵の場合初期状態でSSHdはインストール済みでしょうが念のための記述です

*3:Windows側のSSH設定などは本エントリでは端折っています