none
ファイルやフォルダの強制削除 RRS feed

  • 質問

  • VisualStudio2013 C# .net framework 4.5.1 でアプリケーションを開発しています。

    このアプリでは、アクセス制限の掛かっていないフォルダにテンポラリフォルダやファイルを作成しますが、アプリ終了時に全て削除したいと考えています。

    この時、例えばこのアプリが作成したテンポラリフォルダにコマンドプロンプトやExplorerでアクセスしていると、削除に失敗してしまいます。

    強制的に削除する方法は無いでしょうか。

    (linuxの rm -f を実行するイメージで処理できれば良いのですが。。。)

    2015年5月11日 7:50

回答

  • 他のOSと異なりWindowsの場合、ウィルス対策ソフトが稼働していることが多く、ファイルアクセスをすると対策ソフトが追跡して再オープンしてくるので、ファイル使用後の削除には(まだスキャン中なため)失敗しやすい印象です。

    別のアプローチで、FileStreamクラスのコンストラクターにはFileOptionsを指定することができますが、この中にDeleteOnCloseがあります。これを指定するとファイルを閉じる際に自動的に削除されます。これはOSによる削除機能なので確実に消せるはずです。

    • 回答としてマーク manabubu 2015年5月13日 1:55
    2015年5月12日 1:11
  • 強制的に削除する方法はおそらく無いです。
    他のプロセスが重要な処理の最中にもしも削除ができてしまった場合、そのプロセスのクラッシュを引き起こすことになりますよね。
    #fm -forceは知りませんが、Windowsのrmdir /Qではメッセージが出ないだけで失敗します

    それよりもユーザーが簡単にアクセスできる場所にはテンポラリを作らないという方法を取った方がいいと思います。

    using System;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.IO;
    
    class Program
    {
        static void Main(string[] args)
        {
            //IsolatedStorageという見つけにくい場所を使う
            var storage = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForAssembly();
    
            //IsolatedStrageのフルパスをリフレクションで取り出し
            var pi = storage.GetType().GetProperty("RootDirectory", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            string tempDir = System.IO.Path.Combine((string)pi.GetValue(storage, null), "tempDir");
            var di = new System.IO.DirectoryInfo(tempDir);
            di.Create();
    
            //隠し属性にして見つけにくくする
            di.Attributes |= System.IO.FileAttributes.Hidden;
    
            //一時フォルダをカレントにしてコマンドプロンプトを開く
            System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo("cmd");
            si.WorkingDirectory = tempDir;
            System.Diagnostics.Process.Start(si);
            Console.WriteLine(tempDir);
            Console.Write("Press Enter");
            System.Console.ReadLine();
    
            int retry = 0;
            while (retry++ < 5)
            {
                try
                {
                    di.Delete(true);
                    break;
                }
                catch (IOException) { }
                catch (UnauthorizedAccessException) { }
                System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(100));
            }
            if (retry >= 5)
            {
                var color = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
    
                var id = System.Security.Principal.WindowsIdentity.GetCurrent();
    
                var pri = new System.Security.Principal.WindowsPrincipal(id);
                if (pri.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
                {
                    //管理者権限で実行している場合は再起動時に削除可能にする
                    MoveFileEx(tempDir, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
                    Console.Error.WriteLine("一時フォルダがロックされているため再起動後に削除を試みます。");
                    //ただし空フォルダではない場合は失敗
                }
                else
                {
                    Console.Error.WriteLine("一時フォルダがロックされているため削除できませんでした。");
                }
                Console.ForegroundColor = color;
            }
            else
            {
                Console.Error.WriteLine("一時フォルダが削除できました。");
            }
        }
    
        [FlagsAttribute()]
        enum MoveFileFlags
        {
            MOVEFILE_REPLACE_EXISTING = 0x01,
            MOVEFILE_COPY_ALLOWED = 0x02,
            MOVEFILE_DELAY_UNTIL_REBOOT = 0x04,
            MOVEFILE_WRITE_THROUGH = 0x08,
            MOVEFILE_CREATE_HARDLINK = 0x10,
            MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20
        }
    
        [DllImport("kernel32.dll")]
        static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク manabubu 2015年5月13日 1:55
    2015年5月11日 10:48

すべての返信

  • 強制的に削除する方法はおそらく無いです。
    他のプロセスが重要な処理の最中にもしも削除ができてしまった場合、そのプロセスのクラッシュを引き起こすことになりますよね。
    #fm -forceは知りませんが、Windowsのrmdir /Qではメッセージが出ないだけで失敗します

    それよりもユーザーが簡単にアクセスできる場所にはテンポラリを作らないという方法を取った方がいいと思います。

    using System;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.IO;
    
    class Program
    {
        static void Main(string[] args)
        {
            //IsolatedStorageという見つけにくい場所を使う
            var storage = System.IO.IsolatedStorage.IsolatedStorageFile.GetUserStoreForAssembly();
    
            //IsolatedStrageのフルパスをリフレクションで取り出し
            var pi = storage.GetType().GetProperty("RootDirectory", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
            string tempDir = System.IO.Path.Combine((string)pi.GetValue(storage, null), "tempDir");
            var di = new System.IO.DirectoryInfo(tempDir);
            di.Create();
    
            //隠し属性にして見つけにくくする
            di.Attributes |= System.IO.FileAttributes.Hidden;
    
            //一時フォルダをカレントにしてコマンドプロンプトを開く
            System.Diagnostics.ProcessStartInfo si = new System.Diagnostics.ProcessStartInfo("cmd");
            si.WorkingDirectory = tempDir;
            System.Diagnostics.Process.Start(si);
            Console.WriteLine(tempDir);
            Console.Write("Press Enter");
            System.Console.ReadLine();
    
            int retry = 0;
            while (retry++ < 5)
            {
                try
                {
                    di.Delete(true);
                    break;
                }
                catch (IOException) { }
                catch (UnauthorizedAccessException) { }
                System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(100));
            }
            if (retry >= 5)
            {
                var color = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Red;
    
                var id = System.Security.Principal.WindowsIdentity.GetCurrent();
    
                var pri = new System.Security.Principal.WindowsPrincipal(id);
                if (pri.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
                {
                    //管理者権限で実行している場合は再起動時に削除可能にする
                    MoveFileEx(tempDir, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT);
                    Console.Error.WriteLine("一時フォルダがロックされているため再起動後に削除を試みます。");
                    //ただし空フォルダではない場合は失敗
                }
                else
                {
                    Console.Error.WriteLine("一時フォルダがロックされているため削除できませんでした。");
                }
                Console.ForegroundColor = color;
            }
            else
            {
                Console.Error.WriteLine("一時フォルダが削除できました。");
            }
        }
    
        [FlagsAttribute()]
        enum MoveFileFlags
        {
            MOVEFILE_REPLACE_EXISTING = 0x01,
            MOVEFILE_COPY_ALLOWED = 0x02,
            MOVEFILE_DELAY_UNTIL_REBOOT = 0x04,
            MOVEFILE_WRITE_THROUGH = 0x08,
            MOVEFILE_CREATE_HARDLINK = 0x10,
            MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x20
        }
    
        [DllImport("kernel32.dll")]
        static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags);
    }


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク manabubu 2015年5月13日 1:55
    2015年5月11日 10:48
  • 他のOSと異なりWindowsの場合、ウィルス対策ソフトが稼働していることが多く、ファイルアクセスをすると対策ソフトが追跡して再オープンしてくるので、ファイル使用後の削除には(まだスキャン中なため)失敗しやすい印象です。

    別のアプローチで、FileStreamクラスのコンストラクターにはFileOptionsを指定することができますが、この中にDeleteOnCloseがあります。これを指定するとファイルを閉じる際に自動的に削除されます。これはOSによる削除機能なので確実に消せるはずです。

    • 回答としてマーク manabubu 2015年5月13日 1:55
    2015年5月12日 1:11
  • 他のOSと異なりWindowsの場合、ウィルス対策ソフトが稼働していることが多く、ファイルアクセスをすると対策ソフトが追跡して再オープンしてくるので、ファイル使用後の削除には(まだスキャン中なため)失敗しやすい印象です。

    別のアプローチで、FileStreamクラスのコンストラクターにはFileOptionsを指定することができますが、この中にDeleteOnCloseがあります。これを指定するとファイルを閉じる際に自動的に削除されます。これはOSによる削除機能なので確実に消せるはずです。

    ちょっと曖昧な記憶ですが、DeleteOnCloseは即座に本当に削除されるとは限らないため、ファイルが削除されたか確認しようとするとうまくいっていないように見える場合があるので注意してください。

    ※一時的に、エクスプローラからは見えるが開けない、削除もできない、実質的には削除済みに近いような状態になったりします。

    遅くともマシンを再起動すれば、きちんと消えていることが確認できますが。

    2015年5月12日 3:09
  • みなさま返信ありがとうございます。

    説明不足でしたが、今回テンポラリとして作成するのはある圧縮ファイルを一時的に解凍するエリアになります。

    このテンポラリは、本アプリでのみ参照するので、例え参照しているアプリが存在しそのアプリがクラッシュしたとしても強制削除したいと思っていたのですが、確かにウィルスチェックソフトの存在を忘れていました。

    とすると、

    • アプリの任意のタイミングでテンポラリを削除し、何らかの理由で削除出来ない場合は、次回削除でリトライ。
    • そもそも圧縮ファイルの解凍先は必ずユニークになるようなフォルダをまず作成し、その下で解凍する。

    という対応が必要そうですね。

    色々と勉強になりました。ありがとうございました。

    2015年5月13日 1:54
  • Tempフォルダーを使用している場合は、Windowsのディスククリーンアップにて削除されるので、それを期待するのも1つの手です。

    ランダムなディレクトリ名を生成するにはPath.GetRandomFileName()が用意されています。

    2015年5月13日 4:25