はじめに
先日、複数のExcelファイルをCSVファイルへ変換する必要がでてきました。
1ファイルずつ手でExcelを操作すると少々手間がかかりそうです。でも、PowerShellを利用すれば、一括で変換できそうです。
そこで、PowerShellを利用して、ExcelファイルのデータをCSVファイルに変換するスクリプトを書いてみました。
前提/環境
次の環境で、動作確認しました。
- Windows 10
- PowerShell 5.1
プログラムでは、Excelの機能を呼び出しているので、Excelがインストールされている必要があります。
基本的なコード
下記の関数では、Excelの機能を使って、1つのExcelファイルを読み込み、各シートをそれぞれCSVファイルとして保存します。
Excelファイルを開く際、相対パスだとうまく動かなかったので、絶対パスに変換する処理を入れています。
正常終了時、またはエラー終了時に、Excelプロセスを終了させるため、Quitメソッドを呼び出しています。ただし、呼び出してもすぐには終了しないこともあるようです。
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ファイルのパスを指定しても動作します。
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プロセスの開放処理を追加したりしました。
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
- TryPowerShell/ExcelToCsv3.ps1 at master · kurukurupapa/TryPowerShell