ファイル操作

【VBA】フォルダのサイズをエラー回避しながら取得するコード

更新日:2024/01/18

VBAでFolderのサイズを取得しようと思って、Sizeプロパティを参照したらエラーが発生しました。
原因がわからなかったので時間をかけて調査して、エラーを回避するコードを作成しました。
そこでエラーの原因と、作成したコードを紹介します。

 

エラー内容と原因

FolderオブジェクトのSizeプロパティを参照すると、次のようなエラーが表示されることがあります。

実行時エラー70:書き込みできません。

「実行時エラー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

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。