はじめに
僕は以前 Delphi でファイルの日時を変更する Windows アプリを作りました。
[簡単エクスプローラ拡張 EzExpEx]
(http://hp.vector.co.jp/authors/VA029585/EzExpEx/)
これは残念なことにユニコードの名称のファイルを扱えないんですよね。
最新の Delphi でビルドし直せばいいのですが、僕が持っている Delphi ではだめなんです。
これを VB.NET で作り直したいとずっと思っていました。
僕のアプリが扱えるファイルの日時は
・ファイル自体の作成日時、更新日時
・Exif 情報の撮影日時、更新日時
・PDF ファイルの作成日時、更新日時
・詳細情報の作成日時、更新日時
ファイル自体の日時を変更するのは .NET の標準機能で対応できますが、ファイルの詳細情報の日時などはどうでしょう。
ファイルの概要情報の日時を変更する
Microsoft Office のファイルなどは、Windows が管理するファイルそのものの日時と別に、ファイルの詳細情報に作成日時、更新日時を持っています。
ファイルの詳細情報の日時は、.NET の標準機能で変更できません。
取得するのは、楽な方法があるようです。
※この記事を書くために調べていたところ、Windows API CodePack を使って変更もできそうです。
[ファイル プロパティの取得 | C# プログラミング解説 | so-zou.jp]
(https://so-zou.jp/software/tech/programming/c-sharp/stream/file/property.htm)
僕が Delphi で作ったアプリは、Windows の IPropertyStorage を使っていました。
[サンプル: "EXCEL&WORDファイルのプロパティ取得" | Delphi Users' Forum]
(http://delfusa.main.jp/delfusafloor/archive/www.nifty.ne.jp_forum_fdelphi/samples/00915.html)
調べていくと C# で書かれたコードは見つけられました。
[ぬるり。: C# と NTFS ストリームの甘くなくもない関係 | ぬるりことNullReferenceExceptionに怯えながら日々書き連ねる.NETのコード片(主にC#)]
(http://hongliang.seesaa.net/article/16600421.html)
これを VB.NET で書き直すことにしました。C# のコードも用意しました。
IPropertyStorage を使用する
VB.NET および C# で IPropertyStorage を使用できるようにします。
<ComImport, Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IPropertySetStorage
Function Create() As Integer
<PreserveSig>
Function Open(ByRef FormatId As Guid, Mode As Integer, ByRef Storage As IPropertyStorage) As Integer
Function Delete() As Integer
Function [Enum]() As Integer
End Interface
<DllImport("ole32.dll", SetLastError:=True, CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)>
Public Shared Function StgOpenStorageEx(Name As String, Mode As Integer, Format As Integer, Attributes As Integer, Options As IntPtr, Reserved As IntPtr, <[In]> ByRef Iid As Guid, <Out> ByRef Opened As IPropertySetStorage) As Integer
End Function
<ComImport, Guid("00000138-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IPropertyStorage
<PreserveSig>
Function ReadMultiple(Count As Integer, <MarshalAs(UnmanagedType.LPArray), [In]> Specs() As PropSpec, <MarshalAs(UnmanagedType.LPArray), Out> Variants() As PropVariant) As Integer
Function WriteMultiple(Count As Integer, <MarshalAs(UnmanagedType.LPArray), [In]> Specs() As PropSpec, <MarshalAs(UnmanagedType.LPArray), [In]> Variants() As PropVariant, NameFirstId As Integer) As Integer
Function DeleteMultiple() As Integer
Function ReadPropertyNames() As Integer
Function WritePropertyNames() As Integer
Function DeletePropertyNames() As Integer
Function Commit() As Integer
Function Revert() As Integer
Function [Enum]() As Integer
Function SetTimes() As Integer
Function SetClass() As Integer
Function Stat() As Integer
End Interface
Public Structure PropSpec
Public Type As Integer
Public Id As Integer
End Structure
Public Structure PropVariant
Public Type As Short
Public Reserved1 As Short
Public Reserved2 As Short
Public Reserved3 As Short
Public Value As Long
End Structure
<DllImport("ole32.dll", SetLastError:=True, CharSet:=CharSet.Unicode, CallingConvention:=CallingConvention.Winapi)>
Public Shared Function PropVariantClear(<[In]> ByRef [Variant] As PropVariant) As Integer
End Function
Public Const STGM_CREATE = &H1000
Public Const STGM_READ = &H0
Public Const STGM_WRITE = &H1
Public Const STGM_READWRITE = &H2
Public Const STGM_SHARE_EXCLUSIVE = &H10
Public Const STGFMT_FILE = &H3
Public Const STGFMT_ANY = &H4
Public Const PRSPEC_PROPID = &H1
Public Const VT_FILETIME = &H40
[ComImport, Guid("0000013A-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertySetStorage
{
int Create();
[PreserveSig]
int Open([In] ref Guid formatId, int mode, out IPropertyStorage storage);
int Delete();
int Enum();
}
[DllImport("ole32.dll", CharSet = CharSet.Unicode)]
public static extern int StgOpenStorageEx(string name, int mode, int format, int attributes, IntPtr options, IntPtr reserved, [In] ref Guid iid, out IPropertySetStorage opened);
[ComImport, Guid("00000138-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IPropertyStorage
{
[PreserveSig]
int ReadMultiple(int count, [MarshalAs(UnmanagedType.LPArray), In] PropSpec[] specs, [MarshalAs(UnmanagedType.LPArray), Out] PropVariant[] variants);
[PreserveSig]
int WriteMultiple(int count, [MarshalAs(UnmanagedType.LPArray), In] PropSpec[] specs, [MarshalAs(UnmanagedType.LPArray), In] PropVariant[] variants, int nameFirstId);
int DeleteMultiple();
int ReadPropertyNames();
int WritePropertyNames();
int DeletePropertyNames();
int Commit();
int Revert();
int Enum();
int SetTimes();
int SetClass();
int Stat();
}
public struct PropSpec
{
public int Kind;
public int Id;
}
public struct PropVariant
{
public short Type;
public short Reserved1, Reserved2, Reserved3;
public long Value;
}
[DllImport("ole32.dll", CharSet = CharSet.Unicode)]
public static extern int PropVariantClear([In] ref PropVariant variant);
public const int STGM_CREATE = 0x1000;
public const int STGM_READ = 0x0;
public const int STGM_WRITE = 0x1;
public const int STGM_READWRITE = 0x2;
public const int STGM_SHARE_EXCLUSIVE = 0x10;
public const int STGFMT_FILE = 0x3;
public const int STGFMT_ANY = 0x4;
public const int PRSPEC_PROPID = 0x1;
public const int VT_FILETIME = 0x40;
ファイルの詳細情報の日時を取得する
Dim Path As String = "●●●●.doc" '対象とするファイルのパス
Dim CreateedTime As DateTime '作成日時を返す
Dim CreatedValid As Boolean = False '作成日時を取得できたか返す
Dim LastSavedTime As DateTime '更新日時を返す
Dim LastSavedValid As Boolean = False '更新日時を取得できたか返す
If Not System.IO.File.Exists(Path) Then
Exit Function
End If
Try
Dim PropSetStrge As IPropertySetStorage = Nothing
Dim PropStrge As IPropertyStorage = Nothing
Try
Dim Guid As New Guid("0000013A-0000-0000-C000-000000000046")
Marshal.ThrowExceptionForHR(StgOpenStorageEx(Path, STGM_READ + STGM_SHARE_EXCLUSIVE, STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, Guid, PropSetStrge))
Dim FmtId As New Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9")
Marshal.ThrowExceptionForHR(PropSetStrge.Open(FmtId, STGM_READ + STGM_SHARE_EXCLUSIVE, PropStrge))
Dim Specs(1) As PropSpec
Specs(0).Type = PRSPEC_PROPID
Specs(0).Id = 12
Specs(1).Type = PRSPEC_PROPID
Specs(1).Id = 13
Dim Variants(1) As PropVariant
Marshal.ThrowExceptionForHR(PropStrge.ReadMultiple(Specs.Length, Specs, Variants))
CreatedTime = DateTime.FromFileTime(Variants(0).Value)
CreatedValid = True
LastSavedTime = DateTime.FromFileTime(Variants(1).Value)
LastSavedValid = True
Finally
If PropSetStrge IsNot Nothing Then Marshal.ReleaseComObject(PropSetStrge)
If PropStrge IsNot Nothing Then Marshal.ReleaseComObject(PropStrge)
End Try
Catch ex As Exception
Exit Function
End Try
string Path = "●●●●.doc"; // 対象とするファイルのパス
DateTime CreatedTime; // 作成日時を返す
bool CreatedValid = false; // 作成日時を取得できたか返す
DateTime LastSavedTime; // 更新日時を返す
bool LastSavedValid = false; // 更新日時を取得できたか返す
if (!System.IO.File.Exists(Path)) {
return false;
}
try {
IPropertySetStorage PropSetStrg = null;
IPropertyStorage PropStrg = null;
try {
Guid iid = new Guid("0000013A-0000-0000-C000-000000000046");
Marshal.ThrowExceptionForHR(StgOpenStorageEx(Path, STGM_READ | STGM_SHARE_EXCLUSIVE, STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, ref iid, out PropSetStrg));
Guid fmtid = new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9");
Marshal.ThrowExceptionForHR(PropSetStrg.Open(ref fmtid, STGM_READ | STGM_SHARE_EXCLUSIVE, out PropStrg));
PropSpec[] specs = new PropSpec[2];
specs[0].Kind =PRSPEC_PROPID;
specs[0].Id = 12;
specs[1].Kind = PRSPEC_PROPID;
specs[1].Id = 13;
PropVariant[] variants = new PropVariant[2];
Marshal.ThrowExceptionForHR(PropStrg.ReadMultiple(specs.Length, specs, variants));
CreatedTime = DateTime.FromFileTime(variants[0].Value);
CreatedValid = true;
LastSavedTime = DateTime.FromFileTime(variants[1].Value);
LastSavedValid = true;
}
finally {
if (PropStrg != null) Marshal.ReleaseComObject(PropStrg);
if (PropSetStrg != null) Marshal.ReleaseComObject(PropSetStrg);
}
}
catch (Exception ex) {
return false;
}
ファイルの詳細情報の日時を変更する
Dim Path As String = "●●●●.doc" '対象とするファイルのパス
Dim CreatedTime As DateTime '作成する更新日時を指定する
Dim CreatedValid As Boolean = True '作成日時を変更するか指定する
Dim LastSavedTime As DateTime '変更する更新日時を指定する
Dim LastSavedValid As Boolean = True '更新日時を変更するか指定する
If Not System.IO.File.Exists(Path) Then
Exit Function
End If
Try
Dim PropSetStrge As IPropertySetStorage = Nothing
Dim PropStrge As IPropertyStorage = Nothing
Try
Dim IId As New Guid("0000013A-0000-0000-C000-000000000046")
Marshal.ThrowExceptionForHR(StgOpenStorageEx(Path, STGM_READWRITE + STGM_SHARE_EXCLUSIVE, STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, IId, PropSetStrge))
Dim FmtId As New Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9")
Marshal.ThrowExceptionForHR(PropSetStrge.Open(FmtId, STGM_READWRITE + STGM_SHARE_EXCLUSIVE, PropStrge))
Dim Specs(1) As PropSpec
Dim Variants(1) As PropVariant
If CreatedValid Then
Specs(0).Type = PRSPEC_PROPID
Specs(0).Id = 12
Variants(0).Type = VT_FILETIME
Variants(0).Value = CreatedTime.ToFileTime
End If
If LastSavedValid Then
Specs(1).Type = PRSPEC_PROPID
Specs(1).Id = 13
Variants(1).Type = VT_FILETIME
Variants(1).Value = LastSavedTime.ToFileTime
End If
Marshal.ThrowExceptionForHR(PropStrge.WriteMultiple(Specs.Length, Specs, Variants, 0))
Finally
If PropSetStrge IsNot Nothing Then Marshal.ReleaseComObject(PropSetStrge)
If PropStrge IsNot Nothing Then Marshal.ReleaseComObject(PropStrge)
End Try
Catch ex As Exception
Exit Function
End Try
string Path = "●●●●.doc"; // 対象とするファイルのパス
DateTime CreatedTime; // 変更する作成日時を指定する
bool CreatedValid = true; // 作成日時を変更するか指定する
DateTime LastSavedTime; // 変更する更新日時を指定する
bool LastSavedValid = true; // 更新日時を変更するか指定する
if (!System.IO.File.Exists(Path)) {
return false;
}
try {
IPropertySetStorage PropSetStrg = null;
IPropertyStorage PropStrg = null;
try {
Guid guid = new Guid("0000013A-0000-0000-C000-000000000046");
Marshal.ThrowExceptionForHR(StgOpenStorageEx(Path, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, STGFMT_ANY, 0, IntPtr.Zero, IntPtr.Zero, ref guid, out PropSetStrg));
Guid fmtid = new Guid("F29F85E0-4FF9-1068-AB91-08002B27B3D9");
Marshal.ThrowExceptionForHR(PropSetStrg.Open(ref fmtid, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, out PropStrg));
PropSpec[] specs = new PropSpec[2];
PropVariant[] variants = new PropVariant[2];
if (CreatedValid) {
specs[0].Kind = PRSPEC_PROPID;
specs[0].Id = 12;
variants[0].Type = VT_FILETIME;
variants[0].Value = CreatedTime.ToFileTime();
}
if (LastSavedValid) {
specs[1].Kind = PRSPEC_PROPID;
specs[1].Id = 13;
variants[1].Type = VT_FILETIME;
variants[1].Value = LastSavedTime.ToFileTime();
}
Marshal.ThrowExceptionForHR(PropStrg.WriteMultiple(specs.Length, specs, variants, 0));
}
finally {
if (PropStrg != null) Marshal.ReleaseComObject(PropStrg);
if (PropSetStrg != null) Marshal.ReleaseComObject(PropSetStrg);
}
}
catch (Exception ex) {
return false;
}