Folders, files and poorly described exceptions

You know, sometimes exceptions returned in C# are so irritatingly vague or indirect that it takes longer than I’d like to solve the problem.

For example, I’ve been attempting to loop through a large set of directories on a network share, for a list of users in Active Directory. I have both an File/Folder indexing library, and an Active Directory library to help speed up development.

foreach (User Account in Accounts) {
    if (!Directory.Exists(Account.HomeDirectory)) continue;

    List<DirectoryInfo> folders = new List<DirectoryInfo>();
    List<FileInfo> files = new List<FileInfo>();

    Folders.Index(
        new DirectoryInfo(Account.HomeDirectory),
        ref folders,
        ref files);

    files.Reverse();
    folders.Reverse();

    foreach (FileInfo fi in files) {
        if (fi.Exists) fi.Delete();
    }

    foreach (DirectoryInfo di in folders) {
        if (di.Exists) di.Delete(true);
    }
}

Irritatingly, it kept returning a “Access is denied” error on the first folder that it attempted to delete. I, of course, have had not nearly enough sleep to deal with this sort of obscure issue, but I plowed on. I ran the code under a domain admin account; that didn’t fix it. I added the existence checks and a bit of debugging output to find where it was failing exactly.

Unable to force the issue, I checked the folder that it was failing on, and the folder was “root\My Videos”. Properties showed “Read-only for files in this folder”.

*facepalm*

Here’s the fixed code. Essentially it sets the FileAttributes of the files and folders it attempts to delete to FileAttributes.Normal, thereby bypassing the “Access is Denied” error (which IMHO should be a “Unable to delete – folder is read-only”!) and allowing the deletion.

foreach (User Account in Accounts) {
    if (!Directory.Exists(Account.HomeDirectory)) continue;

    List<DirectoryInfo> folders = new List<DirectoryInfo>();
    List<FileInfo> files = new List<FileInfo>();

    Folders.Index(
        new DirectoryInfo(Account.HomeDirectory),
        ref folders,
        ref files);

    files.Reverse();
    folders.Reverse();

    foreach (FileInfo fi in files) {
        fi.Attributes = FileAttributes.Normal;
        if (fi.Exists) fi.Delete();
    }

    foreach (DirectoryInfo di in folders) {
        di.Attributes = FileAttributes.Normal;
        if (di.Exists) di.Delete(true);
    }
}

I’m guessing that if I still have issues, I’ll probably have to take ownership of the files/folders to delete them. Not a great start to the day.

Update 1:

I found what looks to be a “Privilege Enabling” library. This is definitely something I shall be adding to my toolkit.

public static void TakeOwnership(DirectoryInfo di) {
    Process p = Process.GetCurrentProcess();

    using (new ProcessPrivileges.PrivilegeEnabler(p, Privilege.TakeOwnership)) {
        DirectorySecurity ds = di.GetAccessControl();
        ds.SetOwner(WindowsIdentity.GetCurrent().User);
        di.SetAccessControl(ds);
    }
}

Update 2:

Wow. After trying to take ownership as above, I still had difficulties with certain “RECYCLER” files/folders, and taking ownership apparently wasn’t enough. Here’s the full-on, brute-force, take-control method:

public static void TakeOwnership(ref DirectoryInfo di) {
    Process p = Process.GetCurrentProcess();
    SecurityIdentifier si = WindowsIdentity.GetCurrent().User;

    // Raise the process' access privileges.
    using (new ProcessPrivileges.PrivilegeEnabler(p, Privilege.TakeOwnership, Privilege.Security)) {
        // Get access rules.
        DirectorySecurity ds = di.GetAccessControl();

        // Take "full control" of everything.        
        FileSystemAccessRule fsaRule = new FileSystemAccessRule(
            si,
            FileSystemRights.Delete | FileSystemRights.Modify,
            InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
            PropagationFlags.None,
            AccessControlType.Allow);

        // Remove previous access rules.
        ds.PurgeAccessRules(si);

        // Add the new access rules.
        ds.AddAccessRule(fsaRule);

        // Take ownership
        ds.SetOwner(si);

        // Set the access rules to the object.
        di.SetAccessControl(ds);
    }
}

Advertisement

Comments are closed.