PowerShellスクリプトでPowerPointファイルPNG化+まとめて画像操作

Windows上で軽くスクリプトでまとめて何かしたい、みたいなときにPowerShellさんが変態チックで面白かったのでそのメモ。

#やりたいこと

PowerPointで作った資料をまとめて画像化したい。画像化したものは余白でトリミングして適当なマージンつけてとかしたい。

はじめっからPowerPointで作らず別のツールで作れよ、という感じかもしれませんが、ストーリーを考えながらえいやでべたべた貼りつけていくのにはPowerPointが一番慣れてしまっている・・・

以前もJavaでCOMオブジェクトのラッパーとか使って[PowerPoint.Application](http://msdn.microsoft.com/ja-jp/library/office/ff745704(v=office.15).aspx)オブジェクトにアクセスして自動化とかしてたわけですが、Java実行環境は入ってなさそうな人の環境上でも実行する可能性があったので、Windows標準みたいな部分だけでなんとかならないかなあと。

##PowerShell

で、PowerShellさんのことを思い出しました。
以前はcygwin環境だったのでまったくさわってなかったのですが、インフラ管理系のことをはじめてから、できるだけ、すでにインストールされているソフトウェアだけでなんとかするような感じで適応してしまったりで、ちょこちょこさわるようになってきたというのもあり。

この辺の、他のシェルと一線を画す感じの変態仕様がグッときます。

###パイプを使ってコマンド間でオブジェクトを流せる

パイプで `Get-Process | where {$_.ProcessName -eq "chrome"} | select CPU` とか書けるのが素敵です。
コマンド(コマンドレット)間でパイプを通じて.NETオブジェクトが流れていきます。型がついている分、テキストベースのパイプのカジュアルさが失われている感もありますが、パイプを介してオブジェクトがあれこれされていくさまを見るのは嫌いじゃないです。

###.NET FrameworkだけでなくCOMのオブジェクトも生成できる

コマンドラインで `$ppApp = New-Object -com PowerPoint.Application`とかして、`$pptPres = $ppApp.Presentations.Open("hogehoge.pptx")` とかできるのが素敵です。オブジェクトの後始末はそれなりに面倒ではありますが。


#やってみた

PowerPointをPNGにする / 画像の余白を削り取る / マージンを追加する / 一定サイズに収まるようにリサイズする みたいな個々の機能をPowerShellスクリプト(.ps1)にして必要に応じて組み合わせる形を考えています。
各スクリプトをできるだけ独立して作りたかったのと、既存の `ls` (実体は `Get-ChildItem`)とかとうまくつなげたかったので、同じタイプのオブジェクトをパイプに流す格好に。

![pipe.jpg](https://qiita-image-store.s3.amazonaws.com/0/28770/11b053ef-a47e-86b1-1a0a-3fe8689701e1.jpeg)

作ってみたのはこんな感じのスクリプト：https://gist.github.com/yacchin1205/925851027b84240b512f
自分の作業に必要な範囲内なので色々適当。

| ファイル | 機能 | 
| ----------|------|
| Export-PPT2PNG.ps1 | パイプから入力されたファイルパスで示されたファイルを `PowerPoint.Application` で開き、`-Out`オプションで示されたディレクトリに全スライドをPNG画像として保存し、そのパスを出力する | 
| Trim-Image.ps1 | パイプから入力されたファイルパスで示される画像ファイルの余白(ARGB 0xffffffff)部分を切り抜き、`-Out`オプションで示されたディレクトリに保存し、そのパスを出力する | 
| Resize-Image.ps1 | パイプから入力されたファイルパスで示される画像ファイルを`-Width`, `-Height`オプションで示されるサイズ(pixel)にしたがって縮小し、`-Out`オプションで示されたディレクトリに保存し、そのパスを出力する | 
| Add-Image-Margin.ps1 | パイプから入力されたファイルパスで示される画像ファイルを`-Margin`オプションで示されるサイズ(pixel)にしたがって縮小し、`-Out`オプションで示されたディレクトリに保存し、そのパスを出力する。`-MarginTop`, `-MarginBottom`, `-MarginLeft`, `-MarginRight`でマージンを細かく指定することもできる。 | 

使い方としては、これらのファイルを適当なところにおいて、PowerShellのコンソールから、

```
> ls hogehoge.pptx | .\Export-PPT2PNG.ps1 -Out .\work | .\Trim-Image.ps1 -Out .\work | .\Resize-Image.ps1 -Out .\work -Width 700 | .\Add-Image-Margin.ps1 -Out .\out
```

とかすると`hogehoge.pptx`の内容が画像で保存されて色々加工されて`out`ディレクトリに加工された画像が作られます。作業用の`work`ディレクトリにはもろもろ途中状況の画像が保存される。（各ディレクトリは事前に作成してある必要あり）

あと、PowerShellスクリプトを実行するには`Set-ExecutionPolicy`を変更しておく必要があります。
参考：[Set-ExecutionPolicy コマンドレットの使用](http://technet.microsoft.com/ja-jp/library/ee176961.aspx)

#もろもろ感想

###パラメータの設定が楽ちん

`param()` を使ってスクリプトに与えるパラメータが簡単に定義できてよいです。

```ps1:Export-PPT2PNG.ps1
param(
    [Parameter(Mandatory=$True)]
    [string]$out,
    [Parameter(Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
    [Alias('FullName')]
    [String[]]$filePaths
)

...
```

みたいにしてあげると、`-Out`オプションで指定された内容が簡単に変数で参照できるように。

あと、パイプから与えるファイルパスについても、同じくここで宣言していますが、ここでは`FileSystemInfo`オブジェクトの場合は`FullName`プロパティが受け取りたいし一方で単純な文字列も受け取りたいしで `Alias('FullName')` とか書いています。この辺はちと煩雑な気もする・・・


###作業ディレクトリの扱いが.NET FrameworkとPowerShellで別々なのが謎

PowerShellで `cd` とかして設定した作業ディレクトリと、.NET FrameworkのCurrent Directoryは別ものという謎仕様。

```
PS C:\Users\Test\hogehoge> [IO.Directory]::GetCurrentDirectory()
C:\Users\Test
PS C:\Users\Test\hogehoge> Get-Location

Path
----
C:\Users\Test\hogehoge
```

そのせいで、.NETのオブジェクトに気軽に相対パスとかを渡してしまうと「ファイルが作成されないー」みたいに悩んでしまいます。

この回避方法はいくつかあるようですが、今回は `Resolve-Path` を利用して必ずパス表現オブジェクトを取得して.NETオブジェクトに渡すようにしています。

```ps1:Export-PPT2PNG.ps1
...
begin {
    ...
    $outPath = Resolve-Path $out
}
...
```


###エラーでの終了

一見.NETのコードを書いている気になってしまい忘れがちですが、PowerShellスクリプトはデフォルトではエラーが発生しても終了せず、次の行の実行を継続します。
エラーで終了したい場合は、`$ErrorActionPreference` を使ってエラー時の挙動を定義してあげる必要があります。

```ps1:Export-PPT2PNG.ps1
...
begin {
    $ErrorActionPreference = "Stop"
    ...
}
...
```

こんな感じで、OfficeとかCOM経由で機能をさわれたりするアプリケーションに関しては、PowerShellでお気軽に自動化できそうな感じがします。

画像の処理とかは.NETの標準的なオブジェクトの範囲内でスクリプトでロジックを組んでやってみたけれど、やっぱりNuGetとかで画像処理系のライブラリを拾ってきて、それに食わせてあげるのが楽ちんかなと。今回は、あんまり人の環境にインストールされているものを気にしたくなかった（汚したくなかった）のでこんな格好なわけですが・・・