17

Alright, there are 2 ways to send a file to Recyle Bin in .net, either use Microsoft.VisualBasic.FileIO.FileSystem.DeleteFile or use SHFileOperation. Both works good but they delete file permanently if it can't fit inside Recycle Bin. Is it somehow possible that it can throw Exception or return boolean value if file is too big or just simply do not delete it ? (Do not want default confirm dialog)

One way I got was to get the max size of Recycle Bin allowed for the volume then subtract the used size and check if file will get send to RB or deleted permanently, but it may get bad if deleting many files and check again and again.

Anything else I can try ?

xmen
  • 1,947
  • 2
  • 25
  • 47
  • Do you need to check again and again? Could you get the max size and subtract the used size and continue to subtract the size of the files you are recycling? – Michael B Jan 28 '14 at 13:47
  • Yes can, but it may also possible that another application is deleting or user manually deleting files(sending to RB). And it gets more complicated if deleting a folder. – xmen Jan 28 '14 at 14:05
  • It sounds like the recycle bin is the wrong place to be sending the file if you want to guarantee that it stays around. The policies around the recycle bin are at the mercy of the user (and their administrator), not your program. – Damien_The_Unbeliever Jan 28 '14 at 14:53
  • I.e. imagine that your program has successfully implemented the "recycle bin or don't delete" feature you're looking for. Immediately after this code has run, and reported a successful transfer to the recycle bin, the user decides (maybe they're bored) to go and empty their recycle bin. End result is you thought you'd succeeded but the file is still gone (as in gone) – Damien_The_Unbeliever Jan 28 '14 at 15:29
  • @Damien_The_Unbeliever, thats exactly what I want. I want to send files to RB like windows explorer. So user can recover if mistakenly deleted. And can empty the recycle bin whenever they want. Exact same thing like explorer. – xmen Jan 28 '14 at 16:59
  • I'm trying to tell you that you may as well just use the methods you've already got - if you're sending the file to the recycle bin, so far as your application is concerned, the file is deleted anyway. Whether the file is completely deleted or not is now in the hands of the user. – Damien_The_Unbeliever Jan 28 '14 at 18:15
  • does this have to work in WinXP? Could you use IFileOperation? – wonko realtime Feb 17 '14 at 17:23

3 Answers3

5

This question is not as simple as I thought in the beginning. However, I found it's still possible to be solved.

First of all, You need to know how much the recycle bins were used. The Win32's SHQueryRecycleBin can do it for you:

/// <summary>
/// Retrieves the size of the Recycle Bin and the number of items in it, of a specific drive
/// </summary>
/// <param name="pszRootPath">The path of the root drive on which the Recycle Bin is located</param>
/// <param name="pSHQueryRBInfo">A SHQUERYRBINFO structure that receives the Recycle Bin information</param>
/// <returns></returns>
[DllImport("shell32.dll")]
static extern int SHQueryRecycleBin(string pszRootPath, ref SHQUERYRBINFO pSHQueryRBInfo);

/// <summary>
/// Contains the size and item count information retrieved by the SHQueryRecycleBin function
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct SHQUERYRBINFO
{
    /// <summary>
    /// The size of the structure, in bytes
    /// </summary>
    public int cbSize;
    /// <summary>
    /// The total size of all the objects in the specified Recycle Bin, in bytes
    /// </summary>
    public long i64Size;
    /// <summary>
    /// The total number of items in the specified Recycle Bin
    /// </summary>
    public long i64NumItems;
}

Use the following demo code to retrieve this info:

const int S_OK = 0;
//string drivePath = @"C:\$RECYCLE.BIN\";
string drivePath = @"D:\$RECYCLE.BIN\";
SHQUERYRBINFO pSHQueryRBInfo = new SHQUERYRBINFO();
pSHQueryRBInfo.cbSize = Marshal.SizeOf(typeof(SHQUERYRBINFO));
int hresult = SHQueryRecycleBin(drivePath, ref pSHQueryRBInfo);
Console.WriteLine("{0} Drive {1} contains {2} item(s) in {3:#,##0} bytes", 
    hresult == S_OK ? "Success!" : "Fail!",
    drivePath, pSHQueryRBInfo.i64NumItems, pSHQueryRBInfo.i64Size);

Second, (at least on Win7) users can determine how much size they reserve for the recycle bin on (almost) every drive. So you need to know the maximum capacity of the recycle bin for every drive that we can get. This information can be found in the registry. But we also need to retrieve the guids for all the drives:

/// <summary>
/// Get from the registry all the drive guids
/// </summary>
static string[] GetDriveIds()
{
    const string registryPath = @"Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume\";
    RegistryKey reg = Registry.CurrentUser.OpenSubKey(registryPath);
    string[] readIn = reg.GetSubKeyNames();
    string[] driveIds = new string[readIn.Length - 1];
    Array.Copy(readIn, 1, driveIds, 0, readIn.Length - 1); // The first item must be removed
    return driveIds;
}

/// <summary>
/// Get and return the drive's recycle bin's MaxCapacity
/// </summary>
/// <param name="driveId">The guid of the specified drive</param>
/// <returns>The size in mega bytes</returns>
static int FindDriveCapacity(string driveId)
{
    const string registryPath = @"Software\Microsoft\Windows\CurrentVersion\Explorer\BitBucket\Volume\{0}\";
    RegistryKey reg = Registry.CurrentUser.OpenSubKey(
        string.Format(registryPath, driveId));
    return (int)reg.GetValue("MaxCapacity", 0);
}

With the following code you can retrieve the maximum capacity for every drive:

string[] driveIds = GetDriveIds();
int driveNo = 0;
foreach (string driveId in driveIds)
{
    Console.WriteLine("{0}. MaxCapacity of drive {1} is {2:#,##0} bytes",
        ++driveNo, driveId, FindDriveCapacity(driveId));
}

The last thing we have to do is map the guid with the drive letter. We still need to use registry:

/// <summary>
/// Map the drive letter mapped by the drive ID
/// </summary>
/// <param name="driveId">The guid of the drive</param>
static string MapDriveLetter(string driveId)
{
    const string registryPath = @"SYSTEM\MountedDevices";
    RegistryKey reg = Registry.LocalMachine.OpenSubKey(registryPath);
    string[] readIn = reg.GetValueNames();
    byte[] keyCode = {};
    Regex regGuid = new Regex(@"\{[^\}]+\}");
    Regex regDriveLetter = new Regex(@"[A-Z]:$");
    foreach (string keyRead in readIn) {
        if (regGuid.IsMatch(keyRead) && regGuid.Match(keyRead).Value == driveId )
            keyCode = (byte[])reg.GetValue(keyRead, null);                
    }
    foreach (string keyRead in readIn)
    {
        byte[] codeRead = (byte[])reg.GetValue(keyRead, null);
        if (!regGuid.IsMatch(keyRead) && keyCode.SequenceEqual(codeRead))
        {
            if (regDriveLetter.IsMatch(keyRead)) // Get the drive letter in the form "E:"
                return regDriveLetter.Match(keyRead).Value;
        }
    }
    return string.Empty;
}

Simply pass the guid and you can get the drive number:

string code = MapDriveLetter("{f4b90148-66f6-11e3-9ac5-806e6f6e6963}");

With all the information combined together, you should be able to tell on what drive how much the size of a file or files might be deleted permanently by the system.

Johnny
  • 481
  • 4
  • 13
  • Exactly, I actually did something like this. Getting these info was never a problem, problem was that even using this, files can still get permanent delete. For example, when deleting many files, you get total size and check if can fit in RB or not. But while you are deleting the multiple files(different paths, not sub directories), what if other app like windows explorer send files to RB too, big enough to not let our files to fit inside. And then our rest of deleting files will get permanent deleted. Thats why I asked if something else I could try. Anyway Thank you :) +1 – xmen Feb 11 '14 at 14:55
  • You can't do everything for your users; they decide what to delete, not you, in your case. Otherwise, is it also your responsibility if the user cut the power while your program is running? Prompt the user files are being deleted and warn them there is no guarantee all of the files can be restored from RB because other programs also have access to RB. I believe it's quite enough. – Johnny Feb 12 '14 at 00:40
  • Of course can as much as possible. Even have to write more lines. User is never wrong, even its his/her mistake, they will blame developer. Cut the power is very obvious. But deleting is not, a normal user don't even read confirmation dialog message, they just have to press Yes or No buttons after pressing delete. And if they don't find their files in RB, that would be a problem. Its for everyone, our brain get busy and does mistakes, but an app must not make mistakes :) I hope I made my point. – xmen Feb 12 '14 at 05:23
  • 1
    The only solution that comes out would be to lock the RB, if it's possible. But I don't think so. – Johnny Feb 12 '14 at 05:30
  • Something just occurred to me. Maybe you can create your own RB so no other program can access it. You then can determine when to empty your RB, and your users can restore any file as they wish. – Johnny Feb 12 '14 at 05:38
4

What I can gather from your post is that you want to delete files, but don't want to delete them if they are too large to send to the recycle bin. Your goals are a little unclear but it seems like your apprehensive of checking the recycle bin against every file when deleting in bulk.

Consider this, if you're deleting in bulk, and some of the files can go to the recycle bin, and some cant, but you want your user to be able to restore those files, then which files in the set get into the recycle bin and which don't?

You can gather the size of all files, and then check that against the recycle bin. If it's too large, don't delete any of them (or give a permanent delete prompt).

Unless you're trying to remake File Explorer, I would suggest instead using a trash folder in your application which would give you more control.

GVH
  • 416
  • 3
  • 16
Mr. MonoChrome
  • 1,383
  • 3
  • 17
  • 39
  • Getting total file size can fail too. Assume this, 1000 files need to go in recycle bin, program got the total size and it can fit inside RB but meanwhile explorer or some other app send big file to RB. Now the files by my program will never reach recycle bin. Yes creating own trash folder is good idea, but I cant implement it, I want to write deletion as close as to windows explorer. :) +1 for answering though, thank you :) – xmen Feb 11 '14 at 03:54
  • I found this: http://stackoverflow.com/questions/3282418/send-a-file-to-the-recycle-bin. I would test this on my pc, but, if i delete file from windows explorer and the file it's to big for the recycle-bin windows delete permanently. No question to me.... strange – bdn02 Feb 11 '14 at 11:33
  • @bdn02 : Right click on Recycle Bin > Properties. And select custom size for your volumes. – xmen Feb 11 '14 at 17:15
  • Thanks for +1. Unfortunately that's true for explorer as well. I dont believe there is a way to lock the RB. There is always a possibility that someone could move something into the recycle bin during the deletion process. You could try to delete, and if your file fails because someone maxed it out during deletion, roll back your deletion, and then resume with a permanent delete dialog. I believe explorer just moves to RB until it hits a problem and then dialogs. – Mr. MonoChrome Feb 11 '14 at 19:04
3

Instead of deleting the file rename the file with a different path ie app_history folder. The rename method has two main points.

You can rename the path portion of the file and if the path is on the same disk drive rename runs lightning fast because only the pointer to the file on the disk is changed. If the path is to a different drive the file will be moved. It all happens automatically in the windows os.

The program physically can't delete files so it can never gets bad press from deleting an unrecoverable file.

Here's some doc on the rename method remember a rename in windows can change the path to a file.

avs099
  • 10,937
  • 6
  • 60
  • 110
danny117
  • 5,581
  • 1
  • 26
  • 35