【実務・中級編】【上級プロフェッショナル向け】OneDrive/SharePoint同期環境における「保存競合」を回避する同期待機ロジック – AutoCAD VBA解析バイブル

スポンサーリンク

【AutoCAD VBA極限解説】OneDrive/SharePoint環境の「保存競合」を完全粉砕する同期待機ロジック

開発プロジェクトのリーダーである私のもとに、よくこんな悲鳴が上がってくる。
「ローカルでは完璧に動くマクロなのに、クライアントのOneDrive/SharePoint環境で動かすと、たまにファイル保存時エラー(Error 75: パス名またはファイル名が間違っています、あるいはError 52など)でマクロが異常終了する」と。

断言しよう。そのエラーの原因は、あなたのVBAコードの書き方が悪いのではない。「クラウドストレージの同期ラグ」という物理的な非同期処理の壁を無視しているからだ。

ローカルSSDの感覚で `ThisDrawing.SaveAs` や `Save` を連打すれば、SharePointのバックグラウンドプロセスがファイルをロックしている最中に次の書き込みが走り、ファイルシステムレベルで衝突を起こす。これが「保存競合(ファイル破損・保存失敗)」の正体だ。

今回は、このモダンな開発環境の罠を完全にハックし、ファイルシステムと同期状態を監視しながら安全にセーブを完遂させるプロダクションコードを伝授する。

—

1. なぜ従来のセーブ処理ではクラウド環境で破綻するのか?

多くのプログラマがやりがちな、最も愚劣なアプローチを見てみよう。

‘ 【アンチパターン】絶対に真似してはいけないコード
Sub BadSaveRoutine()
Dim dwgPath As String
dwgPath = “C:\Users\UserName\Contoso\TeamSite – Documents\Drawing1.dwg”

‘ 同期完了を待たずに即座にセーブ
ThisDrawing.SaveAs dwgPath
MsgBox “保存完了!”
End Sub

この書き方が「悪」である理由

1. 同期の非同期性: `SaveAs` が完了してAutoCADがファイルハンドルを解放した瞬間、OSのOneDriveクライアントが「おっ、変更があったな」と検知し、アップロードのためにファイルをロックする。
2. タイムラグの発生: このロックと、次のマクロの処理(あるいは連続バッチ処理の次の図面オープン)がバッティングすると、Windowsは容赦なくアクセス拒否(Error 75)を叩き込む。
3. サイレント破壊: 最悪の場合、ファイルサイズが0バイトになったり、同期競合ファイル(`Drawing-computername.dwg`)が生成されてチームメンバーを絶望の淵に追いやる。

我々が目指すべきは、「ファイルの書き込み完了」だけでなく「クラウドへの同期・ロック解除」までをVBAのスコープ内で担保することだ。

—

2. 解決のアプローチ:APIとファイルシステム監視の融合

この問題を解決するため、今回のプロダクションコードでは以下の2つのアプローチを組み合わせる。

1. FileSystemObject (FSO) による排他制御の監視:
ファイルが他のプロセス(OneDriveの同期エンジン等)によって占有されているかどうかを、実際にストリームを開こうと試みることで判定する。
2. ポーリングによるリトライ機構(指数バックオフ):
一瞬のロックであれば、数ミリ秒〜数秒待って再試行(リトライ)することで確実に保存を成功させる。

—

3. 【プロダクションコード】堅牢なセーブ&同期待機モジュール

以下のコードは、そのままあなたのAutoCAD VBAプロジェクト(`Standard` モジュール等)に貼り付けて即座に使用できる。エラーハンドリングとリトライロジックを極限まで高めた実戦仕様だ。

Option Explicit

‘ Windows API: 処理を指定ミリ秒停止させる
If VBA7 Then
Private Declare PtrSafe Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)
Else
Private Declare Sub Sleep Lib “kernel32″ (ByVal dwMilliseconds As Long)
End If

”
‘ 指定されたDWGファイルを、クラウドの同期競合を回避しながら安全に保存する
‘ @param targetDoc 対象のAcadDocument
‘ @param filePath 保存先のフルパス(OneDrive / SharePoint上のパスを想定)
‘ @return Boolean 保存成功時はTrue
‘
Public Function SafeCloudSave(ByVal targetDoc As AcadDocument, ByVal filePath As String) As Boolean
SafeCloudSave = False

Const MAX_RETRIES As Integer = 5 ‘ 最大リトライ回数
Const BASE_DELAY As Long = 1000 ‘ 初期待機時間 (ミリ秒)
Dim attempt As Integer
Dim isSaved As Boolean

isSaved = False

‘ 1. まず通常通りAutoCADの機能で保存を実行
On Error GoTo SaveError

For attempt = 1 To MAX_RETRIES
‘ 既にファイルが存在する場合、書き込み可能か(ロックされていないか)を事前チェック
If IsFileLocked(filePath) Then
Debug.Print “Attempt ” & attempt & “: ファイルがロックされています。同期完了を待機します…”
GoTo RetryWait
End If

‘ 保存実行
If targetDoc.Path = “” Then
‘ まだ一度も保存されていない新規図面の場合
targetDoc.SaveAs filePath
Else
‘ 既知のパスの場合は上書き保存(別名保存が必要なら適宜変更)
If targetDoc.FullName = filePath Then
targetDoc.Save
Else
targetDoc.SaveAs filePath
End If
End If

‘ 2. 保存直後のファイルシステム安定化を待つ
If WaitUntilFileStable(filePath, 10000) Then
isSaved = True
Exit For
Else
Debug.Print “Attempt ” & attempt & “: 保存後のファイル安定化タイムアウト。”
End If

RetryWait:
‘ 指数バックオフ(待機時間を増やす: 1秒, 2秒, 4秒…)
Sleep BASE_DELAY (2 ^ (attempt – 1))
Next attempt

If isSaved Then
SafeCloudSave = True
Debug.Print “【成功】クラウド同期環境下での安全な保存が完了しました: ” & filePath
Else
MsgBox “致命的なエラー: クラウドストレージの同期競合により、ファイルの保存に失敗しました。” & vbCrLf & _
“パス: ” & filePath, vbCritical, “セーブ&同期ガード”
End If

Exit Function

SaveError:
Debug.Print “Error ” & Err.Number & “: ” & Err.Description
Sleep BASE_DELAY
Resume RetryWait
End Function

”
‘ 指定されたファイルが他のプロセス(同期クライアント等)にロックされているか判定する
Private Function IsFileLocked(ByVal filePath As String) As Boolean
Dim ff As Integer
IsFileLocked = False

‘ ファイルが存在しない場合はロックされていないとみなす
If Dir(filePath) = “” Then Exit Function

On Error GoTo Locked
ff = FreeFile
‘ 排他モードでファイルを開いてみる
Open filePath For Binary Access Read Write Lock Read Write As #ff
Close #ff
Exit Function

Locked:
‘ エラーが発生した場合は、何者かに掴まれている(ロックされている)
Err.Clear
IsFileLocked = True
End Function

”
‘ ファイルサイズやタイムスタンプが安定し、書き込みが完全に完了するまで待機する
Private Function WaitUntilFileStable(ByVal filePath As String, ByVal timeoutMilliSec As Long) As Boolean
Dim fso As Object
Set fso = CreateObject(“Scripting.FileSystemObject”)

Dim startTime As Double
startTime = Timer

Dim lastSize As Currency
lastSize = -1

Dim currentSize As Currency
Dim stableCount As Integer
stableCount = 0

WaitUntilFileStable = False

Do
If fso.FileExists(filePath) Then
On Error Resume Next
currentSize = fso.GetFile(filePath).Size
If Err.Number = 0 Then
‘ サイズが前回チェック時と変わっていなければ「安定した」とみなすカウンタを進める
If currentSize = lastSize And currentSize > 0 Then
stableCount = stableCount + 1
‘ 2回連続でサイズ変化がなければ安定と判定
If stableCount >= 2 Then
WaitUntilFileStable = True
Exit Do
End If
Else
stableCount = 0
lastSize = currentSize
End If
End If
On Error GoTo 0
End If

‘ タイムアウト判定 (Timerは秒単位なのでミリ秒に換算)
If (Timer – startTime) 1000 > timeoutMilliSec Then
Exit Do
End If

Sleep 200 ‘ 0.2秒ごとにポーリング
Loop

Set fso = Nothing
End Function

—

4. このコードのアーキテクチャ上の優位性

1. 指数バックオフ (Exponential Backoff) の採用
リトライ間隔を固定(例:1秒ごと)にするのではなく、`1秒 -> 2秒 -> 4秒` と倍増させていく。これにより、OneDriveの重い同期処理が裏で走っている最中にシステムへ無駄な負荷をかけるのを防ぎ、スマートに処理の順番を譲る設計にしている。
2. バイナリ排他オープンによる厳密なロック検知
`Dir` 関数や単なる存在確認だけでは、ファイルが「存在するかどうか」しか分からない。このコードでは `Open … Lock Read Write` を用いることで、OSレベルで「現在書き込み権限が自分にあるか」を物理的に検証している。
3. ファイルサイズの収束監視
クラウドストレージにデータがダンプされる瞬間、ファイルサイズは `0 -> 途中経過 -> 最終サイズ` と変動する。このモジュールは「サイズが一定数変化しなくなった瞬間」を捉えてから処理を抜けるため、「中途半端な状態で保存完了扱いになる」という最悪のバグを完全にシャットアウトする。

—

5. 実務での呼び出し方(実装例)

マクロのメインルーチンからは、以下のようにこの関数を呼び出すだけでよい。エラーハンドリングのボイラープレートをここに集約しているため、呼び出し側は極めてスマートになる。

Sub ExecuteProductionSave()
Dim targetFilePath As String
targetFilePath = “C:\Users\YourName\SharePoint\EngineeringDept – Documents\Projects\2026_Apt_Design.dwg”

‘ 自作の安全保存関数を叩く
If SafeCloudSave(ThisDrawing, targetFilePath) Then
MsgBox “図面の保存とクラウド同期待機が正常に完了しました。”, vbInformation, “完了”
Else
MsgBox “保存処理が中断されました。ネットワーク接続やファイルの使用状況を確認してください。”, vbExclamation, “警告”
End If
End Sub

—

現場のリーダーから最後に

「動けばいいや」で作られたVBAコードは、ローカル環境では動く。しかし、クライアント企業が全社的にMicrosoft 365(OneDrive / SharePoint)へ移行した瞬間、音もなく崩壊する。

クラウドファーストの時代において、ファイルI/Oは「非同期なネットワーク処理」であるという前提に立った設計が不可欠だ。今回提供したコードをあなたのツールに組み込めば、環境起因のサポート工数は劇的にゼロへと近づくだろう。

プロフェッショナルであれば、インフラの向こう側の挙動まで見通したコードを書け。健闘を祈る。

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