質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.42%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

Q&A

解決済

2回答

1690閲覧

ファイルのアクセス日時に影響を与えずにファイルをコピーしたい(C#)

Marutake

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Windows

Windowsは、マイクロソフト社が開発したオペレーティングシステムです。当初は、MS-DOSに変わるOSとして開発されました。 GUIを採用し、主にインテル系のCPUを搭載したコンピューターで動作します。Windows系OSのシェアは、90%を超えるといわれています。 パソコン用以外に、POSシステムやスマートフォンなどの携帯端末用、サーバ用のOSもあります。

0グッド

2クリップ

投稿2023/05/10 07:15

編集2023/05/10 07:19

実現したいこと

下記のいずれかを実現したいです。

  • ファイルのアクセス日時を変更せずにファイルをコピーしたい。
    (アクセス日時を変更したくないのはコピー元ファイルのみで、コピーした先のファイルのアクセス日時は変わっても大丈夫です。)
  • ファイルコピー時に変更されてしまうコピー元ファイルのアクセス日時を、後で変更前の日時に戻したい。

下記の「前提」に記載したことが実現できれば、上記以外の手段でも良いのですが。

アドバイスなどよろしくお願いいたします。

前提

C#で、Excelファイルの内容を確認・参照するプログラムを作成しているのですが、そのExcelファイルのアクセス日時に影響を与えずに処理を行う必要があります。
(後々「ユーザーにより長らくアクセスされていないファイル」を抽出する際の悪影響とならないため。)

発生している問題

VBAモジュール有無の確認等も行うため Microsoft.Office.Interop.Excel を使用してExcelファイルを開いて内容を参照しているのですが、そうするとファイルのアクセス日時が変わってしまいます。(当たり前?)

そのため、ファイルを別のフォルダにコピーし、コピーしたファイルについて操作すればよいかと思ったのですが、ファイルをコピーするとその時点でアクセス日時が更新されてしまいます。

コピーする前にアクセス日時を取得しておき、コピー後にアクセス日時を元の日時に戻せないかと思ったのですが、やはり「日時を設定した時点」がアクセス日時になってしまいます。

該当のソースコード

以下は大丈夫です。何度実行しても同じ日時がLastAccessTimeとして取得されます。

C#

1var sourceFilePath = @"C:\Users\hoge\test.xlsx"; 2var fileInfo = new FileInfo(sourceFilePath); 3fileInfo.Refresh(); 4 5var lastAccessTime = fileInfo.LastAccessTime; 6Console.WriteLine("アクセス日時:" + lastAccessTime .ToString("yyyy/MM/dd HH:mm:ss")); 7 8// 一応何かやってみる 9var lastWriteTime = fileInfo.LastWriteTime; 10Console.WriteLine("更新日時:" + lastWriteTime.ToString("yyyy/MM/dd HH:mm:ss"));

以下のようにコピー処理を行うとダメです。実行する度にLastAccessTimeとして取得される値が変わってしまいます。(CopyTo を実行した日時になっているようです。)

C#

1var sourceFilePath = @"C:\Users\hoge\test.xlsx"; 2var fileInfo = new FileInfo(sourceFilePath); 3fileInfo.Refresh(); 4 5var lastAccessTime = fileInfo.LastAccessTime; 6Console.WriteLine("アクセス日時:" + lastAccessTime.ToString("yyyy/MM/dd HH:mm:ss")); 7 8var destinationFilePath = Path.GetTempPath() + "\\" + fileInfo.Name; 9fileInfo.CopyTo(destinationFilePath, true);

コピー後に LastAccessTime の値を変更すればよいかと思ったのですが、ダメでした。「変更後アクセス日時」として設定した日時("2023/05/01 09:00:00")が表示されるのでOKかと思ったのですが、維持はされないようで、LastAccessTimeとして取得される値はやはり実行する度に変わってしまいます。(CopyTo を行わなくても変わってしまうので、LastAccessTime に値をセットした日時になるようです。)

C#

1var sourceFilePath = @"C:\Users\hoge\test.xlsx"; 2var fileInfo = new FileInfo(sourceFilePath); 3fileInfo.Refresh(); 4 5var lastAccessTime = fileInfo.LastAccessTime; 6Console.WriteLine("アクセス日時:" + lastAccessTime.ToString("yyyy/MM/dd HH:mm:ss")); 7 8var destinationFilePath = Path.GetTempPath() + "\\" + fileInfo.Name; 9fileInfo.CopyTo(destinationFilePath, true); 10 11fileInfo.LastAccessTime = DateTime.Parse("2023/05/01 09:00:00"); 12Console.WriteLine("変更後アクセス日時:" + fileInfo.LastAccessTime.ToString("yyyy/MM/dd HH:mm:ss"));

試したこと

以下いずれもNGでした。

  • FileInfoのLastAccessTimeに値をセットする代わりに System.IO.File.SetLastAccessTime() により日時をセットする。
  • FileInfoのCopyToに代えてFile.Copy() や FileStreamのCopyToを使う。

補足情報(FW/ツールのバージョンなど)

フレームワークは.NET 6.0ですが、.NET Framework 4.8でも結果は変わらないようです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2023/05/10 07:54

> fileInfo.LastAccessTime = DateTime.Parse("2023/05/01 09:00:00"); この前に 1000ms のインターバルを置くとどうなりますか?
can110

2023/05/10 08:17

robocopyを/COPY:DATコマンド付きで実行すると実現できますが あくまでC#コードで行いたいということでしょうか。
Marutake

2023/05/10 08:19

ありがとうございます。 : System.Threading.Thread.Sleep(2000); fileInfo.LastAccessTime = DateTime.Parse("2023/05/01 09:00:00"); としてみましたが、結果は変わらなかったです。「インターバルを置く」というのはSleepとはまた別の方法が必要でしょうか?
Marutake

2023/05/10 08:25

> robocopyを/COPY:DATコマンド付きで実行すると実現できますが そうなんですね。 特定のフォルダ下のExcelファイル(たぶん結構たくさんあります)を再帰的に探して処理するので、C#で一連の処理とする必要がありますが、C#からrobocopyを実行する、という形は可能かもですね。具体的にはわかってないので、調べてみないとですが。
can110

2023/05/10 08:59

> 特定のフォルダ下のExcelファイル(たぶん結構たくさんあります)を再帰的に探して処理する での処理そものもががバックアップ目的なりの単純なファイルコピー程度であれば 指定拡張子かつ再帰処理など機能豊富なrobocopyだけでまかなえると思いますので まずはrobocopyについて調べてみるとよいかと思います。
Zuishin

2023/05/10 09:05

この処理の後に Refresh を呼んでいるコードがありませんが、呼んでいるんですよね?
Marutake

2023/05/10 23:14

> この処理の後に Refresh を呼んでいるコードがありませんが、呼んでいるんですよね? Refreshは実行してませんでした。<(_ _)> が、 System.Threading.Thread.Sleep(2000); fileInfo.LastAccessTime = DateTime.Parse("2023/05/01 09:00:00"); fileInfo.Refresh(); としてみましたが、改善はされませんでした。この直後に取得している LastAccessTime は "2023/05/01 09:00:00" が返ってくるものの、コードを再度実行すると最初に("2023/05/01…" をセットする前に) LastAccessTime として取得されるのは前回に実行したあたりの日時になってしまいます。
Marutake

2023/05/10 23:19

> 指定拡張子かつ再帰処理など機能豊富なrobocopyだけでまかなえると思いますので > まずはrobocopyについて調べてみるとよいかと思います。 アドバイスありがとうございます。全体的な実現手段含めて robocopy の利用で行けないか検討してみます。 他、現在のC#コードでも「こう修正すれば上手く行くのでは」がありましたら、引き続きご示唆いただけると助かります。<(_ _)>
guest

回答2

0

ベストアンサー

基本的にはReading a file without modifying its Last Access time.に記載された方法をとることで可能だと思います。
ただしHow do I access a file without updating its last-access time?に記載されているとおり、環境(読み取り専用の共有フォルダ環境?)によってはrobocopy含め、正しく動作しない可能性があるようです。

以下、Bing AIにて生成したコード例です。動作未確認です。

C#

1using System; 2using System.IO; 3using System.Runtime.InteropServices; 4 5namespace FileCopyExample 6{ 7 class Program 8 { 9 [DllImport("kernel32.dll", SetLastError = true)] 10 static extern bool SetFileTime(IntPtr hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime); 11 12 static void Main(string[] args) 13 { 14 string sourceFile = @"C:\source.txt"; 15 string destinationFile = @"C:\destination.txt"; 16 17 // Get the file handle for the source file 18 using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) 19 using (var destinationStream = new FileStream(destinationFile, FileMode.Create)) 20 { 21 // Set the last access time to 0xFFFFFFFF to prevent it from being updated 22 long fileTime = -1; 23 SetFileTime(sourceStream.SafeFileHandle.DangerousGetHandle(), ref fileTime, ref fileTime, ref fileTime); 24 25 // Copy the file content 26 sourceStream.CopyTo(destinationStream); 27 } 28 } 29 } 30}

投稿2023/05/11 09:29

can110

総合スコア38332

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Marutake

2023/05/11 23:11

コード例の提示までいただき、感謝です。 動作を確認してみますので少々お時間ください。
Marutake

2023/05/12 23:12

うむむ、Bing AI丸写しではダメでした。やはり実行する度に LastAccessTime は異なる値が取得されます。 sourceStream.CopyTo(destinationStream); をコメントアウトすると LastAccessTime は何度実行しても同じ値なので、「ファイルコピーによりアクセス日時が更新されてしまう」状況が解消されてない模様です。 引用元の情報をよく理解する必要がありそうです。(^_^;) using System; using System.IO; using System.Runtime.InteropServices; namespace FileCopyExample { class Program { [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetFileTime(IntPtr hFile, ref long lpCreationTime, ref long lpLastAccessTime, ref long lpLastWriteTime); static void Main(string[] args) { string sourceFile = @"C:\Users\hoge\test.xlsx"; string destinationFile = @"C:\Users\hoge\copy\test.xlsx"; // Get the file handle for the source file using (var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var destinationStream = new FileStream(destinationFile, FileMode.Create)) { // Set the last access time to 0xFFFFFFFF to prevent it from being updated long fileTime = -1; SetFileTime(sourceStream.SafeFileHandle.DangerousGetHandle(), ref fileTime, ref fileTime, ref fileTime); // Copy the file content sourceStream.CopyTo(destinationStream); } var fileInfo = new FileInfo(sourceFile); fileInfo.Refresh(); var lastAccessTime = fileInfo.LastAccessTime; Console.WriteLine("アクセス日時:" + lastAccessTime.ToString("yyyy/MM/dd HH:mm:ss")); Console.ReadLine(); } } }
can110

2023/05/13 06:51

まずはSetFileTime関数の戻り値(成否)を確認してみてください。たぶん失敗していると思います。 そのうえで回答で提示したサイトには has to call CreateFile() with FILE_WRITE_ATTRIBUTES flag -- otherwise,SetFileTime() call fails. と書かれています。 現状コードでの FileAccess.Read, FileShare.ReadWrite フラグにはこの権限が含まれていないと思われます。
Marutake

2023/05/13 07:57

丁寧に見ていただいて感謝です。 確かに SetFileTime 関数の戻り値は false が返ってました。 var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite) としていたところを var sourceStream = new FileStream(sourceFile, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite) とするとSetFileTime 関数 から true が返るようになり、CopyTo を実行しても LastAccessTime は更新されなくなりました。ありがとうございました! レジストリ HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\NtfsDisableLastAccessUpdate の値を 1 に変更する、という手段もローカルファイルにしか効かず、PowerShell の Set-ItemProperty -Name LastAccessTime での書き換えもNG、と詰まってましたので大変助かりました & 勉強になりました! ベストアンサーとさせていただきます。
otn

2023/05/13 13:41

> という手段もローカルファイルにしか効かず、 ネットワークドライブも対象の話なら、最初から質問に書いておいた方が良いです。 接続先が、Windowsなのか、Linux+samba(単体で売られているネットワークドライブなど)か等によって動作が異なる可能性もあります(実際に今回のケースで異なるのかどうか確認してませんが)。
guest

0

https://devblogs.microsoft.com/oldnewthing/20111010-00/?p=9433

If you’re writing a program which needs to access the file contents but you not want to update the last-access time, you can use the Set­File­Time function with the special value 0xFFFFFFFF in both members of the FILETIME structure passed as the last-access time. This magic value means “do not change the last-access time even though I’m accessing the file.”

投稿2023/05/11 04:28

matukeso

総合スコア1617

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Marutake

2023/05/11 23:09

Thank you for providing valuable information. Please allow me some time to understand the content of the information you provided.
Marutake

2023/05/13 08:05

The best answer was a different answer, but thank you for providing very valuable information related to the solution.
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.42%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問