Is this really a bug with ConfigurationManager.OpenExeConfiguration? - c#

The documentation for ConfigurationManager.OpenExeConfiguration(string exePath) states:
Opens the specified client configuration file as a Configuration
object.
It also states that exePath is "The path of the executable (exe) file"
The method is supposed to open the *.exe.config file for the executable at the path specified by exePath, and that a ConfigurationErrorsException will be thrown if "A configuration file could not be loaded.".
The following code uses the path of a non-executable and the directory of that path contains no *.exe.config files. Yet the code executes without any exceptions, nor any other sign of an invalid argument.
var configs = Directory.GetFiles("C:\\NoConfig", "*.config");
Debug.Assert(configs.Length == 0);
File.WriteAllText("C:\\NoConfig\\notes.txt", "This is not an executable, and there is no .config file in its folder.");
var config = ConfigurationManager.OpenExeConfiguration("c:\\notes.txt");
Debug.Assert(config != null);
Yet it will now slowly become deprecated for the new .NET Core JSON based configuration, and will not be reviewed or fixed anyway.
So, is this due to a bug in this overload of the OpenExeConfiguration method?
I just wanted a 2nd, and nth opinion before I raised it on MS Connect. And Connect is down at the moment.
ADDED: If I call OpenExeConfiguration with a valid exePath, to a real executable (tested), with a valid .config file, then it reads but does not parse the file. I have to request the xml for the appSettings section and parse it myself, using the workaround from this answer to AppSettings from custom files. This adds to my suspicion that this code is not commonly used in this mode, has been accepted as working and not reviewed, and could thus be buggy.
I'm sure it will receive scant attention with the new .NET Core configuration API replacing the old XML only one.

So you have two concerns as I understand:
OpenExeConfiguration does not fail for files with extensions other than "exe".
OpenExeConfiguration does not fail if configuration file not already exists.
I understand both points, but I'd say both of them are arguable.
Executable file does not necessary mean file with .exe extension. Yes, on Windows that's usually true, but let's take Linux for example (and we can do that because .NET is not restricted to Windows only). I can have executable .NET file with any extension (even notes.txt), or without extension at all, it doesn't matter. I can happily execute that file with "mono notes.txt" and it will run as usual.
Non existing configuration file is not an exceptional condition for Configuration object. It even has property named HasFile which indicates if that file exists or not. I can do the following with your code:
var config = ConfigurationManager.OpenExeConfiguration("c:\\notes.txt");
// config.HasFile == false here
config.AppSettings.Settings.Add("test", "test");
config.Save(ConfigurationSaveMode.Full, true);
// config.HasFile == true here, and file is written to disk

Not sure whether it could be called a bug or not but
As per this reference
According to
http://social.msdn.microsoft.com/Forums/en-US/winforms/thread/3943ec30-8be5-4f12-9667-3b812f711fc9
the parameter is the location of an exe, and the method then looks for
the config corresponding to that exe (I guess the parameter name of
exePath makes sense now!).
It also gives a workaround --
ExeConfigurationFileMap map = new ExeConfigurationFileMap { ExeConfigFilename = "EXECONFIG_PATH" };
Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

Related

C# console application not running stand-alone after being published

I'm rather new to C# and Visual Studio.
The problem I have is after I publish my C# console app in Visual Studio. The console app is not handling the augments correctly and is throwing an error.
I have setup a few Launch Profiles to test out how the console app will behave with different arguments. They all pass and give me the expected results when I run the Debug and Release Profiles.
However, when I go to publish and I select self-contained and select Produce single file. (Published Profile Settings.) The app no longer works as expected.
I get the following error:
Generic Exception Handler: System.ArgumentNullException: Value cannot be null. (Parameter 'paths')
at System.IO.Path.Combine(String[] )
at AMP_New_Project.AMP_New_Project_Folder.Main(String[] args) in C:\Users\eriskedahl\Documents\GitHub\JMS\NewAMP_Proj_Console\AMP_New_Proj.cs:line 135
PS C:\Users\erisk\Documents\GitHub\JMS\NewAMP_Proj_Console\Releases>
Now the error itself is fine and looks like my the exception handling is working, but I have a debug profile to check this and the path is allowed to be null. There is an if statement to handle what happens when a path argument is not supplied.
if (strWorkPath == null || strWorkPath.Length == 0)
{
strWorkPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)!;
}
It seems the console app is not self-contained and is missing some references but not sure where else to check. Why would the app behave differently after being published?
When I run debug, and no path is given, the the variable strWorkPath is null and then gets set to the console app's current working directory.
As mentioned this works completely fine and as expected when I run the app through the debug in Visual Studio, I only get this error when I run the published version. If I open a Powershell or cmd window and go to the Bin/Release folder and run the application from there I also get the expected results. The App fails once I copy the exe file to a different folder.
When publishing as a single file, the build system bundles all of the assemblies which make up your application together.
The Assembly.Location docs say:
In .NET 5 and later versions, for bundled assemblies, the value returned is an empty string.
So we're passing an empty string to Path.GetDirectoryName, which says:
Directory information for path, or null if path denotes a root directory or is null.
So that's probably what's happening: Assembly.Location returns an empty string, because the assembly has been bundled together with a load of other assemblies as part of the publish, and Path.GetDirectoryName turns that into null.
To get the application's location in a way which supports bundling, use AppDomain.CurrentDomain.BaseDirectory.
Note that this is not the current working directory. The CWD is the directory that the user is in when they run your application. It's normally assumed that if the user passes a relative path to your application (e.g. as a command-line argument), the application should interpret that as relative to the user's CWD.
Problem is not that you are using self-contained deployment but that you also using single-file deployment (one of the checkboxes).
Docs for single-file deployment and executable mention that some Assembly APIs will not work in single-file deployment mode, including the Location, which will return an empty string:
API
Note
Assembly.CodeBase
Throws System.PlatformNotSupportedException.
Assembly.EscapedCodeBase
Throws System.PlatformNotSupportedException.
Assembly.GetFile
Throws System.IO.IOException.
Assembly.GetFiles
Throws System.IO.IOException.
Assembly.Location
Returns an empty string.
AssemblyName.CodeBase
Returns null.
AssemblyName.EscapedCodeBase
Returns null.
Module.FullyQualifiedName
Returns a string with the value of <Unknown> or throws an exception.
Marshal.GetHINSTANCE
Returns -1.
Module.Name
Returns a string with the value of <Unknown>.
Which leads to Path.GetDirectoryName returning null.
There are some workarounds mentioned:
To access files next to the executable, use System.AppContext.BaseDirectory
To find the file name of the executable, use the first element of System.Environment.GetCommandLineArgs, or starting with .NET 6, use the file name from System.Environment.ProcessPath.
To avoid shipping loose files entirely, consider using embedded resources.
So you can use System.AppContext.BaseDirectory to determine the directory.

Excel files are not being read when executing Selenium C# Scripts on Azure Releases

I have some Selenium C# tests hosted on Azure which they need to look for the pre-built excel file in project tree, stored inside bin folder, to execute some file upload validation scenarios.
When they are executed locally these scenarios pass without any problem, but when it comes to be executed on the Azure they receive the following error.
invalid argument: File not found : D:\a\r1\a_Selenium_Tests\TestApplication\bin\Debug\netcoreapp3.1\Files\SC003_CT014_ActiveEmployees.xlsx
The files do exists in the following path: ...\bin\Debug\netcoreapp3.1\Files...
And the code I use to them is:
string root = Directory.GetCurrentDirectory() + "\\Files\\" + file;
Do you know if there's a missing file configuration or building the filePath in another way?
Thanks for your help :D
Directory.GetCurrentDirection() returns the current working directory, not the folder in which the DLL file resides. The current working directory is a different thing. In your case, the current working directory is probably something like D:\a\r1\. Instead, you need to get the folder in which the test assembly resides:
var binDirectory = Path.GetDirectoryName(GetType().Assembly.Location);
// ^^^^^^^^^
var excelFilePath = Path.Combine(binDirectory, "Files", "SC003_CT014_ActiveEmployees.xlsx");
Note: Replace GetType() with typeof(ClassName) if you are executing your code from a static method, or you would like to specify a path to a different assembly than the one that is currently executing.

How do I get the current executing directory on linux with .Net Core?

I need to load in an XML schema file to validate some information on a .Net Core 2.1 api that is on a linux server. Unfortunately, I do not have access to this server (we use jenkins to deploy so I have 0 contact with it), so I can only test on my computer which is Windows 10.
I have tried the following:
System.AppContext.BaseDirectory
System.AppContext.BaseDirectory.Substring(0, AppContext.BaseDirectory.IndexOf("bin"));
AppDomain.CurrentDomain.BaseDirectory
Directory.GetCurrentDirectory()
GetType().Assembly.Location
System.Reflection.Assembly.GetExecutingAssembly().Location
System.Reflection.Assembly.GetExecutingAssembly().CodeBase
All of these return the current execution location on Windows (i.e. C:/SomePath/SomeProject/Name/api.dll) which I can use with Path.Combine to produce the path to the schema file.
However, on linux, these all return /home/app/ which is not where the dll should be according to the Jenkins logs. This is leading to failures loading the schema file. The project is actually located under /services/projectname/.
Test Code:
var schema = new XmlSchemaSet { XmlResolver = new XmlUrlResolver() };
schema.Add(null, Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Schema/schema.xsd"));
Expected: On Windows and Linux this loads the schema file using the .dll execution path as the base.
Actual: On Linux I get home/app instead of the correct path.
Edit: I cannot hardcode the path. The path changes on every deployment as the project name is versioned. This means the second I deploy it, any hardcoded value will be incorrect. I absolutely require a relative path. Beyond that technical requirement, hard coding is a major taboo. I will never get it past a code review.
For me, in dotnet core 3.1 the following statement works for a console application in both Windows and Ubuntu:
Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

C# File.Exists returns false, file does exist

Using VS 15, C# with .Net 4.5.2
The computer is on an AD network, with the ad name "AD".
This problem happens with AD normal-user rights, AD admin rights, and local admin rights. It doesn't matter what rights the program gets, the same problem occurs.
Our test file is "C:/windows/system32/conhost.exe".
The file above exists, it is very much existing. I can see it with explorer.
This is the file in explorer:
This is the file properties:
You can see that it is there, right?
The following cmd command checks if the file exists:
IF EXIST "C:\windows\system32\conhost.exe" (echo does exist) ELSE (echo doesnt exist)
It returns "does exist" as promised.
The following C# code checks if the file exists:
FileInfo file = new FileInfo("C:/windows/system32/conhost.exe");
MessageBox.Show(file.Exists + "");
This returns "False".
This code also returns "False":
MessageBox.Show(File.Exists("C:/windows/system32/conhost.exe") + "");
This code also doesn't find it:
foreach (string file in Directory.GetFiles("C:/windows/system32/"))
{
//conhost is NEVER mentioned, like it doesn't exist
}
This code also doesn't find it:
foreach (string file in Directory.EnumerateFiles("C:/windows/system32/"))
{
//conhost is NEVER mentioned, like it doesn't exist
}
False, False, False:
MessageBox.Show(File.Exists("C:/windows/system32/conhost.exe") + "");
MessageBox.Show(File.Exists("C:\\windows\\system32\\conhost.exe") + "");
MessageBox.Show(File.Exists(#"C:\windows\system32\conhost.exe") + "");
What am I doing wrong?
Extra note: I copied conhost to C:\conhost.exe, and my program can find that without problem. My program also finds other files in system32, just not conhost and a few others. For example, it finds "connect.dll" which is in system32, so it's not the directory's read permission.
More extra notes: conhost.exe and connect.dll has the same security attributes (Security tab in the file properties).
If you are using x64 system, you will have different content of the c:\Windows\System32 directory for x86 and x64 applications. Please be sure that you are using same architecture running batch file and your C# app.
In the MSDN documentation for System.IO.File.Exists(path), it states:
If the caller does
not have sufficient permissions to read the specified file, no
exception is thrown and the method returns false regardless of the
existence of path.
For this reason, we can safely assume that your application does not have read access to that specific file. Check the security settings and grant read access if not already done so.
Build your application (in release mode) and run as administrator.
This is the problem that come over 64-bit operating system... here is a work around,
go to the project's properties > click on build tab > untick Prefer 32-bit
after that, it should work correctly over 64-bit os.

Could not find a part of the path 'C:\Program Files (x86)\IIS Express\~\TextFiles\ActiveUsers.txt'

I tried many ways to access a text file in my Visual Studio 2012 Solution from a folder named TextFiles
using (System.IO.StreamWriter file = new System.IO.StreamWriter(#"~/TextFiles/ActiveUsers.txt", true))
{
file.WriteLine(model.UserName.ToString());
}
But it kept on throwing the error
Could not find a part of the path 'C:\Program Files (x86)\IIS
Express\~\TextFiles\ActiveUsers.txt'.
Not sure where I made a mistake
You need to use HttpServerUtility.MapPath which will turn the ~/ portion of the path in to the real location it resildes on your hard drive.
So that would change your code to (assuming you are in one of the IIS classes that expose a Server property to it's methods)
var path = Server.MapPath(#"~/TextFiles/ActiveUsers.txt");
using (System.IO.StreamWriter file = new System.IO.StreamWriter(path, true))
{
file.WriteLine(model.UserName.ToString());
}
I ran into a similar issue and ended up using
string sFileName = HttpContext.Current.Server.MapPath(#"~/dirname/readme.txt");
This is an old question but I just ran into this problem myself and wanted to add what I've just discovered, in case it's helpful to anyone else.
If you have UAC turned off but are not running with elevated permissions, and try to write to restricted files (e.g. the "Program Files" folder) you'll get the "could not find a part of the path" error, instead of the (correct) access denied error.
To eliminate the problem, run with elevated permissions as in this solution: https://stackoverflow.com/a/1885543/3838199
~ is not the "user home" or anything else in Windows. You can still set the path as relative to the working directory (where the executable is) by just not specifying a full path.
For .netcore 3.x
You should make use of IWebHostEnvironment using dependency injection.
You can then use it in your code this way
string wwwRootPath = _hostEnvironment.WebRootPath;
string path = Path.Combine(wwwRootPath, $"TextFiles{Path.PathSeparator}ActiveUsers.txt");
Ensure to use PathSeparator otherwise you might face the same error due to the variance in your hosting environment.

Categories