マクロ記録のその先へ。Windows APIとFSOで実現する「絶対に止まらない」ファイル一括変換術
こんにちは。自動化の海を渡るエンジニアの皆さん。
今日は、マクロの記録ボタンを卒業し、実務で「プロの道具」を作るための、少しだけ背伸びした、しかし一生モノの知見をお話ししましょう。
皆さんは、こんな経験はありませんか?
「ネットワーク上の共有フォルダにあるスライドを、VBAで一括PDF化するプログラムを作った。最初は動いていたのに、誰かがそのファイルを開いた瞬間に、マクロがエラーで止まってしまった……」
これは、自動化エンジニアが必ず直面する「排他制御(ロック)」という壁です。
単なる「ファイルを開く」という命令だけでは、共有環境という戦場では通用しません。今日は、Windowsの深淵にあるAPIと、ファイル操作の達人であるFSO(FileSystemObject)を組み合わせ、「誰かが使っていたら安全にスキップし、使える時だけ確実に処理する」という、エンタープライズ級の堅牢なスクリプトを構築していきましょう。
ここをクリアすれば、あなたのVBAは「便利なツール」から「信頼されるシステム」へと進化します。さあ、準備はいいですか?
1. なぜ、普通のVBAでは「共有ファイル」に勝てないのか?
まず、問題の本質を理解しましょう。
通常、VBAでファイルを開くときは `Presentations.Open` を使います。しかし、ネットワーク上のファイルは、他のユーザーがExcelやPowerPointで開いている間、Windowsによって「使用中」という印(ロック)が付けられます。
この状態で無理やり開こうとすると、VBAは「実行時エラー ‘4605’: ファイルは既に開かれています」といったエラーを吐いて止まってしまいます。あるいは、最悪の場合、PowerPoint自体が応答なし(フリーズ)に陥ることもあります。
これを防ぐには、「開く前に、そのファイルが本当に自分だけが触れる状態か?」を、OS(Windows)に直接問いかける必要があるのです。
2. 武器の準備:Windows APIとFSO
今回のミッションでは、2つの強力な武器を使い分けます。
① FileSystemObject (FSO)
これは「ファイルの存在確認」や「フォルダの探索」を得意とする、ファイル操作の司令塔です。
「そもそも、そこにファイルはあるのか?」という、基本的な生存確認を担当させます。
② Windows API (`CreateFile`)
ここが今回のハイライトです。
Windowsの心臓部(Kernel32.dll)に直接アクセスし、「このファイル、今から私が独占していいですか?」と問い合わせるための関数です。
これを使うことで、ファイルが他のユーザーによってロックされているかどうかを、エラーを出さずに、スマートに判定できます。
3. 実装:堅牢な一括PDF変換スクリプト
それでは、コードを見ていきましょう。初心者の方でも理解しやすいよう、役割ごとにブロックを分けて記述しています。
Option Explicit
' --- Windows API の宣言 ---
' ファイルのロック状態を確認するために、Windowsの低層機能(CreateFile)を呼び出します。
' PtrSafeは64bit版Officeでも動作させるための魔法の言葉です。
Private Declare PtrSafe Function CreateFile Lib "kernel32" Alias "CreateFileA" ( _
ByVal lpFileName As String, _
ByVal dwDesiredAccess As Long, _
ByVal dwShareMode As Long, _
ByVal lpSecurityAttributes As LongPtr, _
ByVal dwCreationDisposition As Long, _
ByVal dwFlagsAndAttributes As Long, _
ByVal hTemplateFile As LongPtr) As LongPtr
Private Declare PtrSafe Function CloseHandle Lib "kernel32" (ByVal hObject As LongPtr) As Long
' 定数の定義(APIを使いこなすための合言葉)
Private Const GENERIC_READ As Long = &H80000000
Private Const FILE_SHARE_NONE As Long = 0 ' 「誰とも共有しない(独占)」という意味
Private Const OPEN_EXISTING As Long = 3 ' 「既存のファイルを開く」という意味
Private Const INVALID_HANDLE_VALUE As LongPtr = -1 ' 「失敗」を意味する値
''' <summary>
''' メイン処理:指定したフォルダ内の全PPTXを安全にPDFへ変換する
''' </summary>
Sub BatchConvertToPDF_Robust()
Dim fso As Object
Dim targetFolder As String
Dim targetFile As Object
Dim pptApp As Object
Dim pres As Object
Dim pdfPath As String
' --- 設定エリア ---
targetFolder = "C:\Users\YourName\Desktop\TestFolder\" ' 変換したいフォルダ(末尾に\を忘れずに)
' ------------------
Set fso = CreateObject("Scripting.FileSystemObject")
' フォルダの存在確認
If Not fso.FolderExists(targetFolder) Then
MsgBox "指定されたフォルダが見つかりません。", vbCritical
Exit Sub
End If
' PowerPointアプリケーションをバックグラウンドで起動
On Error Resume Next
Set pptApp = GetObject(Class:="PowerPoint.Application")
If pptApp Is Nothing Then
Set pptApp = CreateObject("PowerPoint.Application")
End If
On Error GoTo 0
Debug.Print "--- 処理開始: " & Now & " ---"
' フォルダ内のファイルをループ
For Each targetFile In fso.GetFolder(targetFolder).Files
' 拡張子が .pptx の場合のみ処理
If LCase(fso.GetExtensionName(targetFile.Path)) = "pptx" Then
Debug.Print "チェック中: " & targetFile.Name
' 【重要】排他制御のチェック
If IsFileAvailable(targetFile.Path) Then
On Error GoTo ErrorHandler
' ファイルを開く
Set pres = pptApp.Presentations.Open(Filename:=targetFile.Path, _
ReadOnly:=msoTrue, _
WithWindow:=msoFalse)
' PDFの保存パスを作成
pdfPath = fso.GetParentFolderName(targetFile.Path) & "\" & _
fso.GetBaseName(targetFile.Path) & ".pdf"
' PDFとして保存 (ppSaveAsPDF = 32)
pres.SaveAs pdfPath, 32
pres.Close
Debug.Print " [成功] -> " & pdfPath
Else
' ロックされている場合はスキップ
Debug.Print " [スキップ] 他のユーザーが使用中、またはロックされています。"
End If
End If
' 次のファイルへ行く前にエラーハンドリングをリセット
Err.Clear
Next targetFile
' 後片付け
' pptApp.Quit ' 必要に応じて有効化してください
Set pptApp = Nothing
Set fso = Nothing
MsgBox "一括変換処理が完了しました。詳細はイミディエイトウィンドウを確認してください。", vbInformation
Exit Sub
ErrorHandler:
Debug.Print " [エラー] " & targetFile.Name & " の処理中に問題が発生しました: " & Err.Description
Resume Next
End Sub
''' <summary>
''' Windows APIを使用して、ファイルが他のプロセスによってロックされていないか確認する
''' </summary>
Function IsFileAvailable(ByVal filePath As String) As Boolean
Dim hFile As LongPtr
' CreateFileを使用して、ファイルへのアクセスを試みる。
' dwShareModeに FILE_SHARE_NONE (0) を指定することで、
' 「もし他の誰かが開いていたら、この関数は失敗する」という挙動を利用する。
hFile = CreateFile(filePath, _
GENERIC_READ, _
FILE_SHARE_NONE, _
0, _
OPEN_EXISTING, _
0, _
0)
' ハンドルが有効(INVALID_HANDLE_VALUEではない)なら、独占的にアクセス可能
If hFile <> INVALID_HANDLE_VALUE Then
IsFileAvailable = True
' 確認が終わったらすぐにハンドルを閉じる(これ重要!)
CloseHandle hFile
Else
IsFileAvailable = False
End If
End Function
4. プロの視点:ここが「極限の知見」
このコードをただコピペするだけでなく、なぜこのように書いているのか、その「設計思想」を理解してください。
① `FILE_SHARE_NONE` の魔法
`IsFileAvailable` 関数の中にある `FILE_SHARE_NONE`。これがこのスクリプトの心臓部です。
これはWindowsに対して「もし他の誰かがこのファイルを1ミリでも触っていたら、私にはアクセス権を与えないでください」と命じる設定です。これにより、「開けるかどうかを、エラーで判断するのではなく、権限の有無で判断する」という、非常にエレガントな回避策を実現しています。
② オブジェクトのライフサイクル管理
`Set pres = …` で開いたプレゼンテーションは、必ず `pres.Close` で閉じなければなりません。
もし、エラーが発生して閉じられずに残ってしまうと、そのマクロ自体が「ファイルを開きっぱなしにする原因」となり、次の実行時にまたロックエラーを引き起こす……という負の連鎖(ゾンビプロセス)に陥ります。
`On Error GoTo ErrorHandler` を使い、エラー時でも次のファイルへ進めるように設計することが、堅牢な自動化の鉄則です。
③ バックグラウンド処理(`WithWindow:=msoFalse`)
大量のファイルを処理する際、PowerPointのウィンドウが次々とポップアップしてくると、ユーザーの作業を妨げるだけでなく、描画処理にリソースを食われて速度が低下します。
`WithWindow:=msoFalse` を指定することで、ユーザーの目に見えない「裏側」で静かに、高速に処理を完遂させることができます。
5. まとめ:一歩上のエンジニアへ
いかがでしたでしょうか?
1. FSO でファイルの存在を確かめる。
2. Windows API でファイルの「独占権」をチェックする。
3. エラーハンドリング で、一つの失敗が全体の停止を招かないようにする。
この3ステップを組み合わせることで、あなたのマクロは「個人の道具」から「組織で使えるシステム」へと昇華されます。
プログラミングにおいて、大切なのは「動くコードを書くこと」ではありません。「動かない状況を、いかにスマートに回避するか」を考えること。これこそが、設計者(アーキテクト)への第一歩です。
ここをクリアすれば、PowerPoint VBAの基本はバッチリですよ。
自信を持って、次の自動化に挑んでくださいね!応援しています。
