【自動化】データのリストから神エクセルを量産する【改良版】

下記記事の改良版です。ちょっと下準備の手順が減ったのと、ちょっと処理速度が上がりました。

https://qiita.com/pippi1013/items/fe37b852f36aea97ff81


※当記事に登場する人名地名等はすべて架空です。実在の人物地名その他いかなる個人情報とも関係ありません。
※※当記事は随時サイレント修正しています。コードの修正や重大なバグがあった場合のみお知らせいたします。

:::note info
手順通りに下準備して実行するだけなので、制限のかかっていないWindowsのPCならソフトのインストール等をしなくてもできます。実行するだけならプログラミング知識も不要です。
:::

## これを
![元データ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/596218e2-317f-d3cd-7e4b-023fd3e66102.png)

## こうする（そしてたくさんつくる）
![実行結果.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/51125b0a-e804-d5fa-0e62-6d7f8fda1df4.png)

データを雛形にひとつひとつコピペすればいいのですが、とにかく数が多いので自動化します。
ひな形のフォーマットは問わないので、「指摘なし」のレビュー記録表なんかも量産できます。


## どうやる

### ① テンプレを用意する
ひな型となるファイルを用意し、「target.xlsx」という名前で保存します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/84455a47-c491-5b35-709b-9a2718f2d644.png)

※以前の記事ではひな型は「template.xlsx」でしたが、今回はtarget.xlsxです

### ② データを用意する
下記画像のようなデータを用意し、「data.xlsx」という名前で保存します。

:::note info
・1行目を見出し行とすること
・A列に生成予定のファイル名を書いておくこと
・データ部分に数式を使っていてもOK
:::

![データ編集.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/b404cda9-a6e4-41ee-4e8a-34b99684e44b.png)

### ③ ファイルを2つ新規作成する

下記コードをメモ帳にコピーし、指定の通りに名前を付けて保存します。

```batchfile:Excelファイルを量産.bat
powershell -NoProfile -ExecutionPolicy Unrestricted "./Excelファイルを量産.ps1" >> Excelファイルを量産.log
exit
```

```powershell:Excelファイルを量産.ps1
####################
#下準備：
#　「data.xlsx」データのリスト。1行目は見出し行、A列は生成予定のファイル名とする　数式を使ってもOK
#　「target.xlsx」生成予定のテンプレート
#　当ps1ファイルの設定部分(10行目～)に情報を入力する
#準備ができたらbatファイルをたたく
####################

####################
#設定
$tgtPath = Convert-Path "./target.xlsx"
$srcPath = Convert-Path "./data.xlsx"
#
#ひな形が何シート目にあるか
$tgtSheetNo = 1
#
#データが何シート目にあるか
$srcSheetNo = 1
#
#セルの設定
#コピー先のセルを、data.xlsxのデータシートの列数分だけ定義する
$tgtCellMap = @{
    #A列はファイル名なので定義しない
    "B" = "G5" #B列のデータのコピー先
	"C" = "AA5" #C列のデータのコピー先（以下同様にD,E,F…の要領でコピー先をリストに追加）
	"D" = "R8"
	"E" = "AF3"
}
#
####################


#　アセンブリの読み込み インプットボックス表示に必要
Add-Type -Assembly System.Windows.Forms

function Log($str) {
	$timestamp = Get-Date
	Write-Host "$timestamp $str"
}

function NumToAlphabet($num) {

    if($num -eq 0) {
        return $num
    }
}


try {

	Log "********* Excelファイルを量産.ps1 起動 ***********"

	$msgBoxTxt = "実行前にテンプレートを用意してください。用意手順はソースをご覧ください。実行してもよろしいですか？"
	$msgBoxInput = [System.Windows.Forms.MessageBox]::Show($msgBoxTxt,'アプリの実行確認','YesNo','Question')

	if($msgBoxInput -ne "Yes") {
		Log "ユーザー入力により処理を中止します。"
		Exit
	}
	
	# エクセルを開く準備
	$excel = New-Object -ComObject Excel.Application
	$excel.Visible = $false
	$excel.DisplAyalerts = $false
    $excel.ScreenUpdating = $false
    $excel.EnableEvents = $false

   	$updateLinks = 0
	$isReadOnly = $false

	try {
		#srcブックを開く
		$srcBook = $excel.Workbooks.Open($srcPath, $updateLinks, $isReadOnly)
	}
	catch {
		Log "エクセルのオープンでエラーが発生しました。"
		throw
	}
	
	try {

        #srcブック1シート目からデータを取得
		$srcSheet = $srcBook.Sheets.Item($srcSheetNo)
		$maxRowCnt = $srcSheet.UsedRange.Rows.Count
		
		#1列目は見出し行なので2から始める
		for($rowIndex = 2; $rowIndex -le $maxRowCnt; $rowIndex++) {
		
			$bookName = $srcSheet.Cells($rowIndex, 1).Text

			if([string]::IsNullOrEmpty($bookName)){
                Log "A列が未入力の行はスキップします。"
                continue
			}
			
			Log "$bookName を作成中..."
			
			#tgtファイルを開く
			$tgtBook = $excel.Workbooks.Open($tgtPath, $updateLinks, $isReadOnly)
			$tgtSheet = $tgtBook.Sheets.Item($tgtSheetNo)
			
			$tgtCellMap.Keys | ForEach-Object {

				$tgtCellStr = $tgtCellMap[$_]
                $srcColumnStr = $_
				
				try {
                    $text = $srcSheet.Range($srcColumnStr + $rowIndex).Text
                }
				catch {
                    Log ("データを取り出す際にエラーが発生しました。「セルの設定」左側を確認してみてください。エラーが発生したセル：" + $srcColumnStr + $rowIndex)
					throw
				}
				
				$tgtSheet.Activate()
				try {
                    $tgtSheet.Range($tgtCellStr) = $text
				}
				catch {
                    Log ("取り出したデータをテンプレートに書き込む際にエラーが発生しました。「セルの設定」右側を確認してみてください。エラーが発生したセル：" + $tgtCellStr)
					throw
				}
			}
			
			$path = Join-Path (Convert-Path .) $bookName
			
			$tgtSheet.Cells(1,1).Select() | Out-Null
			
			$tgtBook.SaveAs($path)
			$isSaveChanges = $true
			$tgtBook.Close($isSaveChanges)
			
			Log "$bookName を作成しました。"
			
        }# End of for
	}
	catch {
		Log "ファイルの作成でエラーが発生しました。"
		throw
	}
}
catch {
	$errorStr = $error[0] | Out-String
	Log $errorStr
	Log "エラーが発生したため、処理を終了します。"
}
finally {
    try {
		if ($excel -ne $null) {
			$excel.Quit()
		}
		$tgtSheet = $null
		$srcSheet = $null
		$tgtBook = $null
		$srcBook = $null
		$excel = $null
		[System.GC]::Collect()
	}
	catch {
		Log "finally処理の中でエラーが発生しました。"
		$errorStr = $error[0] | Out-String
		Log $errorStr
    }
	Log "********* Excelファイルを量産.ps1 終了 ***********"
	pause
}

```

### ④ Excelファイルを量産.ps1 の設定を編集する
ひな形シート・データシートが何番目にあるか（デフォルトでは1）、何列目のデータをどのセルにコピーするのかを書きます
"Listシートの列のアルファベット" = "ひな形シートのコピー先のセル" と書いていきます
コピー先のセルが結合されている場合は一番左上のセルだけを書けばよいです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/11fd232f-2d94-5cc8-4c89-9f18de367c67.png)

### ⑤ data.xlsx、target.xlsx、Excelファイルを量産.ps1を保存して閉じる

### ⑥ Excelファイルを量産.batをダブルクリック
![キャプチャ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/9bb98cdf-b78b-096e-d3a9-4755b68c8a4f.png)
準備できてますか？と訊いてくるので、「はい(Y)」を押してください。

:::note warn
処理中はPCを操作しないでください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/21a1f5c5-7b6f-abd2-bfad-64364cad1313.png)
この表示になったら処理終了です。Enterキーを押してください。

### ⑦実行結果
ファイルが作成されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3505596/cc8b311f-24d4-46ac-a31a-ed765cb89943.png)
Excelファイルを量産.logというファイルも生成されます。ログなので適宜削除してください。

## 改善したこと
- 処理が速くなった
前回は、ExcelとExcelを反復横跳びしデータを値コピペ→最後に書式コピペという力業で処理していたのですが、値コピペを下記のように記述することで、反復横跳びをやめクリップボードを介さなくなったので早くなりました。書式コピペもいらなくなりました
`$tgtSheet.Range($tgtCellStr) = $srcSheet.Range($srcColumnStr + $rowIndex).Text`
- 下準備が減った
  前述の「最後に書式コピペ」がいらなくなったので下準備が減りました
- $srcSheet.UsedRange.Rows.Count を使うことで、Aセルに値がない場合はスキップして次の行を処理できるようになりました（前はAセルに値がなかったら処理をやめていた）



## 課題
- セルの設定がまだるっこしい
- 最初はデータを配列に格納していたのだが、RowとColumnでループを回して必要なセルだけ取り出すのが想像以上に面倒だったのでやめました。結果的にコードのスマートさがありません
- 生成した神エクセルの再利用の方が重要？

## 参考資料

http://powershell.web.fc2.com/Html/Data/2012/12/12/0000.html

http://kamifuji.dyndns.org/PS-Support/PS_Excel/index.html

https://qiita.com/miyamiya/items/161372111b68bad0744a

https://qiita.com/ishi720/items/bac74397ec3521f8f868

https://shift101.hatenablog.com/entry/2021/05/29/223218