【ExcelVBA】ファイルのタイムスタンプを一括で指定変更する

ファイルのタイムスタンプを変更したい時ってありませんか?
変更しようと思うのは、Excelファイルだけでなく、その他すべてのファイルのタイムスタンプです
前記事でShellApplicationで更新日時を変える(元に戻す)処理を説明しましたが、Shellで変更できるのは「DateLastModified ‘ 更新日時」だけなんです
残る「DateCreated ‘ 作成日時」と「DateLastAccessed ‘ アクセス日時」の変更には、Win32APIを使うしかないんですよね

ということで、今回はファイルのタイムスタンプ変更をWin32APIを使って処理する方法について紹介していきます。変更したい方、変更したくない方のどちらにもお役に立てる記事だと思いますので是非ご覧ください(ちょっと長いけど(^^;)

ExcelVBAからAPIを使ってすべてのファイルのタイムスタンプを変更する方法の紹介

タイムスタンプの変更について

タイムスタンプとは

ファイルのタイムスタンプは「作成日時」「更新日時」「アクセス日時」の3種類があります
タイムスタンプとは「作成時刻」とか、「更新時刻」とか、「アクセス時刻」とか、電子データに対して付与される時刻を表す情報のことです

タイムスタンプ変更の必要性

ファイルのタイムスタンプは、ファイルを作成・変更したときに Windows が自動的に設定してくれるものですが、意図的にタイムスタンプを操作したい場合や操作した方が良い場合も少なくありません。 例えば、デジカメで撮った写真など、写りを修正するために画像の回転や露出の変更をしたとします。この時にもし画像ファイルのタ イムスタンプが変わってしまうと、ファイルの日付が画像を修正したときの時刻になってしまいます。最初に撮影した日時がわからなくなってしまいまうこともあり得ます。その他にも、作成したファイルを配布する場合など、いろいろなケースで作成日や更新日を変更したい場合や、変更したくない場合が数多く想定されると思います

また、「更新日が作成日より前になってしまう問題」や「最新アクセス日が更新日より古くなってしまう問題」の解決にもお役に立てるのではないかと思っています

ファイルの変更履歴を残したくないような場合にはファイルの作成日時、変更日時をデータとして取っておき、ファイル編集後、そのデータを使ってタイムスタンプを元に戻すのも良いでしょう

Win32APIを使う設定を行う

使用するAPIの変数などの宣言を行う

タイムスタンプを変更するためには、下のコードを見ていただければわかるとおり
「CreateFile」「CloseHandle」「LocalFileTimeToFileTime」「SystemTimeToFileTime」「SetFileTime」などの関数の宣言のほか、必要な「定数」や「構造体」をModuleのGeneral部分に記述しておく必要があります

Option Explicit
'オブジェクトへのアクセスの種類を指定する定数の宣言
Private Const GENERIC_READ As Long = &H80000000
Private Const GENERIC_WRITE As Long = &H40000000
Private Const FILE_SHARE_READ As Long = &H1
Private Const FILE_ATTRIBUTE_NORMAL As Long = &H80
Private Const OPEN_EXISTING As Long = 3 'ファイルへの動作を指定する定数の宣言

'ファイルなどの作成やオープンや切り捨てを行う関数の宣言
'オブジェクトをアクセスするために利用できるハンドルを返す
Private Declare Function CreateFile Lib "kernel32.dll" Alias "CreateFileA" ( _
    ByVal lpFileName As String, _
    ByVal dwDesiredAccess As Long, _
    ByVal dwShareMode As Long, _
    ByVal lpSecurityAttributes As Long, _
    ByVal dwCreationDisposition As Long, _
    ByVal dwFlagsAndAttributes As Long, _
    ByVal hTemplateFile As Long _
    ) As Long

'オープンされているオブジェクトハンドルをクローズする関数の宣言
Private Declare Function CloseHandle Lib "kernel32.dll" ( _
    ByVal hObject As Long _
    ) As Long

'ファイル時間をシステム時間に変換する関数の宣言
Private Declare Function LocalFileTimeToFileTime Lib "kernel32.dll" ( _
    ByRef lpLocalFileTime As FILETIME, _
    ByRef lpFileTime As FILETIME _
    ) As Long

'システム時間をファイル時間に変換する関数の宣言(SYSTEMTIME構造体へのポインタを指定)
Private Declare Function SystemTimeToFileTime Lib "kernel32.dll" ( _
    ByRef lpSystemTime As SYSTEMTIME, _
    ByRef lpFileTime As FILETIME _
    ) As Long

'ファイルの作成日時などを設定する関数の宣言(最終アクセス、最終更新)
Private Declare Function SetFileTime Lib "kernel32.dll" ( _
    ByVal cFile As Long, _
    ByRef lpCreationTime As FILETIME, _
    ByRef lpLastAccessTime As FILETIME, _
    ByRef lpLastWriteTime As FILETIME _
    ) As Long
'パラメータ
'hFile  日時を設定するファイルのハンドルを指定
'lpCreationTime 作成日時を保持(FILETIME構造体へのポインタ)
'lpLastAccessTime   最終アクセス日時(FILETIME構造体へのポインタ)
'lpLastWriteTime    最終更新日時(FILETIME構造体へのポインタ)
'戻り値  関数が成功すると、0 以外の値が返る(失敗すると0)

'システム日時を格納する構造体
Private Type SYSTEMTIME
    Year As Integer
    Month As Integer
    DayOfWeek As Integer
    Day As Integer
    Hour As Integer
    Minute As Integer
    Second As Integer
    Milliseconds As Integer
End Type

'ファイル時間を定義する構造体
Private Type FILETIME
    LowDateTime As Long
    HighDateTime As Long
End Type

タイムスタンプ変更の動作設定を考える

・Excelファイルだけでなくすべてのファイルの変更を可能にする
・変更前に現状のタイムスタンプを確認できるようにする⇒「タイムスタンプ確認」ボタン
・「作成」「更新」「アクセス」日時を個別に指定できるようにする
・「一括で変更」するモードと「個別ファイルごと」に変更するモードの両方に対応する
・失敗しても元に戻せるようにしたい(現状のタイムスタンプのコピーから復元する)

もっと細かい設定もありそうですが、今回は私が考えた必要な部分で設定しています

動作させるためのシートを用意します

・画像を用意しましたので確認してください。用意するのはこのシートだけです

「タイムスタンプ確認」ボタンで、現状のタイムスタンプを取得してセルに表示します
「選択して変更実行」ボタンは、「E3~G3」の設定で一括変更を行います
「表示中の設定に変更を実行」ボタンは、個別のファイル毎に違う設定で変更します
 ただし、「E3~G3」に設定がある場合は個別設定を無視して一括で変更してしまうので注意!
・設定が不要なファイルの場合は、設定を空欄にしておけば変更しません(個別動作時)

動作させるためのVBAコード

ファイルのタイムスタンプを確認するためのコード

「タイムスタンプ確認」ボタンで起動させます

'ファイルのタイムスタンプを確認する処理
Sub GetFileTimeStmp()
    Dim fso As FileSystemObject
    Set fso = New FileSystemObject
    Dim selectFileName As Variant
    Dim PathName As String, fileName As String
    Dim tgFileName As Variant
    Dim d As Date, i As Long
    Dim f As File
    
    'ファイル選択ダイアログを表示
    selectFileName = _
        Application.GetOpenFilename( _
            FileFilter:="全てのファイル,*.*,Microsoft Excel,*.xls?", _
            FilterIndex:=1, _
            Title:="ファイルを選択してください(複数可)", _
            MultiSelect:=True)
    '選択されたファイルに対する処理
    If IsArray(selectFileName) Then
        Cells(1, 5) = ""
        Range(Cells(4, 4), Cells(1000, 7)) = "" 'とりあえず1000行まで
        fileName = selectFileName(1)
        PathName = Left$(fileName, InStrRev(fileName, "\", -1, vbTextCompare) - 1)
        Cells(1, 5) = PathName
        '全てのファイルに繰り返し処理を行う
        i = 3
        For Each tgFileName In selectFileName
            i = i + 1
            Set f = fso.GetFile(tgFileName) ' ファイルを取得
            
            Cells(i, 4) = f.Name
            d = f.DateCreated      ' 作成日時を取得
            Cells(i, 5) = d
            d = f.DateLastModified ' 更新日時を取得
            Cells(i, 6) = d
            d = f.DateLastAccessed ' アクセス日時を取得
            Cells(i, 7) = d        
        Next
    Else
        MsgBox ("ファイルを選択しないで終了!")
        Set fso = Nothing
        Exit Sub
    End If
    Set fso = Nothing    
End Sub

共通部分のコード

この部分がタイムスタンプ変更の「キモ」の部分です

'ファイルタイムを取得する処理
Private Function GetFileTime(ByVal reSetting As Date) As FILETIME
    Dim tSystemTime As SYSTEMTIME

    With tSystemTime
        .Year = Year(reSetting)
        .Month = Month(reSetting)
        .DayOfWeek = Weekday(reSetting)
        .Day = Day(reSetting)
        .Hour = Hour(reSetting)
        .Minute = Minute(reSetting)
        .Second = Second(reSetting)
    End With
    Dim tLocalTime As FILETIME
    Call SystemTimeToFileTime(tSystemTime, tLocalTime)

    Dim tFileTime As FILETIME
    Call LocalFileTimeToFileTime(tLocalTime, tFileTime)

    GetFileTime = tFileTime
End Function
'ファイルのハンドルを取得
Private Function GetFileHandle(ByVal strFilePath As String) As Long
    GetFileHandle = CreateFile( _
        strFilePath, GENERIC_READ Or GENERIC_WRITE, _
        FILE_SHARE_READ, 0, OPEN_EXISTING, _
        FILE_ATTRIBUTE_NORMAL, 0 _
        )
End Function
'ファイルの指定タイムスタンプを指定日時に設定する処理
Private Sub reSetFileTime(ByVal strFilePath As String, _
                            ByVal d1 As Date, _
                            ByVal d2 As Date, _
                            ByVal d3 As Date)
    
    Dim i1 As Long, i2 As Long, i3 As Long
    Dim lngCase As Long
    Dim d1FileTime As FILETIME
    Dim d2FileTime As FILETIME
    Dim d3FileTime As FILETIME
    Dim cFileHandle As Long
    
    'ファイルタイムを取得する
    If d1 <> 0 Then d1FileTime = GetFileTime(d1): i1 = 1 Else i1 = 0
    If d2 <> 0 Then d2FileTime = GetFileTime(d2): i2 = 1 Else i2 = 0
    If d3 <> 0 Then d3FileTime = GetFileTime(d3): i3 = 1 Else i3 = 0
    lngCase = i1 & i2 & i3
    
    'ファイルのハンドルを取得する
    cFileHandle = GetFileHandle(strFilePath)
    
    'ファイルのハンドルが取得できた場合のみ「更新日時」を更新する
    If cFileHandle >= 0 Then
        Dim tNull As FILETIME
        Select Case lngCase
            Case 100  '作成日時のみ
                Call SetFileTime(cFileHandle, d1FileTime, tNull, tNull)
            Case 10    '更新日時のみ
                Call SetFileTime(cFileHandle, tNull, d2FileTime, tNull)
            Case 1    'アクセス日時のみ
                Call SetFileTime(cFileHandle, tNull, tNull, d3FileTime)
            Case 111  'すべての日時
                Call SetFileTime(cFileHandle, d1FileTime, d2FileTime, d3FileTime)
            Case 110  '作成日時と更新日時
                Call SetFileTime(cFileHandle, d1FileTime, d2FileTime, tNull)
            Case 11   '更新日時とアクセス日時
                Call SetFileTime(cFileHandle, tNull, d2FileTime, d3FileTime)
        End Select
        
        Call CloseHandle(cFileHandle)
    
    End If
End Sub

一括変更を実行するコード

「選択して変更実行」ボタンで実行させます

'ファイルのタイムスタンプを一括で変更する処理
Sub TimeStmpChange()
    Dim OpenFileName As Variant
    Dim fileName As String
    Dim selectFileName As Variant
    Dim d1 As Date, d2 As Date, d3 As Date
    Dim pos As Long, i As Long
    
    'ファイル選択ダイアログを表示
    selectFileName = _
        Application.GetOpenFilename( _
            FileFilter:="全てのファイル,*.*,Microsoft Excel,*.xls?", _
            FilterIndex:=1, _
            Title:="ファイルを選択してください(複数可)", _
            MultiSelect:=True)
    '選択されたファイルに対する処理
    If IsArray(selectFileName) Then
        On Error GoTo ErrHandler
        'タイムスタンプを変更する種類の判別(flg = i)
        If Cells(3, 5) <> "" Then d1 = Cells(3, 5) Else: d1 = 0
        If Cells(3, 6) <> "" Then d2 = Cells(3, 6) Else: d2 = 0
        If Cells(3, 7) <> "" Then d3 = Cells(3, 7) Else: d3 = 0
        
        i = d1 + d2 + d3
        If i = 0 Then
            MsgBox ("タイムスタンプが指定されていないので終了します!")
            Exit Sub
        End If
        '全てのファイルに繰り返し処理を行う
        For Each OpenFileName In selectFileName
            pos = InStrRev(OpenFileName, "\")
            fileName = Mid(OpenFileName, pos + 1)
            
            'タイムスタンプをを変更する処理へ
            Call reSetFileTime(OpenFileName, d1, d2, d3)
        Next
    Else
        MsgBox ("ファイルを選択しないで終了!")
        Exit Sub
    End If
    MsgBox "選択したファイルのタイムスタンプ変更が終了しました", _
            vbOKOnly + vbInformation, "タイムスタンプ一括変更"
    Exit Sub

ErrHandler:
    MsgBox "「" & fileName & "」の処理中にエラーが発生しました" & _
    vbCrLf & Err.Description, vbExclamation, "タイムスタンプ一括変更"
    
End Sub

個別ファイルごとに変更を実行するコード

「表示中の設定に変更を実行」ボタンで起動するコードです

'セルに表示されているファイルのタイムスタンプを一括で変更する処理
Sub SheetSetTimeStmpChange()
    Dim OpenFileName As Variant
    Dim selectFileName As Variant
    Dim d1 As Date, d2 As Date, d3 As Date
    Dim i As Long
    Dim n As Long, k As Long
    Dim allflg As Long
    
    'セルに表示されているファイルに対する処理
    n = Cells(Rows.Count, 4).End(xlUp).Row
    If n < 4 Then MsgBox ("ファイルの指定がありません!"): Exit Sub
    If Cells(1, 5) = "" Then MsgBox ("フォルダの指定がありません!"): Exit Sub
    On Error GoTo ErrHandler
    For k = 4 To n
        If allflg = 1 Then GoTo all '一括変更だった場合処理をジャンプします
        If Cells(3, 5) <> "" Then d1 = Cells(3, 5) Else: d1 = 0
        If Cells(3, 6) <> "" Then d2 = Cells(3, 6) Else: d2 = 0
        If Cells(3, 7) <> "" Then d3 = Cells(3, 7) Else: d3 = 0
        i = d1 + d2 + d3
        If i <> 0 Then allflg = 1: GoTo all '一括変更だった場合Flgセット
        
        'タイムスタンプを変更する種類の判別(flg = i)
        If Cells(k, 5) <> "" Then d1 = Cells(k, 5) Else: d1 = 0
        If Cells(k, 6) <> "" Then d2 = Cells(k, 6) Else: d2 = 0
        If Cells(k, 7) <> "" Then d3 = Cells(k, 7) Else: d3 = 0
        i = d1 + d2 + d3
        If i = 0 Then GoTo tugi
all:
        OpenFileName = Cells(1, 5) & "\" & Cells(k, 4)
        'タイムスタンプをを変更する処理へ
        Call reSetFileTime(OpenFileName, d1, d2, d3)
tugi:
    Next
    MsgBox "指定ファイルのタイムスタンプ変更が終了しました", _
            vbOKOnly + vbInformation, "タイムスタンプ一括変更"
    Exit Sub

ErrHandler:
    MsgBox "「" & Cells(k, 4) & "」の処理中にエラーが発生しました" & _
    vbCrLf & Err.Description, vbExclamation, "タイムスタンプ一括変更"
    
End Sub

・各コードの細かい説明は省略します
・コード内のコメント記述で確認してください
「作成」「更新」「アクセス」の設定を確認して処理方法を変更しています

まとめ(おわりに)

注意事項の確認と感想など

・実行前に必ずコピーを取っておくようにしてください
・現ファイルのタイムスタンプの確認を行い、取得できたセルデータを保存しておけば
・万一の場合は、そのデータから元通りに復元することが可能ですのでご利用ください

前回記事ではパスワード変更時にタイムスタンプを変更しなかったのは「更新日時」
 だけでしたが、今回の記事で「すべて」変更できるようになりました
・しかも、指定日時への変更が可能となっていますので有効活用できます
・というわけですので、くれぐれも注意して使用してください。悪用はしないでね(^^;

今後の記事について

今回の記事はいかがだったでしょうか。お役に立てたなら幸いです(^^;
是非!サンプルがありますので使ってみてください(^^)/


【今後の記事内容はどうしようかなぁ・・・】
・ZIPファイルの作成方法と活用法など
・高速化した「VlookUp関数」のVBAでの活用法を再検討してみたいと思います
・その他「小ネタ」などなど・・・・・
・今後これらのどれかについて記事にしていきたいと思います。ご期待ください(^^)/

サンプルファイルをダウンロードできます(下記リンク先へ)

記事で使用したサンプルファイルがダウンロードできるページを設置しています
こちら(このリンク先)からご利用ください