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.
Related
I have a C# WPF application. It uses a small commercial framework (https://www.inosoft.com/en/product/product-features/).
I'm building this application both locally and via a buildserver (Azure pipelines). I use a marketplace task to change the assemblyinfo.cs before building: https://marketplace.visualstudio.com/items?itemName=bleddynrichards.Assembly-Info-Task
The build server executes the following tasks:
NuGet restore
Inject/Edit assemblyVersion, AssemblyFileVersion and AssemblyInformationalVersion with the right version info
Build
Now when I run this application, it starts up and runs for a while.
Quickly after starting I hook the VS debugger into the process.
Then all of the sudden the application crashes:
This is weird, because when I build locally, this runtime error does not occur.
Note that i set all properties to the same values for testing:
AssemblyVersion: 1.2.3.4
AssemblyFileVersion: 5.6.7.8
AssemblyInformationalVersion: 9.10.11.12
I then use Telerik justAssembly to compare the build output from my local build and the buildserver:
As we can see the local output (on the left) does not have a version added to the Application.LoadComponent(..) whilst the build server output (on the right) does.
public void InitializeComponent()
{
if (!this._contentLoaded)
{
this._contentLoaded = true;
Application.LoadComponent(this, new Uri("/HmiMetis;component/views/app.xaml", UriKind.Relative));
}
}
This means that this is the root cause of the runtime exception.
I find it weird that the build process on my local machine differs from the build server output. Both (should) use visual studio 2017 to build. Why does the buildserver add the version to the uri of loadComponent and my local machine does not?
Anyways, I need this exception gone.
Therefore I think the easiest way would be to force the buildserver to not add the version information under any circumstances. Is this possible and how?
Edit:
I Found a relating issue report that may have something to do with this:
https://github.com/dotnet/core/issues/3189
I published a .net core console app with '/p:PublishSingleFile=true' option, but now assembly path is the temporary path where it inflated to.
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)
now returns:
C:\Users\DEFUSER\AppData\Local\Temp\.net\myApp\3dzfa4fp.353\_myApp.json
originally:
C:\devel\myApp\bin\publish\_myApp.json
How can I get the original path of where i put the exe file originally?
thanks in advance!
Based on https://github.com/dotnet/coreclr/issues/25623, which was also confirmed by Scott Hanselman a year later:
.NET Core: Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)
.NET 5: AppContext.BaseDirectory.
There is a new .NET 6.0 property Environment.ProcessPath added exactly for that reason
Returns the path of the executable that started the currently
executing process
Design notes can be found here
Docs for single-file deployment and executable mention that some Assembly APIs will not work in this mode, including 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>.
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.
So based on this you can use Environment.ProcessPath (since .NET 6) or Environment.GetCommandLineArgs()[0] (since .NET 5, can be preferable in cases when the executable is distributed and run in different ways, i.e. via single file, .exe or via dotnet command).
From Environment.GetCommandLineArgs remarks:
The first element in the array contains the file name of the executing program. If the file name is not available, the first element is equal to String.Empty. The remaining elements contain any additional tokens entered on the command line.
In .NET 5 and later versions, for single-file publishing, the first element is the name of the host executable.
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);
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.
I'm still trying to run my easyhook exercize. right now, i get this error:
System.ApplicationException: Unable to install assembly in the GAC. This usually indicates either an invalid assembly path or you are not admin.
at EasyHook.NativeAPI.GacInstallAssembly(IntPtr InContext, String InAssemblyPath, String InDescription, String InUniqueID)
at EasyHook.Config.Register(String InDescription, String[] InUserAssemblies)
at HookTest.Program.Main()
and the problem seems to originate here:
Config.Register(
"Easy hook test",
"Hook Test.vshost.exe",
"TestInject.dll");
The solution I'm trying to build is composed by two projects, a library and an application. Once I build the solution, i copy testinject.dll to the hooktest debug folder, and then I run it in debug mode.
Maybe I should use an absolute path to indicate where testinject.dll is? or add the library somewhere?
UPDATE 1
"Easy hook test",
#"Hook Test.vshost.exe",
#"I:\Documents and Settings\foo\Desktop\Hook Test\TestInject\bin\Debug\TestInject.dll");
Even with this change, I get the same error
Try changing the target framework from 4.0 to 3.5, that should do the trick.
This usually indicates either an invalid assembly path or you are not admin.
That's as good an error message as you can expect. The path could be wrong because you don't specify the full path of the assembly (i.e. c:\mumble\foo.dll). You commonly don't have admin rights because of UAC. Use a manifest to get the privilege elevation or run Visual Studio in admin mode (change the desktop shortcut).
Even though you yourself are an Admin, it does not mean that apps that you run will be elevated to admin. In fact, VS 2010 will NOT be, nor will most others. You actually have to right-click "Run as Admin...". I actually set my VS start menu shortcut's properties to "Run as Administrator" so that I never forget, as I was burned on this too.