【VBA】フォルダのサイズをエラー回避しながら取得するコード
更新日:2024/01/18
VBAでFolderのサイズを取得しようと思って、Sizeプロパティを参照したらエラーが発生しました。
原因がわからなかったので時間をかけて調査して、エラーを回避するコードを作成しました。
そこでエラーの原因と、作成したコードを紹介します。
エラー内容と原因
FolderオブジェクトのSizeプロパティを参照すると、次のようなエラーが表示されることがあります。
「実行時エラー70:書き込みできません。」
何に書き込めないのかよくわかりませんが、ウォッチウィンドウ等でSizeプロパティを確認すると上手く値が取得できていないことがわかります。
値が次のようになっていますね。
<アプリケーション定義またはオブジェクト定義のエラーです。>
このエラーが出る状況を追っていくと、子孫フォルダを含めて、サイズを取得できないファイルがあるのが原因のようです。
例えば、他のフォルダへのリンクや読み込み権限のないファイルです。
このようなファイルがあるとライブラリ内部でエラーが発生し、有効な値を返すことなく終了していると思われます。
その結果Sizeプロパティの値が異常なものとなって、前述のエラーが報告されたと思われます。
フォルダサイズを取得するクラスモジュール
エラーを発生させずにSizeプロパティの有効性をチェックできればよいのですが、参照するだけでエラーになります。
仕方がないので、エラーを発生させて有効性のチェックを行います。
今回はフォルダサイズを取得するクラスモジュールを作成しました。
Private fso As Scripting.FileSystemObject
Private Enum dType
FILE = 1
folder
End Enum
Private Sub Class_Initialize()
gbCount = 0
byteCount = 0
Set fso = CreateObject("Scripting.FileSystemObject")
End Sub
Private Function getFolderObj(folderName As String) As folder
Set getFolderObj = Nothing
On Error Resume Next
Set getFolderObj = fso.GetFolder(folderName)
On Error GoTo 0
End Function
'************************************************
' フォルダサイズ取得メソッド(フォルダ名から)
'
Function getFolderSizeFromPath(folderName As String) As Variant
Dim folderObj As folder
Set folderObj = getFolderObj(folderName)
If folderObj Is Nothing Then
getFolderSizeFromPath = 0
Else
getFolderSizeFromPath = getFolderSize(folderObj)
End If
End Function
'************************************************
' フォルダサイズ取得メソッド(Folderオブジェクトから)
'
Function getFolderSize(folderObj As folder) As Variant
Dim size As Variant
size = 0
On Error GoTo sizeError
' sizeプロパティの取得
size = folderObj.size
EndStep:
On Error GoTo 0
getFolderSize = size
Exit Function
sizeError:
' sizeプロパティの取得失敗
Dim fsize As Variant
On Error GoTo sizeError2
' Filesプロパティの有効性チェック
If checkObj(folderObj, dType.FILE) Then
' ファイルサイズのカウント
Dim fl As FILE
For Each fl In folderObj.Files
fsize = fl.size
size = size + fsize
Next
End If
' SubFoldersプロパティの有効性チェック
If checkObj(folderObj, dType.folder) Then
' フォルダサイズのカウント
Dim fo As folder
For Each fo In folderObj.SubFolders
fsize = getFolderSizeFromPath(fo.Path)
size = size + fsize
Next
End If
GoTo EndStep
sizeError2:
'サイズ取得失敗 0として計算
fsize = 0
Resume Next
End Function
'************************************************
' サブフォルダサイズ取得メソッド(フォルダ名から)
' 戻り値:Dictionary key⇒フォルダ名 item⇒サイズ
Function getSubFolderSizeFromPath(folderName As String) As Variant
Dim folderObj As folder
Set folderObj = getFolderObj(folderName)
If folderObj Is Nothing Then
Set getSubFolderSizeFromPath = New Dictionary
Else
Set getSubFolderSizeFromPath = getSubFolderSize(folderObj)
End If
End Function
'************************************************
' サブフォルダサイズ取得メソッド
' 戻り値:Dictionary key⇒フォルダ名 item⇒サイズ
Function getSubFolderSize(folderObj As folder) As Dictionary
Dim dic As New Dictionary
' SubFoldersプロパティの有効性チェック
If checkObj(folderObj, dType.folder) Then
' フォルダサイズのカウント
On Error GoTo sizeError
Dim fo As folder, fsize As Variant
For Each fo In folderObj.SubFolders
fsize = fo.size
dic.Add Item:=fsize, Key:=fo.name
Next
End If
EndStep:
On Error GoTo 0
Set getSubFolderSize = dic
Exit Function
sizeError:
fsize = getFolderSizeFromPath(fo.Path)
Resume Next
End Function
'************************************************
' FolderまたはFileオブジェクトの有効チェック
'
Private Function checkObj(fo As folder, ctype As dType) As Boolean
Dim c As Object
On Error GoTo er
' オブジェクトの取り出しチェック
If ctype = dType.FILE Then
Set c = fo.Files
Else
Set c = fo.SubFolders
End If
Dim count As Long
' countの取り出しチェック
count = c.count
checkObj = True
EndStep:
On Error GoTo 0
Exit Function
er:
checkObj = False
Resume EndStep
End Function
FolderオブジェクトのSubFoldersプロパティ(Foldersコレクション)とFilesプロパティ(Fileコレクション)もSizeプロパティと同じように値が壊れている可能性があります。
そのため、countプロパティを取得できるかどうかで有効性を確認しています。
簡単な使用例と解説
この関数は、4つのメソッドを公開しています。
1つめが、getFolderSizeFromPathメソッドです。
このメソッドは、フォルダ名を受け取ってサイズを返します。
Sub test()
Debug.Print Time
Dim fs As New folderSize
Debug.Print fs.getFolderSizeFromPath("C:\")
Debug.Print Time
End Sub
結果は、次のようになりました。
16:19:03
670375117201
16:23:19
実行から終了まで、5分くらいかかりました。
フリーズしたように見えるので、DoEvents関数を使うべきかもしれません。
また、権限がないファイルやフォルダなどのサイズがカウントされていないので、実際のサイズと異なる結果になっています。
2つめが、getFolderSizeメソッドです。
このメソッドは、Folderオブジェクトを受け取って、サイズを返します。
Sub test2()
Debug.Print Time
Dim fso As New Scripting.FileSystemObject
Dim fs As New folderSize
Debug.Print fs.getFolderSizeFromPath(fso.GetFolder("C:\"))
Debug.Print Time
End Sub
結果は実行例1の結果と同じです。
3つめが、getSubFolderSizeFromPathメソッドです。
このメソッドはフォルダ名を受け取って、サブフォルダの名前とサイズを返します。
本来なら名前とサイズをプロパティに持つオブジェクトを定義すべきですが、コードが長くなるのでDictionaryを使用しています。
Sub test3()
Debug.Print Time
Dim fs As New folderSize
Dim dic As Dictionary
Set dic = fs.getSubFolderSize("C:\")
Dim name As Variant
For Each name In dic
Debug.Print name & ":" & dic.Item(name)
Next name
Debug.Print Time
End Sub
結果は、次のようになりました。
16:35:33
$Recycle.Bin:18052881403
$WinREAgent:0
Config.Msi:0
Documents and Settings:0
Program Files:27824796327
Program Files (x86):290400502435
ProgramData:20619023347
Recovery:1109
Runtime:13989
System Volume Information:0
temp:2823224
Users:254708353929
Windows:44145166227
16:40:10
4つめは、3つめのFolder指定版です。
Sub test4()
Debug.Print Time
Dim fso As New Scripting.FileSystemObject
Dim fs As New folderSize
Dim dic As Dictionary
Set dic = fs.getSubFolderSize(fso.GetFolder("C:\"))
Dim name As Variant
For Each name In dic
Debug.Print name & ":" & dic.Item(name)
Next name
Debug.Print Time
End Sub
結果は実行例3の結果と同じです。
Dir関数は使わない
Dir関数でフォルダとファイルを取得して、再帰でサブフォルダのサイズを一つずつ取得していく方法でもフォルダサイズを計算できます。
しかしFolderオブジェクトと比較して、時間が多くかかります。
また、名前に環境依存文字が使われていたり、名前が長かったりすると上手く動作しません。
Folderオブジェクトを使用できるなら、そちらを使うべきでしょう。
ですが、Folderオブジェクトは外部ライブラリのWindows Script Runtimeに含まれているFileSystemObjectオブジェクトで定義されています。
そのためMacでは使用できません。
Macで同じようなことを行う時は、AppleScriptを使用した方がよさそうです。
更新日:2024/01/18
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。