📊
PowerShellでExcel操作
はじめに
Windows標準のPowerShell(5.1 + インストール済み Microsoft Excel)で、Excel 操作を自動化するメモ。外部モジュール(ImportExcel
など)は使用しない。COM(OLE Automation) を直接叩くやり方だけで、作成・読み書き・保存・非表示実行・無人化。
前提・環境
- OS: Windows 10/11(64bit)
- PowerShell: 5.1(64bit推奨)
- Office: Excel 2016 以降
- 参照追加は不要。
New-Object -ComObject Excel.Application
で起動
使用クラス(COMオブジェクト)
-
Excel.Application
…… Excel 本体。Visible
,DisplayAlerts
,ScreenUpdating
,Calculation
,EnableEvents
などの全体設定 -
Workbooks
/Workbook
…… ブックコレクション/単一ブック。Open()
,Add()
,Save()
,SaveAs()
-
Worksheets
/Worksheet
…… シートコレクション/単一シート。Cells
,Range
,UsedRange
-
Range
…… セル/範囲。Value2
,NumberFormat
,Resize
,End()
等 -
[System.Runtime.InteropServices.Marshal]
……ReleaseComObject()
で COM を確実に解放 -
[System.GC]
……Collect()
/WaitForPendingFinalizers()
で最終解放
基本操作
COMオブジェクト作成
$excel = New-Object -ComObject Excel.Application
新規ブック作成
$wb = $excel.Workbooks.Add()
既存ブックのオープン
$filePath = "\path\to\file.xlsx"
$wb = $excel.Workbooks.Open($filePath)
新規シート追加
# 先頭にシート追加
$ws = $wb.Worksheets.Add()
# シート名を変更
$ws.Name = "TestSheet"
# 3番目の位置にシートを追加
$ws = $wb.Worksheets.Add($wb.Worksheets.Item(3))
- 指定位置が範囲外の場合エラー
- シート名が重複する場合エラー
既存シートの取得
$ws = $wb.Worksheets.Item("SheetName") # シート名で取得
$ws = $wb.Worksheets.Item(1) # 1番目のシートを取得
シート削除
- 警告が出るので
$excel.DisplayAlerts = $false
を設定しておく。
# 削除したいシートを取得
$ws = $wb.Worksheets.Item("TestSheet")
# シートを削除
$ws.Delete()
セルの読み書き
-
Value
ではなく、Value2
を使用するとCurrency,Dateデータ型は使用されないので型や精度の影響を避けられる。
$ws.Range('A1').Value2 = 'タイトル'
$ws.Cells.Item(2,1).Value2 = 12345
# 日付は Value2 だと OADate (Double)。読み込み時に必要なら変換
$dt = [DateTime]::FromOADate($ws.Range('B2').Value2)
保存
- 上書き保存時、警告が出ることがあるので
$excel.DisplayAlerts = $false
を設定しておく。
# 上書き保存
$wb.Save()
# 名前を付けて保存
$newFilePath = "\path\to\new_file.xlsx"
$wb.SaveAs($newFilePath)
# 保存せずに閉じる場合
$wb.Close($false) # $false は保存しないオプション
PDF または XPS形式で保存
# ファイル形式を指定して保存(例: PDF形式で保存)
$pdfFilePath = "C:\path\to\file.pdf"
$xlTypePDF = 0 # 0:PDF, 1:XPS
$wb.ExportAsFixedFormat($xlTypePDF, $pdfFilePath)
非表示・非通知オプション(自動化)
- Excel を画面を出さず自動で操作するための代表的オプション。
# 事前に現在値を退避し、最後に戻すのが安全
$excel = New-Object -ComObject Excel.Application
$orig = [ordered]@{
Visible = $excel.Visible
DisplayAlerts = $excel.DisplayAlerts
ScreenUpdating = $excel.ScreenUpdating
EnableEvents = $excel.EnableEvents
}
$excel.Visible = $false # ウィンドウ非表示
$excel.DisplayAlerts = $false # 上書き確認などのダイアログ抑止
$excel.ScreenUpdating = $false # 描画停止(高速化)
$excel.EnableEvents = $false # イベント無効化
# ...処理...
# 後始末で元に戻す
$excel.EnableEvents = $orig.EnableEvents
$excel.ScreenUpdating = $orig.ScreenUpdating
$excel.DisplayAlerts = $orig.DisplayAlerts
$excel.Visible = $orig.Visible
自動計算をオフにしたい場合:
$excel.Calculation = -4135
(xlCalculationManual
)
リンク更新確認を抑止したい場合:$excel.AskToUpdateLinks = $false
書式関連
項目 | 取得プロパティ | 代表例(取得コード) | 備考 |
---|---|---|---|
表示形式(数値書式) | Range.NumberFormat |
$cell.NumberFormat |
ロケール非依存。"0.0" など |
ローカル表示形式 | Range.NumberFormatLocal |
$cell.NumberFormatLocal |
OSロケール依存(例:日本語環境で "yyyy/m/d" など) |
文字色 | Range.Font.Color |
$cell.Font.Color |
BGRの int (例:赤=255) |
フォント名 | Range.Font.Name |
$cell.Font.Name |
例:"Meiryo"
|
フォントサイズ | Range.Font.Size |
$cell.Font.Size |
ポイント |
太字 | Range.Font.Bold |
$cell.Font.Bold |
True/False |
斜体 | Range.Font.Italic |
$cell.Font.Italic |
True/False |
下線 | Range.Font.Underline |
$cell.Font.Underline |
-4142(None)/2(Single) など |
背景色(塗りつぶし) | Range.Interior.Color |
$cell.Interior.Color |
BGRの int
|
塗りつぶしパターン | Range.Interior.Pattern |
$cell.Interior.Pattern |
-4142(None)/1(Solid) など |
横位置揃え | Range.HorizontalAlignment |
$cell.HorizontalAlignment |
-4131(Left)/-4108(Center)/-4152(Right) |
縦位置揃え | Range.VerticalAlignment |
$cell.VerticalAlignment |
-4160(Top)/-4108(Center)/-4107(Bottom) |
折り返して全体を表示 | Range.WrapText |
$cell.WrapText |
True/False |
文字方向(角度) | Range.Orientation |
$cell.Orientation |
角度(-90〜90) |
セル結合 | Range.MergeCells |
$cell.MergeCells |
True/False |
列幅 | Range.ColumnWidth |
$cell.ColumnWidth |
文字数ベース |
行の高さ | Range.RowHeight |
$cell.RowHeight |
ポイント |
罫線(線種) | Range.Borders.LineStyle |
$cell.Borders.Item(1).LineStyle |
-4142(None)/1(Continuous) など |
罫線(色) | Range.Borders.Color |
$cell.Borders.Item(1).Color |
BGRの int
|
罫線(太さ) | Range.Borders.Weight |
$cell.Borders.Item(1).Weight |
2(xlThin)/4(xlMedium)/-4138(xlHairline) 等 |
よく使う定数
COM では列挙体が使いづらいので、ハッシュテーブルなどで最低限だけ定義しておくと便利。
# 保存形式(XlFileFormat)
$XlFileFormat = @{ Xlsx = 51; Xlsm = 52; Xls = 56; Csv = 6 }
# 計算モード(XlCalculation)
$XlCalc = @{ Automatic = -4105; Manual = -4135; Semiautomatic = 2 }
# 移動方向(XlDirection)
$XlDirection = @{ Up = -4162; Down = -4121; ToLeft = -4159; ToRight = -4161 }
その他Tips
使用範囲の列幅・行高を自動調整
$used = $ws.UsedRange
$used.Columns.AutoFit() | Out-Null
$used.Rows.AutoFit() | Out-Null
フィルタ設定(いったん解除→再設定)
# フィルタを解除
$used.AutoFilter() # フィルタ解除
# フィルタを設定(例: 1列目にフィルタを設定)
$used.AutoFilter(1) # 1列目にフィルタを設定
フォーカス位置(A1 を選択)
$ws.Range('A1').Select() | Out-Null
式を設定
- 例:C列に =A列*B列
$ws.Range("C2:C$lastRow").Formula = '=A2*B2'
クリップボード解除
$excel.CutCopyMode = 0 # クリップボード解除で余計なダイアログ回避
指定範囲をテーブル化
- ListObjects.Add(SourceType, Source, LinkSource, XlYesNoGuess)
- 第1引数 1 = xlSrcRange(範囲をテーブル化する)
- 第2引数 $rng = テーブル化する Range
- 第3引数 $null = リンク元(外部接続)の指定。範囲ソースなので未使用
- 第4引数 1 = xlYes(先頭行はヘッダとして扱う)
- ※ 0=xlGuess(Excelに推測させる), 2=xlNo(ヘッダなしで1行挿入)もある
- Name / TableStyle
- テーブル名とスタイル名。TableStyleMedium9 は標準の中間色スタイルのひとつ(他にも TableStyleLight* / Dark* など)。
$range = $ws.Range('A1:E9')
$tbl = $ws.ListObjects.Add(1, $range, $null, 1) # 1=xlSrcRange, 1=HasHeaders
$tbl.Name = 'T_Data';
$tbl.TableStyle = 'TableStyleMedium9'
プロセスが残っているか確認
Get-Process excel -ErrorAction SilentlyContinue | Select-Object Id, MainWindowTitle
- プロセスが残っている場合は出力される
Id MainWindowTitle
-- ---------------
2152 text.xlsx - Excel
10304
使用例
- 後処理等の記載は省略
- Worksheetなど、COMオブジェクトの参照を解放しないとExcelのプロセスが残る場合がある
前後処理のテンプレート化例(参考)
- 「★処理」の部分に処理を実装する
$excel = $null
$wb = $null
$ws = $null
# 元設定退避用
$orig = [ordered]@{
Visible = $null
DisplayAlerts = $null
ScreenUpdating = $null
EnableEvents = $null
}
try {
$excel = New-Object -ComObject Excel.Application
# 元設定を退避して最小限の高速化
$orig.Visible = $excel.Visible
$orig.DisplayAlerts = $excel.DisplayAlerts
$orig.ScreenUpdating = $excel.ScreenUpdating
$orig.EnableEvents = $excel.EnableEvents
$excel.Visible = $false # ウィンドウ非表示
$excel.DisplayAlerts = $false # 上書き確認などのダイアログ抑止
$excel.ScreenUpdating = $false # 描画停止(高速化)
$excel.EnableEvents = $false # イベント無効化
# ----------------------------
# ★処理
# ----------------------------
} finally {
# ブックを閉じる
if ($wb) { try { $wb.Close($false) } catch {} }
# Excel 設定を戻してから Quit(生きていれば)
if ($excel) {
try {
if ($orig.ScreenUpdating -ne $null) { $excel.ScreenUpdating = $orig.ScreenUpdating }
if ($orig.DisplayAlerts -ne $null) { $excel.DisplayAlerts = $orig.DisplayAlerts }
if ($orig.EnableEvents -ne $null) { $excel.EnableEvents = $orig.EnableEvents }
if ($orig.Visible -ne $null) { $excel.Visible = $orig.Visible }
} catch {}
try { $excel.Quit() } catch {}
}
# COM 解放(親子の下位→上位の順)
if ($ws) { [void][Runtime.InteropServices.Marshal]::ReleaseComObject($ws); $ws = $null }
if ($wb) { [void][Runtime.InteropServices.Marshal]::ReleaseComObject($wb); $wb = $null }
if ($excel) { [void][Runtime.InteropServices.Marshal]::ReleaseComObject($excel); $excel = $null }
# GC(残留プロセス対策)
[GC]::Collect(); [GC]::WaitForPendingFinalizers()
}
CSVを読み込んで表を作成
- 下記の例では
Import-Csv
でCSVを読み込み、新規作成したブックのシートに貼り付けている - データ量が多い場合は
StreamReader
の使用なども検討
$csvPath = Join-Path $PSScriptRoot 'sample.csv'
$savePath = Join-Path $PSScriptRoot 'newSample.xlsx'
$wb = $excel.Workbooks.Add();
$ws = $wb.Worksheets.Item(1)
# CSVを読み込み、シートに貼り付け
$data = Import-Csv -Path $csvPath -Encoding UTF8
if ($data.Count -gt 0) {
# ヘッダー行
$headers = $data[0].PSObject.Properties.Name
$colCount = $headers.Count
for ($col = 0; $col -lt $colCount; $col++) {
$ws.Cells.Item(1, $col + 1).Value = $headers[$col]
}
# データ行
for ($row = 0; $row -lt $data.Count; $row++) {
$col = 0
foreach ($prop in $data[$row].PSObject.Properties) {
$ws.Cells.Item($row + 2, $col + 1).Value = $prop.Value
$col++
}
}
$used = $ws.UsedRange
# 表の体裁を整える
$used.Columns.AutoFit() | Out-Null
$used.Rows.AutoFit() | Out-Null
$headerRange = $ws.Range($ws.Cells.Item(1,1), $ws.Cells.Item(1,$colCount))
$headerRange.Font.Bold = $true
$headerRange.Interior.ColorIndex = 15 # 薄い灰色
$used.Borders.LineStyle = 1
# フィルタ設定
[void]$used.AutoFilter(1) # 1列目にフィルタを設定
} else {
$ws.Range('A1').Value2 = 'No Data'
}
# 保存する。
$wb.SaveAs($savePath, 51)
指定ディレクトリ内の全ファイル・全シートを処理
- 以下の例では全シートのフォーカスをA1セルに変更して保存
$root = 'path\to\root'
$extensions = @('.xlsx','.xlsm','.xls')
# 対象ファイル列挙(再帰)
$files = Get-ChildItem -Path $root -Recurse -File | Where-Object { $_.Extension -in $extensions }
foreach ($f in $files) {
try {
# 読み取り専用を避けて開く
$wb = $excel.Workbooks.Open($f.FullName, $null, $false)
# ReadOnlyになってしまった場合はスキップ(別プロセスで編集中など)
if ($wb.ReadOnly) {
Write-Warning "ReadOnlyのためスキップ: $($f.FullName)"
$wb.Close($false)
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($wb); $wb = $null
continue
}
# 全ワークシートを走査
$count = $wb.Worksheets.Count
for ($i=1; $i -le $count; $i++) {
$ws = $wb.Worksheets.Item($i)
try {
# シートをアクティブ化 → A1を選択 → スクロールも左上へ
$ws.Activate() | Out-Null
$ws.Range("A1").Select() | Out-Null
$excel.ActiveWindow.ScrollRow = 1
$excel.ActiveWindow.ScrollColumn = 1
} finally {
if ($ws) { [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($ws); $ws = $null }
}
}
# 上書き保存
$wb.Save()
$wb.Close($false)
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($wb); $wb = $null
Write-Verbose "OK: $($f.FullName)"
} catch {
Write-Warning "失敗: $($f.FullName) : $($_.Exception.Message)"
if ($wb) {
try { $wb.Close($false) } catch {}
[void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($wb); $wb = $null
}
}
}
Discussion