PowerShellでExcelファイルをCSV形式に変換する

# はじめに

先日、複数のExcelファイルをCSVファイルへ変換する必要がでてきました。
1ファイルずつ手でExcelを操作すると少々手間がかかりそうです。でも、PowerShellを利用すれば、一括で変換できそうです。
そこで、PowerShellを利用して、ExcelファイルのデータをCSVファイルに変換するスクリプトを書いてみました。

## 前提/環境

次の環境で、動作確認しました。

- Windows 10
- PowerShell 5.1

プログラムでは、Excelの機能を呼び出しているので、Excelがインストールされている必要があります。

# 基本的なコード

下記の関数では、Excelの機能を使って、1つのExcelファイルを読み込み、各シートをそれぞれCSVファイルとして保存します。
Excelファイルを開く際、相対パスだとうまく動かなかったので、絶対パスに変換する処理を入れています。
正常終了時、またはエラー終了時に、Excelプロセスを終了させるため、Quitメソッドを呼び出しています。ただし、呼び出してもすぐには終了しないこともあるようです。

```powershell
function ConvExcelToCsv($InPath) {
  $fullInPath = Resolve-Path $InPath
  $basePath = $fullInPath -replace '\.xls(.)?', ''
  $excel = New-Object -ComObject Excel.Application

  try {
    # 既に同名CSVファイルがあっても確認ダイアログを表示しない（上書きする）。
    $excel.DisplayAlerts = $false

    $excel.Workbooks.Open($fullInPath) | ForEach-Object {
      $_.Worksheets | ForEach-Object {
        $outPath = "$($basePath)_$($_.Name).csv"
        $_.SaveAs($outPath, [Microsoft.Office.Interop.Excel.XlFileFormat]::xlCSV)
      }
    }
  }
  finally {
    $excel.Quit()
  }
}
```

上記では、1つのExcelファイルを対象としていました。複数のExcelファイルを処理するには、次のコードを書きました。
引数\\$InPathに、フォルダーが指定されている場合、そのフォルダー配下のファイルを再帰的に検索し、拡張子がxlsやxlsxなどのExcelファイルを対象に、上述したConvExcelToCsv関数を呼び出しています。
引数\\$InPathにワイルドカードが指定されている場合、ワイルドカードに該当するファイルを処理します。1つのExcelファイルのパスを指定しても動作します。

```powershell
function ConvExcelToCsv2($InPath) {
  if (Test-Path -PathType leaf $InPath) {
    Get-ChildItem -File $InPath | ForEach-Object {
      ConvExcelToCsv $_.FullName
    }
  } else {
    Get-ChildItem -File $InPath -Recurse -Include ('*.xls', '*.xls?') | ForEach-Object {
      ConvExcelToCsv $_.FullName
    }
  }
}
```

# もう少し複雑なことをしたい場合

特定のシートや、特定のセル範囲だけをCSVファイルに出力したい場合、以下のように記述しました。
引数\\$InSheetにシート名を指定すると、指定したシートのみを処理対象にします。未指定なら全シートを処理。
引数\\$InRangeに"A1C3"のようなセル範囲を指定すると、指定したセル範囲のみを処理対象にします。未指定なら全セル。
引数\\$OutPathには、出力先のCSVファイルパスを指定します。未指定なら、入力Excelファイルの拡張子をcsvに変更したパスとしています。
また、Excel処理をすこし慎重に記述してみました。Excelファイルを読み込む時に読み取り専用モードにしたり、Excelファイルクローズ時に変更を保存しないオプションを指定したり、Excelプロセスの開放処理を追加したりしました。

```powershell
function ConvExcelToCsv($InPath, $InSheet, $InRange, $OutPath) {
  $fullInPath = Resolve-Path $InPath
  if (!$OutPath) {
    $dir = [System.IO.Path]::GetDirectoryName($fullInPath)
    $name = [System.IO.Path]::GetFileNameWithoutExtension($fullInPath)
    $OutPath = Join-Path $dir ($name + '.csv')
  }
  if (Test-Path $OutPath) {
    Remove-Item $OutPath
  }
  $excel = New-Object -ComObject Excel.Application

  try {
    $excel.Visible = $false
    $excel.DisplayAlerts = $false

    # Excelファイルを開く。
    # UpdateLinks=0 外部参照(リンク)を更新しない
    # ReadOnly=$true 読み取り専用モード
    $excel.Workbooks.Open($fullInPath, 0, $true) | ForEach-Object {
      # シートを処理する。
      $sheets = $_.Worksheets
      if ($InSheet) {
        $sheets = $_.Worksheets($InSheet)
      }
      $sheets | ForEach-Object {
        # セルを処理する。
        $range = $_.UsedRange
        if ($InRange) {
          $range = $_.Range($InRange)
        }
        $range.Rows | ForEach-Object {
          $line = ''
          $_.Columns | ForEach-Object {
            if ($_.Column -gt 1) {
              $line += ','
            }
            $line += $_.Text
          }
          $line | Out-File -Append -Encoding Default $OutPath
        }
      }

      # Excelファイルを閉じる。
      # SaveChanges=$false 変更を保存しない
      $_.Close($false)
      # 次の書き方も可能。
      # $excel.Workbooks.Close()

      Write-Verbose "$OutPath を保存しました。"
    }
  }
  finally {
    # Excelプロセスを終了する。
    $excel.Quit()
    # 念のため、Excelプロセスが残らないように、COMオブジェクトを開放する。
    [System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null
    # 即時にExcelプロセスを終了させるため、GCを実行する。
    [System.GC]::Collect()
    [System.GC]::WaitForPendingFinalizers()
  }
}
```

# おわりに

PowerShellで、ExcelファイルをCSVファイルに変換するコードを説明いたしました。

上述のコードをスクリプトファイルにしたものは、次の場所に置いてあります。
- [TryPowerShell/ExcelToCsv1.ps1 at master · kurukurupapa/TryPowerShell](https://github.com/kurukurupapa/TryPowerShell/blob/master/File/Excel/ExcelToCsv1.ps1)
- [TryPowerShell/ExcelToCsv3.ps1 at master · kurukurupapa/TryPowerShell](https://github.com/kurukurupapa/TryPowerShell/blob/master/File/Excel/ExcelToCsv3.ps1)

# 参考サイト

- [PowerShellでExcelファイルを読み込んでみる - Qiita](https://qiita.com/kurukurupapa@github/items/c17b80412341b8e463cc)
- [PowerShellでExcelを操作する](https://zenn.dev/kumarstack55/articles/20210718-powershell-excel)
- [Workbooks.Open メソッド (Excel) | Microsoft Docs](https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbooks.open)
- [Workbook.SaveAs メソッド (Excel) | Microsoft Docs](https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbook.saveas)
- [Workbook.Close メソッド (Excel) | Microsoft Docs](https://docs.microsoft.com/ja-jp/office/vba/api/excel.workbook.close)