🕌

PowerShellでExcelを操作してみよう

に公開

Excel-Fun.xls*様の勉強会で登壇しました。
https://www.youtube.com/live/vp6aIFM1laI

概要

PowerShellとは

WindowsOSに標準搭載されているCLI・スクリプトツール。
Mac/Linuxにもインストールできる。

https://learn.microsoft.com/ja-jp/powershell/scripting/install/installing-powershell?view=powershell-7.5

PowerShellで何ができる?

  1. Windows の操作

    1. ファイル操作(コピー、移動、削除、圧縮)
    2. レジストリ操作
    3. タスクスケジューラの設定
    4. イベントログの取得・解析
    5. サービスの開始・停止・監視
    6. プロセスやパフォーマンスの監視
  2. アプリケーション制御(特に Office)

    1. Excel、Word、Outlook などの操作(COM オブジェクト)
    2. ファイル作成・データ書き込み・印刷・送信など
    3. Active Directory の操作(ユーザー・グループ管理)
    4. SQL Server などのデータベース操作(ODBC / ADO.NET)
  3. ネットワーク・システム管理

    1. ネットワーク接続確認(Test-Connection / Test-NetConnection)
    2. リモートPCの操作(PowerShell Remoting)
    3. IP設定やFirewallの構成変更
    4. DNS / DHCP サーバーの管理(モジュールあり)
  4. スクリプトやツールの作成

COM(Component Object Model)とは?

  1. コンポーネント化
    ソフトウェアをコンポーネント(部品)化し、それを他のアプリケーションで利用できるようにする。

  2. 言語非依存
    異なる言語で書かれたコンポーネントが相互に通信できるようにする。

  3. プロセス間通信
    異なるプロセス間でのオブジェクトのやりとりできるようにする

VBAよりPowerShellの方がいい場面

基本的にVBAとPowerShell(COMオブジェクト)は同じ操作ができますが
PowerShellがいい場面は下記のとおりです。

  1. 複数のファイルを一括処理したい場合
  2. Excel以外の処理と連携させたい場合
  3. GUIを必要としないバックグラウンド処理
  4. 管理者視点での一括レポート生成やログ集計

操作してみよう

起動

  • powershellで検索する
  • Windows PowerShellをクリックする

ISE(統合開発環境)は開発が中止されていて、PowerShell+VSCodeが推奨されている

コードを実行する

sample.ps1
# Excel COMオブジェクト作成
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true

# 新しいブック作成
$book = $excel.Workbooks.Add()

# シート取得
$sheet = $book.Sheets.Item(1)

# セルに書き込み
$sheet.Cells.Item(1,1).Value = "Hello, Excel!"

コードを実行する2

sample2.ps1
$book.Close($false)
$excel.Quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
  • $excelオブジェクトだけ開放してもプロセスが残る

コードを実行する3

sample3.ps1
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($sheet) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
  • 宣言したすべてのオブジェクトを開放する

ユーザーが宣言したComオブジェクトを格納したすべての変数に対して解放処理を実施する

garbageCollection.ps1
# COMオブジェクトのみを取得
$variables = Get-Variable | Where-Object { 
    $_.Value -is [System.__ComObject] -and $_.Name -notmatch '^env:|^global:|^function:' 
}

Write-Host "リリース予定: $($var.Name)"
foreach ($var in $variables) {
    Write-Host "$($var.Name)"
}

# ユーザーに確認のプロンプトを表示
$confirmation = Read-Host "上記変数をリリースしますか? (yes でリリース)"

# 2回目のループ:リリース処理
if ($confirmation -eq "yes") {
    foreach ($var in $variables) {
        Write-Host "リリース中: $($var.Name)"
        # COMオブジェクトの解放
        [System.Runtime.Interopservices.Marshal]::ReleaseComObject($var.Value) | Out-Null
        # 変数の参照をnullに設定
        $var.Value = $null
    }

    # ガーベジコレクションを強制実行
    [GC]::Collect()
    [GC]::WaitForPendingFinalizers()
    
    Write-Host "COMオブジェクトの解放が完了しました"
}

サンプルコード

https://zenn.dev/towamz/articles/72379343b1c0d6

変数の宣言

  • $を先頭につける
variable.ps1
PS C:\> num = 1
console
num : 用語 'num' は、コマンドレット、関数、スクリプト ファイル、または操作可能なプログラムの名前として認識されません。
名前が正しく記述されていることを確認し、パスが含まれている場合はそのパスが正しいことを確認してから、再試行してください
。
発生場所 行:1 文字:1
+ num = 1
+ ~~~
    + CategoryInfo          : ObjectNotFound: (num:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException
  • $を付けないとコマンドや関数と認識される

ファイル・ディレクトリ操作

1

Get-ChildItem.ps1
Get-ChildItem
console
PS C:\Users\towamz\public\Excel-Fun> Get-ChildItem


    ディレクトリ: C:\Users\towamz\public\Excel-Fun


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2025/07/16     17:06                【VBA】007_月次請求書を作成_他の人の作品
-a----        2025/05/09     22:55          28112 【VBA】001_スケジュールへの着色.xlsm
-a----        2025/06/02     12:14          22579 【VBA】002_カラー定数一覧.xlsx
-a----        2025/06/01     14:44          23134 【VBA】003_領収書をPDF出力.xlsm
-a----        2025/06/12      3:08          94565 【VBA】004 カレンダー入力フォームを作ってみよう.xlsm
-a----        2025/03/24     13:22         577417 【VBA】005_プラモの部品構成表.xlsm
-a----        2025/04/07     11:24          62747 【VBA】007_月次請求書を作成.xlsm
-a----        2025/05/04     21:56         752655 【VBA】008_社員名簿.csv
-a----        2025/05/31     22:34         217963 【VBA】009_人事データからクロス表を作成.xlsm
  • オプションなしでカレントディレクトリのファイル・サブディレクトリ一覧が表示される

2

Get-ChildItem.ps1
Get-ChildItem -File
console
PS C:\Users\towamz\public\Excel-Fun> Get-ChildItem -File


    ディレクトリ: C:\Users\towamz\public\Excel-Fun


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2025/05/09     22:55          28112 【VBA】001_スケジュールへの着色.xlsm
-a----        2025/06/02     12:14          22579 【VBA】002_カラー定数一覧.xlsx
-a----        2025/06/01     14:44          23134 【VBA】003_領収書をPDF出力.xlsm
-a----        2025/06/12      3:08          94565 【VBA】004 カレンダー入力フォームを作ってみよう.xlsm
-a----        2025/03/24     13:22         577417 【VBA】005_プラモの部品構成表.xlsm
-a----        2025/04/07     11:24          62747 【VBA】007_月次請求書を作成.xlsm
-a----        2025/05/04     21:56         752655 【VBA】008_社員名簿.csv
-a----        2025/05/31     22:34         217963 【VBA】009_人事データからクロス表を作成.xlsm
  • Fileオプションでファイルのみ表示できる

3

Get-ChildItem.ps1
Get-ChildItem -File | ForEach-Object {
    $date = Get-Date -Format "yyyyMMdd"
    Write-Host ($_.BaseName + "_" + $date + $_.Extension)
}
console
PS C:\Users\towamz\public\Excel-Fun> Get-ChildItem -File | ForEach-Object {
>>     $date = Get-Date -Format "yyyyMMdd"
>>     Write-Host ($_.BaseName + "_" + $date + $_.Extension)
>> }
【VBA】001_スケジュールへの着色_20250717.xlsm
【VBA】002_カラー定数一覧_20250717.xlsx
【VBA】003_領収書をPDF出力_20250717.xlsm
【VBA】004 カレンダー入力フォームを作ってみよう_20250717.xlsm
【VBA】005_プラモの部品構成表_20250717.xlsm
【VBA】007_月次請求書を作成_20250717.xlsm
【VBA】008_社員名簿_20250717.csv
【VBA】009_人事データからクロス表を作成_20250717.xlsm
  • [$_]でパイプで受け取ったオブジェクトを示す
  • このコードではFileオブジェクトを受け取っているので、ファイルパス・ファイル名・拡張子などのプロパティにアクセスできる

4

Get-ChildItem.ps1
# ExcelアプリケーションのCOMオブジェクトを作成
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true  # Excelを表示(非表示にしたい場合は $false)
# 警告メッセージやプロンプトを非表示にする
$excel.DisplayAlerts = $false

Get-ChildItem -Recurse -Depth 2 -Filter *.xls* -File | ForEach-Object {
    Write-Host $_.Name
    $book = $excel.Workbooks.Open($_.FullName)
    Start-Sleep -Seconds 2
    $book.Close($false)
}

$excel.Quit()

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
console
【VBA】001_スケジュールへの着色.xlsm
【VBA】002_カラー定数一覧.xlsx
【VBA】003_領収書をPDF出力.xlsm
【VBA】004 カレンダー入力フォームを作ってみよう.xlsm
【VBA】005_プラモの部品構成表.xlsm
【VBA】007_月次請求書を作成.xlsm
【VBA】009_人事データからクロス表を作成.xlsm
Kou_【VBA】007_月次請求書を作成.xlsm
【sele_chan】007_月次請求書を作成.xlsm
【いおり】007_月次請求書を作成.xlsm
【いおり】007_月次請求書を作成_ACE版.xlsm
【しゃあ】007_月次請求書を作成.xlsm
【ひろ】007_月次請求書を作成.xlsm
ぷりずむ_【VBA】007_月次請求書を作成.xlsm
ゅぇ。【VBA】007_月次請求書を作成_v1.xlsm
和風スパ【VBA】007_月次請求書を作成.xlsm
  • Recurseオプションでサブフォルダも再帰的に処理できる
  • Depthオプションでどの階層まで処理するか指定できる

5

Get-ChildItem.ps1
# ExcelアプリケーションのCOMオブジェクトを作成
$excel = New-Object -ComObject Excel.Application
$excel.Visible = $true  # Excelを表示(非表示にしたい場合は $false)

Get-ChildItem -Recurse -Depth 2 -File | 
Where-Object {$_.Name -match "^[^【]"} | 
ForEach-Object {
    Write-Host $_.Name
    $book = $excel.Workbooks.Open($_.FullName)
    Start-Sleep -Seconds 2
    $book.Close($false)
}

$excel.Quit()

[System.Runtime.Interopservices.Marshal]::ReleaseComObject($book) | Out-Null
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($excel) | Out-Null
console
Kou_【VBA】007_月次請求書を作成.xlsm
ぷりずむ_【VBA】007_月次請求書を作成.xlsm
ゅぇ。【VBA】007_月次請求書を作成_v1.xlsm
和風スパ【VBA】007_月次請求書を作成.xlsm
  • 正規表現でフィルタしたい場合はWhere-Objectを使う

ファイル

UTF-8(BOMあり)かshift-jisで保存する

  • UTF-8(BOMなし)は文字化けする

スクリプトファイル(ps1)

既定値はスクリプトファイル実行不可のためセキュリティポリシーを指定する必要がある

セキュリティポリシーを変更しないで実行する

ExecutionPolicy
powershell -ExecutionPolicy Bypass -File .\msgbox.ps1
  • 別プロセスで実行する

セキュリティポリシーを変更する

ExecutionPolicy
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
Scope 意味 有効期限
Process 現在の PowerShell セッション(プロセス)にのみ適用される。 該当プロンプトのみ
CurrentUser 現在ログインしているユーザーに対して設定が適用される。 該当ユーザーのみ永続的
LocalMachine システム全体に対して設定が適用され、全てのユーザーに影響を与える。 すべてのユーザーで永続的
ExecutionPolicy 説明
Restricted すべてのスクリプトの実行を禁止(既定の設定)
AllSigned すべてのスクリプトと構成ファイルに、信頼された発行元の署名が必要
RemoteSigned リモート(インターネットなど)から取得したスクリプトに署名が必要
Unrestricted すべてのスクリプトを実行可能。初回実行時に警告が表示される
Bypass 実行制限を完全に無視。警告も署名も不要
Undefined 実行ポリシーを未定義に戻す(親スコープの設定に従う)
Default PowerShell 7 以降で使用可能。OSやグループポリシーの既定設定に従う

モジュールファイル(psm1)

  • 拡張子psm1は、再利用可能な汎用関数を保存するための拡張子
  • クラスは保存できないようです
garbageCollection.psm1
function garbageCollection(){
    # COMオブジェクトのみを取得
    $variables = Get-Variable | Where-Object { 
        $_.Value -is [System.__ComObject] -and $_.Name -notmatch '^env:|^global:|^function:' 
    }

    Write-Host "リリース予定: $($var.Name)"
    foreach ($var in $variables) {
        Write-Host "$($var.Name)"
    }

    # ユーザーに確認のプロンプトを表示
    $confirmation = Read-Host "上記変数をリリースしますか? (yes でリリース)"

    # 2回目のループ:リリース処理
    if ($confirmation -eq "yes") {
        foreach ($var in $variables) {
            Write-Host "リリース中: $($var.Name)"
            # COMオブジェクトの解放
            [System.Runtime.Interopservices.Marshal]::ReleaseComObject($var.Value) | Out-Null
            # 変数の参照をnullに設定
            $var.Value = $null
        }

        # ガーベジコレクションを強制実行
        [GC]::Collect()
        [GC]::WaitForPendingFinalizers()
        
        Write-Host "COMオブジェクトの解放が完了しました"
    }
}

# モジュール内でエクスポートする関数を指定
Export-ModuleMember -Function garbageCollection
  • Export-ModuleMemberでどの関数を利用可能にするかを指定することができる
  • 指定しなければすべての関数が利用可能になる
main.ps1
Import-Module .\GarbageCollection.psm1

タスクスケジューラ

taskscheduler.ps1
$ScriptPath = "C:\Users\towamz\public\ps\0_task\myfirsttask.ps1"
$Taskname = "myfirsttask"
# $time = Get-Date "2025-07-20 21:30"
$time = (Get-Date).Date.AddHours(21).AddMinutes(45)

$Action    = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-File `"$ScriptPath`""
$Trigger   = New-ScheduledTaskTrigger -Once -At $time
$Principal = New-ScheduledTaskPrincipal -UserId "towamz" -LogonType Interactive
# $Principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -LogonType Interactive
# $Principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest


Unregister-ScheduledTask -TaskName $Taskname  -Confirm:$false
Register-ScheduledTask -TaskName $Taskname -Action $Action -Trigger $Trigger -Principal $Principal
Get-ScheduledTask -TaskName $Taskname  | Get-ScheduledTaskInfo
triggerオプション 説明
-AtStartup PC起動時に実行
-AtLogOn 任意のユーザーがログオンしたときに実行
-AtLogOn -User "<ユーザー名>" 特定のユーザーがログオンしたときに実行
-Daily -At "HH:mm" 毎日指定した時刻に実行 -Daily -At "09:00"
-Weekly -DaysOfWeek <曜日> -At "HH:mm" 毎週指定した曜日に実行 -Weekly -DaysOfWeek Monday -At "08:00"
-Monthly -DaysOfMonth <日> -At "HH:mm" 毎月の特定日に実行 Monthly -DaysOfMonth 1,15 -At "07:00"
-Once -At "HH:mm" 指定した時刻に1回だけ実行 -Once -At "14:30"
LogonTypeオプション 説明
Interactive ユーザーがログオンしているときにのみ実行。GUI操作や画面表示が可能
Password ユーザー名とパスワードで認証し実行。バックグラウンドで動作
ServiceAccount SYSTEMやNETWORK SERVICEなどのサービスアカウントで実行

Discussion