I'm working on a piece of software that needs to copy a file to a given directory on the filesystem. It needs to work on both UAC-aware OSs (Vista, 7) as well as XP. To get around the issue of writing to a directory where UAC elevation is required, the app actually kicks off another process with a manifest that states that UAC is required. This generates the prompt and then does the copy when the user confirms.
From what I can see, a directory can have three different logical permission states - writeable without UAC elevation, writeable with UAC elevation and not writeable.
My question is this: For a given directory, how do I reliably determine whether the current user can copy (and potentially overwrite) a file to that directory, and if I can, how do I determine if UAC elevation is required?
On XP, this could just be as simple as checking whether the 'Allow Write' permission is granted, but on Vista / 7, there are directories where this permission isn't granted, but this action is still possible with UAC.
We have a method for WriteAccess on files, you can probably adapt it for Directories (Directory.GetAccessControl and so on)
/// <summary> Checks for write access for the given file.
/// </summary>
/// <param name="fileName">The filename.</param>
/// <returns>true, if write access is allowed, otherwise false</returns>
public static bool WriteAccess(string fileName)
{
if ((File.GetAttributes(fileName) & FileAttributes.ReadOnly) != 0)
return false;
// Get the access rules of the specified files (user groups and user names that have access to the file)
var rules = File.GetAccessControl(fileName).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier));
// Get the identity of the current user and the groups that the user is in.
var groups = WindowsIdentity.GetCurrent().Groups;
string sidCurrentUser = WindowsIdentity.GetCurrent().User.Value;
// Check if writing to the file is explicitly denied for this user or a group the user is in.
if (rules.OfType<FileSystemAccessRule>().Any(r => (groups.Contains(r.IdentityReference) || r.IdentityReference.Value == sidCurrentUser) && r.AccessControlType == AccessControlType.Deny && (r.FileSystemRights & FileSystemRights.WriteData) == FileSystemRights.WriteData))
return false;
// Check if writing is allowed
return rules.OfType<FileSystemAccessRule>().Any(r => (groups.Contains(r.IdentityReference) || r.IdentityReference.Value == sidCurrentUser) && r.AccessControlType == AccessControlType.Allow && (r.FileSystemRights & FileSystemRights.WriteData) == FileSystemRights.WriteData);
}
Hope this helps.
You handle the writable without elevation case just by trying the operation. It's when that fails, and you have to distinguish between not-writable vs writable via UAC elevation that is potentially difficult.
I don't think that I would like programs trying to figure that out for me (since they'll inevitably get it wrong quite often).
I think it's safe to design it with these assumptions:
- Administrators sometimes run as restricted accounts to trial software they don't trust -> if your app is going to make invasive changes to the computer that require UAC they want to cancel, not elevate.
- Elevated administrators can write the file (they are admin after all) -> no need for an actual ACL check, detecting a restricted token is enough.
- Users may elevate using a different account, or may ask a co-worker to complete the UAC-required action -> checking for the restricted token will miss these cases.
- Other recoverable things cause access denied, including file in use -> sometimes the right thing to do is retry using the same restricted permissions.
So altogether, I would suggest trying the operation AsInvoker, in case of access denied bring up a prompt that explains that Windows denied the operation, possible causes are: file-in-use, elevation required, administrator credentials required, and give the user three buttons:
- Cancel
- Retry with current credentials
- (shield icon) Elevate permissions and retry