【入門編】【上級者】Windowsの排他制御APIとFSOを組み合わせ、ネットワーク上の共有PPTXファイルを安全にロック取得した上で、他ユーザーの編集競合を回避しながらバックグラウンドでPDF一括変換を行う堅牢スクリプト – PowerPoint VBA解析バイブル

スポンサーリンク
スポンサーリンク

マクロ記録のその先へ。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の基本はバッチリですよ。
自信を持って、次の自動化に挑んでくださいね!応援しています。

タイトルとURLをコピーしました