AutoCAD VBAを掌握する極限の知見
OneDrive「オンデマンド保存」の罠:VBAでクラウドファイルを確実に制圧する極意
開発プロジェクトのリーダーである私のもとに、よくこんな悲鳴が届く。
「夜間バッチでOneDrive上のDWGを一括処理しようとしたら、ファイルを開くところでエラーが起きて止まった」
「手動で開くと何ともないのに、VBAの `Workbooks.Open` や `Documents.Open`(※AutoCADでは `Documents.Open`)に相当するメソッドが突如として沈黙する」
原因は明白だ。現代のインフラストラクチャにおいて避けて通れないOneDriveの「ファイルオンデマンド(Files On-Demand)」機能である。
クラウド上に実体があり、ローカルには「影(プレースホルダー)」しか存在しないファイルを、AutoCAD VBAの冷徹なオブジェクトモデルがそのまま叩こうとする。結果、ファイルが存在しない(あるいは読み込み権限が同期中でロックされている)と判定され、容赦なくRuntimeエラーを吐き捨てるのだ。
今回は、このモダンPC環境特有の罠を完全にハックし、VBAからクラウドストレージのステータスを検知・強制ダウンロードさせてから安全にAutoCAD図面を開く、プロダクション品質のソリューションを伝授する。
なぜ通常の `Dir` 関数や `CreateObject(“Scripting.FileSystemObject”)` では防げないのか?
アマチュアプログラマブルな発想であれば、こう考えるだろう。
*「`Dir(“C:\path\to\drawing.dwg”)` でファイルの存在確認をすればいいのではないか?」*
大間違いだ。
FSOや `Dir` 関数は、OneDriveのプレースホルダー(拡張属性としての再解析ポイント:Reparse Point)を「ファイルが存在する」と正常判定してしまう。なぜなら、Windows OSのファイルシステム層において、そのファイルはエクスプローラー上に見えているからだ。
しかし、AutoCADのコアエンジンが低レイヤーでファイルストリームを開こうとした瞬間、実体がクラウドの彼方にあるため、I/Oエラーを引き起こす。
我々が実装すべきは、「ファイルが見えるか」ではなく「ローカルに実体(バイトデータ)が存在するか」の厳密な判定である。
決定版:属性判定とシェルAPIによる自動ダウンロード制御
以下のコードは、単なる「動くコード」ではない。実務の現場で予期せぬ切断や同期遅延が発生してもプロセスがクラッシュしないよう、エラーハンドリングとシェル実行を完璧に同期させたエンタープライズグレードのモジュールだ。
Option Explicit
' Windows API Declarations for File Attribute Inspection
Private Declare PtrSafe Function GetFileAttributesW Lib "kernel32" (ByVal lpFileName As LongPtr) As Long
Private Const INVALID_FILE_ATTRIBUTES As Long = &HFFFFFFFF
Private Const FILE_ATTRIBUTE_RECALL_ON_OPEN As Long = &H40000
Private Const FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS As Long = &H400000
''
' 指定されたDWGファイルがOneDriveのオンデマンド未ダウンロード状態(クラウド専用)か判定する
' @param filePath 判定するファイルのフルパス
' @return True = ローカルに実体なし(要ダウンロード), False = ローカルに実体あり
''
Public Function IsCloudOnlyFile(ByVal filePath As String) As Boolean
Dim attr As Long
attr = GetFileAttributesW(StrPtr(filePath))
If attr = INVALID_FILE_ATTRIBUTES Then
' ファイルが存在しない、またはアクセス権がない
IsCloudOnlyFile = False
Exit Function
End If
' FILE_ATTRIBUTE_RECALL_ON_OPEN または FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS が
' 立っている場合、それはクラウド上にのみ存在する(=オンデマンドのプレースホルダー)状態を指す
If (attr And FILE_ATTRIBUTE_RECALL_ON_OPEN) <> 0 Or _
(attr And FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS) <> 0 Then
IsCloudOnlyFile = True
Else
IsCloudOnlyFile = False
End If
End Function
''
' クラウド上のファイルを強制的にローカルへ同期(ダウンロード)させ、AutoCADで安全に開く
' @param targetPath 開きたいDWGファイルのフルパス
' @return AcadDocumentオブジェクト
''
Public Function OpenCloudAwareDocument(ByVal targetPath As String) As AcadDocument
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
' 1. 基本的な存在チェック
If Not fso.FileExists(targetPath) Then
Err.Raise vbObjectError + 1000, "OpenCloudAwareDocument", "指定されたファイルが存在しません: " & targetPath
End If
' 2. オンデマンド未ダウンロード状態の判定
If IsCloudOnlyFile(targetPath) Then
Debug.Print "【OneDrive制御】ファイルがクラウド上にあります。ダウンロードをトリガーします: " & targetPath
' 強制的にファイルを読み込む(シェル経由でアクセスすることでOneDriveクライアントが同期を開始する)
If Not TriggerOneDriveDownload(targetPath) Then
Err.Raise vbObjectError + 1001, "OpenCloudAwareDocument", "ファイルのクラウドからのダウンロードに失敗しました。"
End If
End If
' 3. AutoCADのドキュメントコレクションに追加(オープン)
' ※注意: AutoCADのバージョンにより、Documents.Openの引数仕様が異なる場合があります。
Dim targetDoc As AcadDocument
Set targetDoc = ThisDrawing.Application.Documents.Open(targetPath)
Set OpenCloudAwareDocument = targetDoc
End Function
''
' シェルオブジェクト経由でファイルにアクセスし、OneDriveのダウンロードプロセスを強制キックする
' @param filePath 対象ファイルのフルパス
' @return 成功したかどうか
''
Private Function TriggerOneDriveDownload(ByVal filePath As String) As Boolean
On Error GoTo ErrorHandler
Dim shellApp As Object
Set shellApp = CreateObject("Shell.Application")
' フォルダとファイル名に分離
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim folderPath As String
Dim fileName As String
folderPath = fso.GetParentFolderName(filePath)
fileName = fso.GetFileName(filePath)
Dim ns As Object
Set ns = shellApp.NameSpace(folderPath)
If ns Is Nothing Then
TriggerOneDriveDownload = False
Exit Function
End If
Dim item As Object
Set item = ns.ParseName(fileName)
If Not item Is Nothing Then
' 「常にこのデバイスに保持する(Make available offline)」属性を一時的に付与するか、
' 単にプロパティにアクセスすることで同期を促す。
' 最も確実なのは、バイナリとしての読み込みストリームをシェル経由でかすめること。
Dim dummyStream As Integer
dummyStream = FreeFile
' プレースホルダーに対してバイナリリードを試みることで、
' WindowsのファイルシステムフィルタドライバがOneDriveへフェッチ要求を送信する
Open filePath For Binary Access Read As #dummyStream
Close #dummyStream
' 同期完了まで微小なウェイトを挟みつつ、実体化をポーリングする(最大10秒)
Dim startTime As Double
startTime = Timer
Do While IsCloudOnlyFile(filePath)
DoEvents
If Timer - startTime > 10 Then
TriggerOneDriveDownload = False
Exit Function
End If
Loop
TriggerOneDriveDownload = True
Else
TriggerOneDriveDownload = False
End If
Exit Function
ErrorHandler:
On Error Resume Next
Close #dummyStream
TriggerOneDriveDownload = False
End Function
アーキテクトからの実践的アドバイス:なぜこの実装が「プロフェッショナル」なのか
1. Win32 APIの厳密な定数活用
`FILE_ATTRIBUTE_RECALL_ON_OPEN` などの属性値は、マイクロソフトのファイルシステム仕様書(MS-FSCC)に定義されているモダンOS向けのフラグだ。これを理解しているか否かで、クラウドストレージ連携バグに直面した際のデバッグスピードが文字通り10倍変わる。
2. バイナリリードによる同期トリガー(Dumb-to-Smart Hack)
シェル経由でただ待つのではなく、`Open … For Binary Access Read` をあえて実行することで、OSのI/Oサブシステムに「今すぐこのデータブロックが必要だ」と強制シグナルを送る。これが、OneDriveクライアントを最も確実かつプログラム的におびき出すキラーテクニックである。
3. タイムアウト機構(ポーリング)の重要性
ネットワーク環境やOneDriveの負荷によっては、ダウンロードに数秒のラグが生じる。`DoEvents` とタイマーを組み合わせたブロッキング回避ループを入れることで、AutoCADのUIスレッドを完全にフリーズさせることなく、スマートに同期完了を待つことができる。
結論
クラウドの普及により、ローカルディスクの概念は変質した。今や「そこにあるはずのファイルが開けない」という矛盾は、VBAエンジニアにとって日常的なリスクである。
今回提供したコードをあなたの自動化ライブラリの基盤層に組み込むことで、OneDrive、SharePoint、さらには将来的なクラウドストレージ環境においても、一切の例外を恐れない鉄壁の図面処理自動化パイプラインが完成する。
手戻りのない、真に堅牢なアーキテクチャを現場に導入してほしい。
