How to work around LocalFileSettingsProvider requiring Full Control/OwnerRights - c#

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...

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());
}
}

Related

How can I get full read access to the Security event log with a local account on Windows Server 2016

I have an application that creates new event sources in an event log on a 2016 Windows Server v1607. It is not known which names the sources will have. To archive this the account needs read access to all the event sources to assure the source name does not already exist (why double source names in different logs are not allowed is another interesting question). By default a local account is blocked from reading the Security event log, so the creation of a new source ends up with an error that there is no read access to the Security log.
The most promising approach seemed to be the answer to this question: https://stackoverflow.com/a/3138269/2091030
I followed the steps 1-5 changing the registry permissions of HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Security by adding read permissions for the local account. I checked all the sub-keys in the Security folder and they all show proper read access for the account. Nevertheless I get another error now when using a simple C# program to add an event with a new source:
using System;
using System.Diagnostics;
namespace EventlogTest {
public class Test {
public static void Main() {
var log = new EventLog("SomeLog", ".", "SomeNewSource");
log.WriteEntry("Test 123", EventLogEntryType.Information);
}
}
}
System.Security.SecurityException: Der angeforderte Registrierungszugriff ist unzulässig.
bei System.ThrowHelper.ThrowSecurityException(ExceptionResource resource)
bei Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
bei System.Diagnostics.EventLog.CreateEventSource(EventSourceCreationData sourceData)
bei System.Diagnostics.EventLogInternal.VerifyAndCreateSource(String sourceName, String currentMachineName)
bei System.Diagnostics.EventLogInternal.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData)
bei System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type)
bei EventlogTest.Test.Main()
Did I miss something?
The following settings for the local account allowed me to add new sources in my event-log "MyLog":
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog:
Add your local account with the following rights: Query Value, Set Value, Create Subkey, Enumerate Subkey
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Security:
This folder does not inherit rights from it's parent. Add the local account with normal read access.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\MyLog:
Deactivate inheritance and copy values, then add your local account with full access

C#: ArgumentException when calling System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace()

I am in a bit of a strange situation. I have been given a fairly large suite of PowerShell modules and functions, and it is my job to tie these together into an executable. The requirements state that this must be a single, standalone executable with no installer and .net 3.5 may be the only dependency. The Windows Management Framework is not an exception and cannot be assumed to exist on the machine. To get around this, I have added System.Management.Automation as a reference and made it an embedded resource, along with all of the PowerShell module files, and load them from reflection at runtime. This seems to work OK, but I have some errors that I cannot seem to figure out and think it might have something to do with this system.
So Here is the issue: When I start to initialize things to run the PowerShell command, I get a strange error that I can't seem to control.
Here is the code:
public static void RunCommand(object objcommand)
{
//create a script block for toolbox once, get the embeded resource, convert from byte array to string, make scriptblock from string
ScriptBlock toolbox = System.Management.Automation.ScriptBlock.Create(System.Text.Encoding.Default.GetString(Properties.Resources.toolbox));
string command = (string)objcommand;
//get the module name
string modname = options.Commands[command]["module"];
//get the module from the embeded resources, convert to string, convert to scriptblock
var module = System.Management.Automation.ScriptBlock.Create(new System.IO.StreamReader(myasm.GetManifestResourceStream("piramids.Resources." + modname + ".psm1")).ReadToEnd());
using (var powerShell = PowerShell.Create())
{
System.Management.Automation.Runspaces.Runspace rs = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(); //i think this line triggers the exception
rs.Open();
powerShell.Runspace = rs;
//make the necesary powershell modules of the command availible
powerShell.AddCommand("new-module").AddParameter("ScriptBlock", toolbox).Invoke();
powerShell.AddCommand("new-module").AddParameter("ScriptBlock", module).Invoke();
//if inethistory, make dlls availible
if (modname.Equals("inethistory"))
{
powerShell.AddCommand("add-type").AddParameter("Path", sqldll).Invoke();
powerShell.AddCommand("add-type").AddParameter("Path", esentdll).Invoke();
}
ICollection<PSObject> output = new List<PSObject>(0);
try {
output = powerShell.AddCommand("get-" + command).AddCommand(format).AddCommand("out-string").Invoke();//pipeline.Invoke();
} catch (System.Management.Automation.RuntimeException e)
{
Console.Error.WriteLine("An Error occured while executing '" + command + "'");
Console.Error.WriteLine(e.Message);
}
//do stuff with the results
and here is the stack trace:
Unhandled Exception: System.ArgumentException: The path is not of a legal form.
at System.IO.Path.NormalizePathFast(String path, Boolean fullCheck)
at System.IO.Path.NormalizePath(String path, Boolean fullCheck)
at System.IO.Path.GetFullPathInternal(String path)
at System.IO.Path.GetFullPath(String path)
at System.Diagnostics.FileVersionInfo.GetFullPathWithAssert(String fileName)
at System.Diagnostics.FileVersionInfo.GetVersionInfo(String fileName)
at System.Management.Automation.PSVersionInfo.GetPSVersionTable()
at System.Management.Automation.PSVersionInfo.get_PSVersion()
at Microsoft.PowerShell.DefaultHost..ctor(CultureInfo currentCulture, CultureInfo currentUICulture)
at System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace()
at piramids.Program.RunCommand(Object objcommand)
at piramids.Program.Main(String[] args)
I believe this line is where the exception occurs:
System.Management.Automation.Runspaces.Runspace rs = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace();
The CreateRunspace method is not documented to throw any exceptions, and this exception comes from so many levels down that I have no idea what kind of path this thing is checking, as I never called a function that asked for a path.
I am stumped. Does anyone have any idea what may be causing this?
EDIT: After some digging, here is what I found. PSVersionTable is a static field of VersionInfo, so the static constructor is called the first time get called for this field. The static constructor calls an internal method called GetBuildVersion, which tries to get the assembly location of PSVersionInfo. According to This documentation page:
If the assembly is loaded from a byte array, such as when using the Load(Byte[]) method overload, the value returned is an empty string ("").
I am loading from a byte array, so this will be an empty string. But then GetBuildVersion uses this location to do FileVersionInfo.GetVersionInfo which verifies the path with Path.GetFullPath. According to This documentation page:
ArgumentException:
the path is a zero-length string
So there is the problem. Now the question is, How do I assign a location to an assembly loaded from a byte array? May God have mercy on me.
I'm not at all convinced this is even remotely reasonable to expect PowerShell code to work without installing WMF. If I were approached with that request I would respond that all code must be rebuilt in another .NET language (that is, C#).
Still, perhaps you can see if it's this static method. You'll have to de-PowerShell the code I'm afraid. The PowerShell accelerator is just a simple way for me to get at the System.Management.Automation assembly. The class is not public and the method on the class is not public either.
$verInfo = [PowerShell].Assembly.GetTypes() | Where-Object Name -eq 'PSVersionInfo'
$verInfo.GetMethod('get_PSVersion', [System.Reflection.BindingFlags]'NonPublic,Static').Invoke($null, [System.Reflection.BindingFlags]'NonPublic,Static', $null, #(), $null)
Chris

Directory Not Found Exception after publishing

I'm developing a windows form application that has different features, one of these features is that the user has the ability to generate a receipt file in a pdf format.
The Problem I'm facing now is that when I run the application from visual studio this feature works perfectly, but when I publish and run the application in my pc and in other pcs the following error pops-up:
System.IO.DirectoryNotFoundException: C:\Users\USERNAME\AppData\Local\Apps\2.0\B8GVHPQK.4RG\XND3YQM7.9AB\hsar..tion_5a44077fdab68dde_0002.0000_e0623e33977d687d\TempReceipts
at System.Security.AccessControl.NativeObjectSecurity.CreateInternal(ResourceType resourceType, Boolean isContainer, String name, SafeHandle handle, AccessControlSections includeSections, Boolean createByName, ExceptionFromErrorCode exceptionFromErrorCode, Object exceptionContext)
at System.Security.AccessControl.FileSystemSecurity..ctor(Boolean isContainer, String name, AccessControlSections includeSections, Boolean isDirectory)
at System.Security.AccessControl.DirectorySecurity..ctor(String name, AccessControlSections includeSections)
at System.IO.DirectoryInfo.GetAccessControl()
The error tells that the directory, where the receipts are stored, is can't be found, while in visual studio the there is no such error.
I've read a little about and some said that this is occurred because of permissions restrictions, I've tried to use the following code to grant the WRITE permissions in the targeted directory, but still facing the same error:
AddDirectorySecurity("TempReceipts", #"Resources\\TempReceipts", FileSystemRights.WriteData, AccessControlType.Allow);
Here is the method:
public static void AddDirectorySecurity(string FileName, string Account, FileSystemRights Rights, AccessControlType ControlType)
{
// Create a new DirectoryInfo object.
DirectoryInfo dInfo = new DirectoryInfo(FileName);
// Get a DirectorySecurity object that represents the
// current security settings.
DirectorySecurity dSecurity = dInfo.GetAccessControl();
// Add the FileSystemAccessRule to the security settings.
dSecurity.AddAccessRule(new FileSystemAccessRule(Environment.UserName, Rights, ControlType));
// Set the new access settings.
dInfo.SetAccessControl(dSecurity);
}
so could anyone please suggest a solution for this problem?

Given path format not supported c#

I get this exception when trying to perform an XSLT transformation with C#:
Exception: System.NotSupportedException: The given path's format is not supported.
at System.Security.Permissions.FileIOPermission.QuickDemand(FileIOPermissionAccess access, String fullPath, Boolean checkForDuplicates, Boolean needFullPath)
at System.Xml.XmlResolver.ResolveUri(Uri baseUri, String relativeUri)
at System.Xml.XmlUrlResolver.ResolveUri(Uri baseUri, String relativeUri)
at System.Xml.XmlTextReaderImpl..ctor(String url, XmlNameTable nt)
at System.Xml.XPath.XPathDocument..ctor(String uri, XmlSpace space)
at System.Xml.XPath.XPathDocument..ctor(String uri)
at ConsoleApplication8.Program.TransformXML(String sXmlPath, String sXslPath)
when i try to run this code
void test()
{
var myXslTrans = new XslCompiledTransform();
myXslTrans.Load(#"‪C:\Users\ahmed\Desktop\fewf\visio.xsl");
myXslTrans.Transform(#"‪C:\Users\ahmed\Desktop\fewf\page1.xml", #"‪C:\Users\ahmed\Desktop\fewf\page.html");
}
i try to use Path.Combine(); but give me the same case
How can i solve this ?
Unfortunately, it was not clear from your question that you are using Silverlight (or at least some platform that somehow triggers this method)). When I started searching for QuickDemand, it turned out that this is only called in SilverLight (or similar?) environments, so I am rewriting my answer with that in mind.
at System.Security.Permissions.FileIOPermission.QuickDemand
This error is caused by this block of code in the reference source. As can be seen there, if the path contains a colon above second position, it is considered invalid.
In your code above this isn't shown, but since this is the only place in the reference source where this specific error is thrown, I am going to assume that the real code you have (possibly any of the xsl:includes in the XSLT) is containing a colon at some position other than the second position.
Either way, if this doesn't help, go to the Exceptions screen and check the NotSupportedException and in the Debug window uncheck the "Just my code" and check the "Enable .NET framework source stepping". That way, you can break at the position where the error is thrown and use IntelliSense and the debug windows to find out the context (i.e. the actual path) that is causing this error.
Also, from my previous post, if this doesn't help:
Try your code with a minimal XSLT (this is vital!)
Once you have that running, add an xsl:include
Once you have that running, add a document (if you use that, in addition, enable it in the settings)
Once you have that running... well, you get the drift

How do I check whether File.Delete() will succeed without trying it, in C#?

In C#, System.IO.File.Delete(filePath) will either delete the specified file, or raise an exception. If the current user doesn't have permission to delete the file, it'll raise an UnauthorizedAccessException.
Is there some way that I can tell ahead of time whether the delete is likely to throw an UnauthorizedAccessException or not (i.e. query the ACL to see whether the current thread's identity has permission to delete the specified file?)
I'm basically looking to do:
if (FileIsDeletableByCurrentUser(filePath)) {
/* remove supporting database records, etc. here */
File.Delete(filePath);
}
but I have no idea how to implement FileIsDeletableByCurrentUser().
The problem with implementing FileIsDeletableByCurrentUser is that it's not possible to do so. The reason is the file system is a constantly changing item.
In between any check you make to the file system and the next operation any number of events can and will happen. Including ...
Permissions on the file could change
The file could be deleted
The file could be locked by another user / process
The USB key the file is on could be removed
The best function you could write would most aptly be named FileWasDeletableByCurrentUser.
Have you tried System.IO.File.GetAccessControl(filename) it should return a FileSecurity with information about the permissions for that file.
Strictly speaking, an UnauthorizedAccessException means that the path is a directory, so you can use a System.IO.Path.GetFileName(path) type command and catch the argument exception.
But if you want a more holistic solution, use System.IO.File.GetAccessControl as mentioned by Dale Halliwell
As stated above.
Lookup the file permissions and compare with the user who is running the application.
You could always use this aproach as well
bool deletemyfile()
{
try
{
...delete my file
return true;
}
catch
{
return false;
}
}
if it returns false you know it failed if it returns true then.. it worked and file is gone. Not sure what you're after exactly but this was the best I could think of
Of course you can check ReadOnly flags using System.IO and probably ACL security on the file combined with the current user too, but like Mehrdad writes in his comment it would never be full-proof in all cases. So you would need exception handling for the exceptional case at all times (even if its just a top-level catch-all, that logs/shows an "unexpected problem" and kills your application).
You should get the access control list (ACL) of that file.
But this doesn't necessarily mean you could actually delete it because the readonly flag could still be set or another program has locked the file.
Seems like it would be easier to do things in the order of:
Get whatever information you need about the file in order to do the other parts (delete database data, etc)
Try to delete the file
If you successfully delete the file, then carry out the rest of the "cleanup" work. If you don't successfully delete it, return/handle the exception, etc.

Categories

Resources