PowerShellで複数のExcelファイルをpdf化する

<br>
ファイル一気にpdf化したい、でもソフトウェア入れたくないなあ……ってとき、スクリプト書くこと自体はそんなに珍しいことではないけど、それをPowerShellでっていうとあんまり聞かない気がするのでチャレンジ。


# 目次
[1. スクリプト仕様](#1-スクリプト仕様)
[2. コード](#2-コード)
[3. 処理詳細](#3-処理詳細)
[4. おわりに](#4-おわりに)



# 1. スクリプト仕様
* 指定したフォルダ内の拡張子が.xlsxのファイルを全てpdf化する（指定したフォルダの子フォルダ内のファイルはpdf化の対象外とする）
* 変換対象のファイルは全てのシートをpdf化する（非表示に設定しているシートはpdf化しない）
* pdfファイルは、変換元のxlsxファイルと同じフォルダ内に出力する
* pdf化後、元のxlsxファイルは削除しない
* pdf化する際、同じ名前のファイルが既にある場合は上書きする

# 2. コード
全体としてはこんな感じ。
これを実行するとC:\hogeフォルダ直下の.xlsxファイルをpdf化するので、
適宜\\$pathのパスを変更して実行する。

```PowerShell
#pdf化対象フォルダと拡張子を指定
$path = "C:\hoge"
$ext = ".pdf"

#変数初期化
$files = $null
$files_name = $null
$book = $null
$pdffile = $null


#変換対象ファイル(拡張子が.xlsxのファイル)を取得
$files = Get-ChildItem $path -Filter *.xlsx

#変換対象ファイルのフルパスを取得
$files_name = $files.FullName

#Excelアプリケーションをオブジェクトとして格納
$obj = New-Object -ComObject Excel.Application

#Excelをバックグラウンド実行
$obj.Visible = $False

#警告・メッセージを表示しない
$obj.DisplayAlerts = $False


foreach ($f in $files_name){
    
    #変換対象ファイルを開く
    $book = $obj.Workbooks.Open($f)

    #変換対象ファイルをpdfとしてエクスポート
    $book.ExportAsFixedFormat(0, $f)

    #ファイル名に".xlsx"が残ってしまうのでリネームする
    $pdffile =  $f + $ext
    Get-ChildItem $pdffile | Foreach-Object {$newname = $_.Name -Replace ".xlsx",""}
    
    #Rename-Itemだと上書きができないためMove-Itemでリネーム
    Move-Item $pdffile -Destination "$path\$($newname)" -Force

}

#終了処理
$obj.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($obj)
[GC]::Collect() 

```

# 3. 処理詳細
### 1. COMオブジェクトの作成

```PowerShell
#Excelアプリケーションをオブジェクトとして格納
$obj = New-Object -ComObject Excel.Application
```

New-ObjectコマンドでCOMオブジェクトインスタンスを作成し、$objに格納する。
-ComObjectオプションでExcelを指定するので、このオプションを例えばWord.Applicationに書き換えるとWordの操作ができる。


COMオブジェクトの詳細は以下参照。

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/new-object?view=powershell-7.3


```PowerShell
#Excelをバックグラウンド実行
$obj.Visible = $False

#警告・メッセージを表示しない
$obj.DisplayAlerts = $False
```
\\$obj.Visibleを\\$Trueにすると、スクリプト実行したときに変換対象のファイルが順次開かれてまあまあ鬱陶しいので\\$Falseでバックグラウンド実行にしておく。

あとポップアップが出て処理が止まるのは嫌なのでDisplayAlertsも$Falseにしておく。
バックグラウンド実行にしておけばDisplayAlertsの設定はいらないかもしれないけど念のため。

### 2. pdfとしてエクスポート&リネーム
変換対象のファイルの数だけforeachで処理をループさせる。

```PowerShell
foreach ($f in $files_name){
    
    #変換対象ファイルを開く
    $book = $obj.Workbooks.Open($f)

    #変換対象ファイルをpdfとしてエクスポート
    $book.ExportAsFixedFormat(0, $f)

    #ファイル名に".xlsx"が残ってしまうのでリネームする
    $pdffile =  $f + $ext
    Get-ChildItem $pdffile | Foreach-Object {$newname = $_.Name -Replace ".xlsx",""}
    
    #Rename-Itemだと上書きができないためMove-Itemでリネーム
    Move-Item $pdffile -Destination "$path\$($newname)" -Force

}
```
#### エクスポート
pdfとしてエクスポートするコマンドは以下記事を参考にさせていただきました。

https://qiita.com/shiroma77_/items/7115865d6474eda63479


ExportAsFixedFormatメソッドの詳細は以下参照。

https://learn.microsoft.com/ja-jp/office/vba/api/excel.workbook.exportasfixedformat


ExportAsFixedFormatの0はpdf形式で出力。1にするとxps形式で出力。

https://learn.microsoft.com/ja-jp/office/vba/api/excel.xlfixedformattype

pdfとしてエクスポートするだけだとファイル名に.xlsxが残ってしまう(例：fuga.xlsx.pdf)、
のでファイル名の.xlsxを消す。

#### リネーム
Rename-Itemは-Forceオプションつけても上書きができないので、Move-Itemを使う。
（Rename-Itemの-Forceは読み取り専用ファイルなどの名前を変更するときに使う。）

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/rename-item?view=powershell-7.3


### 3. オブジェクトの解放
オブジェクトを生成したら解放も忘れずに。
COMオブジェクトを解放するロジックが無いと複数回実行したときに往々にしてエラーが出て、スクリプトが最後まで実行できなくなる。（開発中よくやらかして仕方なく再起動した）

```PowerShell
#終了処理
$obj.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($obj)
[GC]::Collect() 
```
上記でCOMオブジェクトを解放して念のためガベージコレクションする。

解放しないとどうなるの？というのは下記参照。メモリ圧迫したりアプリケーションの挙動がおかしくなる可能性がある。

https://social.msdn.microsoft.com/Forums/ja-JP/0d9c6273-bade-4f6a-a0de-5adb748d15eb/office?forum=officesupportteamja

# 4. おわりに
今まで何回かPowerShellでpdf化スクリプト書こうとしてたけど、
エクスポートのコマンドとCOMオブジェクトのリリースでふんづまってしまい、今回、三連休で腰を据えてやってようやく動くものが書けた。

WordとかPowerPointのpdf化もこのスクリプトをベースにすればできそうなので、スクリプト改修して.docx, .xlsx, .pptx対応できるようにしたい。

リネームのところはもう少しスマートに書けるかな...?それは今後の課題ということで。