PowerShellでPDF Fill Form

PDFフォームにデータを差し込むことができればどんなに楽だろうか？
上手くいけばPDFフォームが帳票テンプレートして使えるんじゃないだろうか。

Windowsでは

- PowerShell標準装備
- PowerShellはdllからライブラリを呼べる

iTextSharpというiTextをC#で弄れるライブラリが存在する。
PowerShellからiTextSharpを呼べばPDFフォームにデータを差し込めるんじゃないだろうか。

PowerShellでPDF Fill Formをやってみたいと思います。

## 見つけた！
そのまんまのを見つけてしまった。(^-^;
[How To Fill In PDF Forms Using PowerShell](http://www.tomsitpro.com/articles/powershell-fill-pdf-forms,2-38.html)

このScriptちと問題が…。

- Find-iTextLibrary を実行すると常にiTextSharp.dllをダウンロード
- Get-Fields でdll指定しないとFind-iTextLibraryを即実行
- 既にdllがあると解凍時にエラー吐きまくる

ウザい！
使いやすいように改造することにする。

## 改造
もう何もかも内蔵してCmdletとして呼び出せばいいんだよ。
下記の方針で行きます。

- Module化
- dll内蔵

### Module化
PowerShellGetに登録は却下。
だって僕のコードじゃないんだもの。

個人で使うライブラリは $env:USERPROFILE\Documents\WindowsPowerShell/Modules に置きます。

```posh:Module化
# フォルダ作成
PS> mkdir $env:USERPROFILE\Documents\WindowsPowerShell/Modules\PdfForm
# PdfForm.psm1をダウンロード
PS> Invoke-WebRequest -Uri https://raw.githubusercontent.com/adbertram/Random-PowerShell-Work/master/Random%20Stuff/PdfForm.psm1 -OutFile $env:USERPROFILE\Documents\WindowsPowerShell/Modules\PdfForm\PdfForm.psm1

```
iTextSharpも用意

```posh:iTextSharp.dll
# iTextSharp.dllをダウンロード GitHubにあるよ。
PS> Invoke-WebRequest -Uri 'https://github.com/itext/itextsharp/releases/download/5.5.10/itextsharp-all-5.5.10.zip' -OutFile $env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom\itextsharp.zip
# 解凍
PS> cd $env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom
PS> Expand-Archive $env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom\itextsharp.zip
PS> Expand-Archive $env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom\itextsharp\itextsharp-dll-core.zip
# このitextsharp.dllを使うようにする。
PS>  ls .\itextsharp-dll-core

    ディレクトリ: C:\Users\username\Documents\WindowsPowerShell\Modules\PdfFrom\itextsharp-dll-core

Mode                LastWriteTime         Length Name                                                                                                                                                            
----                -------------         ------ ----                                                                                                                                                            
-a----       2016/10/07     15:33        4059136 itextsharp.dll                                                                                                                                                  
-a----       2016/10/07     15:33        3164846 iTextSharp.xml

```

PdfForm.psm1を下記のように書き換えてしまう。

```posh:PdfForm.psm1
#Requires -Version 4

## Find-iTextLibraryは削除


function Get-PdfFieldNames
{
	[OutputType([string])]
	[CmdletBinding()]
	param
	(
		[Parameter(Mandatory)]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('\.pdf$')]
		[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
		[string]$FilePath,
		
		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('\.dll$')]
		[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
		#[string]$ITextLibraryPath = (Find-ITextSharpLibrary).FullName
        [string]$ITextLibraryPath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom\itext-core-dll\itextsharp.dll"
	)
	begin
	{
		$ErrorActionPreference = 'Stop'
		## Load the iTextSharp DLL to do all the heavy-lifting 
		[System.Reflection.Assembly]::LoadFrom($ITextLibraryPath) | Out-Null
	}
	process
	{
		try
		{
			$reader = New-Object iTextSharp.text.pdf.PdfReader -ArgumentList $FilePath
			$reader.AcroFields.Fields.Key
		}
		catch
		{
			$PSCmdlet.ThrowTerminatingError($_)
		}
	}
}

function Save-PdfField
{
	[CmdletBinding()]
	param
	(
		[Parameter(Mandatory)]
		[ValidateNotNullOrEmpty()]
		[Hashtable]$Fields,
		
		[Parameter(Mandatory)]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('\.pdf$')]
		[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
		[string]$InputPdfFilePath,
		
		[Parameter(Mandatory)]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('\.pdf$')]
		[ValidateScript({ -not (Test-Path -Path $_ -PathType Leaf) })]
		[string]$OutputPdfFilePath,
		
		[Parameter()]
		[ValidateNotNullOrEmpty()]
		[ValidatePattern('\.dll$')]
		[ValidateScript({ Test-Path -Path $_ -PathType Leaf })]
		#[string]$ITextSharpLibrary = (Find-ITextSharpLibrary).FullName
		[string]$ITextLibraryPath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom\itext-core-dll\itextsharp.dll"
	)
	begin
	{
		$ErrorActionPreference = 'Stop'
	}
	process
	{
		try
		{
			$reader = New-Object iTextSharp.text.pdf.PdfReader -ArgumentList $InputPdfFilePath
			$stamper = New-Object iTextSharp.text.pdf.PdfStamper($reader, [System.IO.File]::Create($OutputPdfFilePath))
			
			## Apply all hash table elements into the PDF form
			foreach ($j in $Fields.GetEnumerator())
			{
				$null = $stamper.AcroFields.SetField($j.Key, $j.Value)
			}
		}
		catch
		{
			$PSCmdlet.ThrowTerminatingError($_)
		}
		finally
		{
			## Close up shop 
			$stamper.Close()
			Get-Item -Path $OutputPdfFilePath
		}
	}
}

```

保存します。
これでModule完成です。

```posh:load
# 通常はこれで読込む
PS> Import-Module PdfForm
# ダメならフルパス指定で読込
PS> Import-Module "$env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfForm"
```

これで使える！

## 使用方法

```posh:フォームのフィールド名を取得
PS> Get-PdfFieldNames -FilePath .\pdfform.pdf
name
gender

```

```posh:フィールドにデータを埋めて出力
PS>  Save-PdfField -Fields @{'name'='オレオレ';'gender'='不明'} -InputPdfFilePath .\pdfform.pdf -OutputPdfFilePath .\fill.pdf


    ディレクトリ: C:\Users\yusuke


Mode                LastWriteTime         Length Name                                                                                                                                                            
----                -------------         ------ ----                                                                                                                                                            
-a----       2018/02/21     11:18           6707 fill.pdf

```
AdobeReaderで開いてみましょう。

<img src="https://qiita-image-store.s3.amazonaws.com/0/24550/a66409a7-4eab-b656-2c6d-829b469882ce.png" width=50%>

動いてますね。

### 複数のデータを差込

```posh:ソース
Write-Output "csvデータ読み込み"
$data=Import-Csv -Path formdata.csv -Delimiter `t
$data

Import-Module $env:USERPROFILE\Documents\WindowsPowerShell\Modules\PdfFrom

Write-Output " "
Write-Output "FieldNameを取得"
Get-PdfFieldNames -FilePath .\pdfform.pdf

Write-Output " "
Write-Output "データ差込実行"
$i=0
foreach($d in $data){
     Save-PdfField -InputPdfFilePath .\pdfform.pdf -OutputPdfFilePath .\fill$i.pdf -Fields @{'name'=$d.name;'gender'=$d.gender}
     $i=$i+1
}

```

```posh:実行結果
PS> fillforms.ps1
csvデータ読み込み

name      gender
----      ------
のびた       男     
ドラえもん     男     
しずか       女     
 
FieldNameを取得
name
gender
 
データ差込実行
fill0.pdf       
fill1.pdf       
fill2.pdf       

```
fill2.pdfに しずかちゃんが入っているはず。

![output1.png](https://qiita-image-store.s3.amazonaws.com/0/24550/4f8c18bf-339d-f230-3e3d-4a65893bfd46.png)

OK。
出来てるね。

## Lisences
サンプルソースなのでご自由にお使い下さい。
但し、iTextSharp.dllのライセンスがAGPLv3なので商用時にはiTextにライセンス料を払う必要があるかと。

[iTextSharp Lisence AGPLv3](https://github.com/itext/itextsharp/blob/develop/LICENSE.md)