📊

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)

https://learn.microsoft.com/ja-jp/office/vba/api/excel.range.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)

https://learn.microsoft.com/ja-jp/office/vba/api/excel.xlfixedformattype

非表示・非通知オプション(自動化)

  • 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