How to work around LocalFileSettingsProvider requi

2019-04-30 00:04发布

问题:

In my .NET client application I use the default settings provider with Scope=User and Roaming=True. This works fine in most environments, no matter if client or Terminal Server, except for a customer with a Citrix Terminal Server farm. Whenever Properties. Settings.Default.Save() is called, the following exception is thrown:

System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.
   at System.Security.AccessControl.Win32.SetSecurityInfo(ResourceType type, String name, SafeHandle handle, SecurityInfos securityInformation, SecurityIdentifier owner, SecurityIdentifier group, GenericAcl sacl, GenericAcl dacl)
   at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, SafeHandle handle, AccessControlSections includeSections, Object exceptionContext)
   at System.Security.AccessControl.NativeObjectSecurity.Persist(String name, AccessControlSections includeSections, Object exceptionContext)
   at System.Security.AccessControl.FileSystemSecurity.Persist(String fullPath)
   at System.Configuration.Internal.WriteFileContext.DuplicateTemplateAttributes (String source, String destination)
   at System.Configuration.Internal.WriteFileContext.DuplicateFileAttributes(String source, String destination)
   at System.Configuration.Internal.WriteFileContext.Complete(String filename, Boolean success)
   at System.Configuration.Internal.InternalConfigHost.StaticWriteCompleted(String streamName, Boolean success, Object writeContext, Boolean assertPermissions)
   at System.Configuration.Internal.DelegatingConfigHost.WriteCompleted(String streamName, Boolean success, Object writeContext, Boolean assertPermissions)
   at System.Configuration.ClientSettingsStore.ClientSettingsConfigurationHost.WriteCompleted(String streamName, Boolean success, Object writeContext)
   at System.Configuration.UpdateConfigHost.WriteCompleted(String streamName, Boolean success, Object writeContext)
   at System.Configuration.MgmtConfigurationRecord.SaveAs(String filename, ConfigurationSaveMode saveMode, Boolean forceUpdateAll)
   at System.Configuration.ClientSettingsStore.WriteSettings(String sectionName, Boolean isRoaming, IDictionary newSettings)
   at System.Configuration.LocalFileSettingsProvider.SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection values)
   at System.Configuration.SettingsBase.SaveCore()
   at System.Configuration.SettingsBase.Save()

The reason for this exception:

  • System.Configuration.Internal.WriteFileContext writes a new copy (...newcfg) of the user settings in the user's roaming profile. Then, DuplicateTemplateAttributes tries to modify the ACLs of this file and explicitly set the ownership to the current user.
  • In the case of this customer this fails because the roaming profile is stored on a file share and the users have only Read and Change permissions, but not Full Control. They probably have Full Control in NTFS (because by default you are "Owner" of all files you create, and as the owner, you can do anything with the file no matter if you have "Full Control" explicitly set), but it seems like its blocked on the SMB share level.

This behavior doesn't make any sense to me: Given that the LocalFileSystemProvider always uses a private profile folder of the current user (local or roaming), we can safely assume that the user is the owner anyway.

Since WriteFileContext catches the exception, deletes the temporary .newcfg file and then rethrows, there is no way to simply catch the exception in my code and rename the file or somehow grab its content since it is already deleted when the exception is thrown.

I couldn't find any simple way to work around this issue except for implementing my own settings provider. For this, it seems like I even would have to rebuild things like the serialization part since all the System.Configuration stuff used for this is internal. And of course I don't want to break the currently used settings, so it looks like a ridiculous amount of code just to rebuild everything as it is with just "one line commented out" (setting the owner of the file).

Any ideas what else I could try?

There is no way the customer changes anything in its file share permissions...

回答1:

I have experienced a similar issue on Citrix - AppData is redirected to a "Change & Read" network share (not "Full Control" - it works on "Full Control"). On first run, our application will create the user.config on the first Save() call but throws UnauthorizedAccessException on any subsequent Save() calls.

The answer appears to be to delete the user.config file if it exists before calling Save().

We are currently testing this with our client - I will update my answer when I have concrete results.

Update: You need to "touch" each setting in Settings.Default before calling Save() as the temp file is actually merged with existing user.config. By calling the following method before calling Save(), the user.config is correctly recreated each time (no UnauthorizedAccessException thrown).

public static void ClearUserConfigFile()
{
    //Touch each setting
    foreach (SettingsProperty property in Settings.Default.Properties)
    {
        if (property.DefaultValue != Settings.Default[property.Name])
            Settings.Default[property.Name] = Settings.Default[property.Name];
    }

    //Delete the user.config file
    var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.PerUserRoaming);
    var userConfigPath = config.FilePath;
    try
    {
        if (File.Exists(userConfigPath) == true)
            File.Delete(userConfigPath);
    }
    catch (Exception ex)
    {
        _log.ErrorFormat("Exception thrown while deleting user.config : {0}", ex.ToString());
    }
}