Find out the letter of the USB [duplicate] - c#

How do I find the application's path in a console application?
In Windows Forms, I can use Application.StartupPath to find the current path, but this doesn't seem to be available in a console application.

System.Reflection.Assembly.GetExecutingAssembly().Location1
Combine that with System.IO.Path.GetDirectoryName if all you want is the directory.
1As per Mr.Mindor's comment:
System.Reflection.Assembly.GetExecutingAssembly().Location returns where the executing assembly is currently located, which may or may not be where the assembly is located when not executing. In the case of shadow copying assemblies, you will get a path in a temp directory. System.Reflection.Assembly.GetExecutingAssembly().CodeBase will return the 'permanent' path of the assembly.

You can use the following code to get the current application directory.
AppDomain.CurrentDomain.BaseDirectory

You have two options for finding the directory of the application, which you choose will depend on your purpose.
// to get the location the assembly is executing from
//(not necessarily where the it normally resides on disk)
// in the case of the using shadow copies, for instance in NUnit tests,
// this will be in a temp directory.
string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
//To get the location the assembly normally resides on disk or the install directory
string path = System.Reflection.Assembly.GetExecutingAssembly().CodeBase;
//once you have the path you get the directory with:
var directory = System.IO.Path.GetDirectoryName(path);

Probably a bit late but this is worth a mention:
Environment.GetCommandLineArgs()[0];
Or more correctly to get just the directory path:
System.IO.Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]);
Edit:
Quite a few people have pointed out that GetCommandLineArgs is not guaranteed to return the program name. See The first word on the command line is the program name only by convention. The article does state that "Although extremely few Windows programs use this quirk (I am not aware of any myself)". So it is possible to 'spoof' GetCommandLineArgs, but we are talking about a console application. Console apps are usually quick and dirty. So this fits in with my KISS philosophy.
Edit
It seems, from feedback, that most of the other solutions don't work when you are using a unit testing system. This sort of makes sense as the executable item is not your application but the testing system. I have not checked this out - so I could be completely wrong. If this is so, I will delete this edit.

For anyone interested in asp.net web apps. Here are my results of 3 different methods
protected void Application_Start(object sender, EventArgs e)
{
string p1 = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
string p2 = System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath;
string p3 = this.Server.MapPath("");
Console.WriteLine("p1 = " + p1);
Console.WriteLine("p2 = " + p2);
Console.WriteLine("p3 = " + p3);
}
result
p1 = C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Temporary ASP.NET Files\root\a897dd66\ec73ff95\assembly\dl3\ff65202d\29daade3_5e84cc01
p2 = C:\inetpub\SBSPortal_staging\
p3 = C:\inetpub\SBSPortal_staging
the app is physically running from "C:\inetpub\SBSPortal_staging", so the first solution is definitely not appropriate for web apps.

The answer above was 90% of what I needed, but returned a Uri instead of a regular path for me.
As explained in the MSDN forums post, How to convert URI path to normal filepath?, I used the following:
// Get normal filepath of this assembly's permanent directory
var path = new Uri(
System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().CodeBase)
).LocalPath;

If you are looking for a .NET Core compatible way, use
System.AppContext.BaseDirectory
This was introduced in .NET Framework 4.6 and .NET Core 1.0 (and .NET Standard 1.3). See: AppContext.BaseDirectory Property.
According to this page,
This is the prefered replacement for AppDomain.CurrentDomain.BaseDirectory in .NET Core

You may be looking to do this:
System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase)

you can use this one instead.
System.Environment.CurrentDirectory

For Console Applications, you can try this:
System.IO.Directory.GetCurrentDirectory();
Output (on my local machine):
c:\users\xxxxxxx\documents\visual studio 2012\Projects\ImageHandler\GetDir\bin\Debug
Or you can try (there's an additional backslash in the end):
AppDomain.CurrentDomain.BaseDirectory
Output:
c:\users\xxxxxxx\documents\visual studio 2012\Projects\ImageHandler\GetDir\bin\Debug\

I have used this code and get the solution.
AppDomain.CurrentDomain.BaseDirectory

Following line will give you an application path:
var applicationPath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName)
Above solution is working properly in the following situations:
simple app
in another domain where Assembly.GetEntryAssembly() would return null
DLL is loaded from Embedded resources as a byte array and loaded to AppDomain as Assembly.Load(byteArrayOfEmbeddedDll)
with Mono's mkbundle bundles (no other methods work)

You can simply add to your project references System.Windows.Forms and then use the System.Windows.Forms.Application.StartupPath as usual .
So, not need for more complicated methods or using the reflection.

I have used
System.AppDomain.CurrentDomain.BaseDirectory
when I want to find a path relative to an applications folder. This works for both ASP.Net and winform applications. It also does not require any reference to System.Web assemblies.

I mean, why not a p/invoke method?
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
public class AppInfo
{
[DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = false)]
private static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);
private static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);
public static string StartupPath
{
get
{
StringBuilder stringBuilder = new StringBuilder(260);
GetModuleFileName(NullHandleRef, stringBuilder, stringBuilder.Capacity);
return Path.GetDirectoryName(stringBuilder.ToString());
}
}
}
You would use it just like the Application.StartupPath:
Console.WriteLine("The path to this executable is: " + AppInfo.StartupPath + "\\" + System.Diagnostics.Process.GetCurrentProcess().ProcessName + ".exe");

Assembly.GetEntryAssembly().Location or Assembly.GetExecutingAssembly().Location
Use in combination with System.IO.Path.GetDirectoryName() to get only the directory.
The paths from GetEntryAssembly() and GetExecutingAssembly() can be different, even though for most cases the directory will be the same.
With GetEntryAssembly() you have to be aware that this can return null if the entry module is unmanaged (ie C++ or VB6 executable). In those cases it is possible to use GetModuleFileName from the Win32 API:
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern int GetModuleFileName(HandleRef hModule, StringBuilder buffer, int length);

I use this if the exe is supposed to be called by double clicking it
var thisPath = System.IO.Directory.GetCurrentDirectory();

in VB.net
My.Application.Info.DirectoryPath
works for me (Application Type: Class Library). Not sure about C#...
Returns the path w/o Filename as string

I didn't see anyone convert the LocalPath provided by .Net Core reflection into a usable System.IO path so here's my version.
public static string GetApplicationRoot()
{
var exePath = new Uri(System.Reflection.
Assembly.GetExecutingAssembly().CodeBase).LocalPath;
return new FileInfo(exePath).DirectoryName;
}
This will return the full C:\\xxx\\xxx formatted path to where your code is.

AppDomain.CurrentDomain.BaseDirectory
Will resolve the issue to refer the 3rd party reference files with installation packages.

With .NET Core 3 and above you will get the .dll and not the .exe file. To get the .exe file path you can use.
var appExePath = Process.GetCurrentProcess().MainModule.FileName;

Try this simple line of code:
string exePath = Path.GetDirectoryName( Application.ExecutablePath);

For .NET 6 there's Environment.ProcessPath.
See https://learn.microsoft.com/en-us/dotnet/api/system.environment.processpath?view=net-6.0

None of these methods work in special cases like using a symbolic link to the exe, they will return the location of the link not the actual exe.
So can use QueryFullProcessImageName to get around that:
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Diagnostics;
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool QueryFullProcessImageName([In]IntPtr hProcess, [In]int dwFlags, [Out]StringBuilder lpExeName, ref int lpdwSize);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr OpenProcess(
UInt32 dwDesiredAccess,
[MarshalAs(UnmanagedType.Bool)]
Boolean bInheritHandle,
Int32 dwProcessId
);
}
public static class utils
{
private const UInt32 PROCESS_QUERY_INFORMATION = 0x400;
private const UInt32 PROCESS_VM_READ = 0x010;
public static string getfolder()
{
Int32 pid = Process.GetCurrentProcess().Id;
int capacity = 2000;
StringBuilder sb = new StringBuilder(capacity);
IntPtr proc;
if ((proc = NativeMethods.OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid)) == IntPtr.Zero)
return "";
NativeMethods.QueryFullProcessImageName(proc, 0, sb, ref capacity);
string fullPath = sb.ToString(0, capacity);
return Path.GetDirectoryName(fullPath) + #"\";
}
}

Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName)
Is the only one that has worked for me in every case I have tried.

In .NET 6, my WPF app (<TargetFramework>net6.0-windows</TargetFramework>) returns the .dll file path for Assembly.GetEntryAssembly()!.Location instead of the .exe file. They introduced System.Environment.ProcessPath for this purpose:
var path = Environment.ProcessPath; // Note it may be null
Returns the path of the executable that started the currently executing process. Returns null when the path is not available.
See discussion for it here and here.

I use this for console + net 6
Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location)

The techniques, and pitfalls, keep changing. The below assumes you're running a .NET 6 console app on linux (on win/mac the results will follow a similar pattern, just replace /usr/share/ and /home/username/ with the standard locations for your OS).
Demo:
Console.WriteLine("Path.GetDirectoryName(Process.GetCurrentProcess()?.MainModule?.FileName) = " + Path.GetDirectoryName(Process.GetCurrentProcess()?.MainModule?.FileName));
Console.WriteLine("Path.GetDirectoryName(Environment.ProcessPath) = " + Path.GetDirectoryName(Environment.ProcessPath));
Console.WriteLine("Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) = " + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
Console.WriteLine("typeof(SomeType).Assembly.Location = " + typeof(SomeType).Assembly.Location);
Console.WriteLine("Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]) = " + Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]));
Console.WriteLine("AppDomain.CurrentDomain.BaseDirectory = " + AppDomain.CurrentDomain.BaseDirectory);
Console.WriteLine("System.AppContext.BaseDirectory = " + System.AppContext.BaseDirectory);
Results:
Path.GetDirectoryName(Process.GetCurrentProcess()?.MainModule?.FileName) = /usr/share/dotnet
Path.GetDirectoryName(Environment.ProcessPath) = /usr/share/dotnet
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) = /home/username/myproject/bin/Debug/net6.0
typeof(SomeType).Assembly.Location = /home/username/myproject/bin/Debug/net6.0
Path.GetDirectoryName(Environment.GetCommandLineArgs()[0]) = /home/username/myproject/bin/Debug/net6.0
AppDomain.CurrentDomain.BaseDirectory = /home/username/myproject/bin/Debug/net6.0/
System.AppContext.BaseDirectory = /home/username/myproject/bin/Debug/net6.0/
Each approach has its own pros and cons - see the other answers to learn in which uses cases to use which approach.
I run my .NET 6 console app with dotnet myapp, so what works (reliably) for me is either of:
typeof(SomeType).Assembly.Location
// or
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)

Here is a reliable solution that works with 32bit and 64bit applications.
Add these references:
using System.Diagnostics;
using System.Management;
Add this method to your project:
public static string GetProcessPath(int processId)
{
string MethodResult = "";
try
{
string Query = "SELECT ExecutablePath FROM Win32_Process WHERE ProcessId = " + processId;
using (ManagementObjectSearcher mos = new ManagementObjectSearcher(Query))
{
using (ManagementObjectCollection moc = mos.Get())
{
string ExecutablePath = (from mo in moc.Cast<ManagementObject>() select mo["ExecutablePath"]).First().ToString();
MethodResult = ExecutablePath;
}
}
}
catch //(Exception ex)
{
//ex.HandleException();
}
return MethodResult;
}
Now use it like so:
int RootProcessId = Process.GetCurrentProcess().Id;
GetProcessPath(RootProcessId);
Notice that if you know the id of the process, then this method will return the corresponding ExecutePath.
Extra, for those interested:
Process.GetProcesses()
...will give you an array of all the currently running processes, and...
Process.GetCurrentProcess()
...will give you the current process, along with their information e.g. Id, etc. and also limited control e.g. Kill, etc.*

You can create a folder name as Resources within the project using Solution Explorer,then you can paste a file within the Resources.
private void Form1_Load(object sender, EventArgs e) {
string appName = Environment.CurrentDirectory;
int l = appName.Length;
int h = appName.LastIndexOf("bin");
string ll = appName.Remove(h);
string g = ll + "Resources\\sample.txt";
System.Diagnostics.Process.Start(g);
}

Related

Using log4net With Visual Studio Debugger

I'm having trouble getting my log4net.config file to load when using Visual Studio in debug mode for an Excel VSTO Plugin. The config file is in the top level directory of my project. I have the property "Copy to Output Directory" set to "Copy Always". This ensures the file is copied to bin/Debug/log4net.config. I can verify this is the case when I build.
However, the file won't load when I run in Debug mode. I gave up on trying to get the file to load automatically and decided to do it by code, as per the OP's code at the bottom of this question.
However, I realised that I needed to use an absolute path to the config file, as relative paths weren't picking it up. On further investigation, I realised that the executing DLL wasn't actually the DLL in the debug/bin folder. It was in the following location:
C:\Users\cbhandal\AppData\Local\assembly\dl3\MO52QQWP.9ZL\K36XZHGN.1PB\230751e6\d09b7fb2_19f6d401
Also the current working directory, as found by System.IO.Directory.GetCurrentDirectory(); was set to "C:\\Users\\cbhandal\\Documents".
Hard-coding the path as an absolute path works as in the following code:
var log4netConfig = "C:\\" + path + "\\Log4net.config";
var log4netInfo = new FileInfo(log4netConfig);
log4net.Config.XmlConfigurator.ConfigureAndWatch(log4netInfo);
But that's not a solution I can deploy. I'm stuck here. Wondering if there's a way to either force Visual studio to copy the .config file to that appdata/temp location, or if there's a way to programatically reference the folder where the original DLL lay- the one that was built. Or if anyone had any other solution?
For me the easiest solution was to use this:
https://stackoverflow.com/a/6963420/4754981
But there are several other solutions on that link for different approaches, each with their caveats.
So mine looks like this:
using System.Reflection;
using System.IO;
using System;
public static class Extensions {
private static string GetDirectory(this Assembly a) {
string codeBase = a.CodeBase;
UriBuilder uri = new UriBuilder(codeBase);
string path = Uri.UnescapeDataString(uri.Path);
return Path.GetDirectoryName(path);
}
private static void AlterLogPath(this log4net.Repository.ILoggerRepository repo, string newPath, string directory="") {
log4net.Repository.Hierarchy.Hierarchy h = (log4net.Repository.Hierarchy.Hierarchy) repo;
foreach (log4net.Appender.IAppender a in h.Root.Appenders) {
if (a is log4net.Appender.FileAppender) {
var fa = (log4net.Appender.FileAppender)a;
var fileName = Path.GetFileName(fa.File);
fa.File = newPath + (String.IsNullOrEmpty(directory)?"":(directory + Path.DirectorySeparatorChar.ToString())); // edit: filename is attached after next line automatically.
fa.ActivateOptions();
break;
}
}
}
}
and in the bootup (via [assembly: System.Web.PreApplicationStartMethod] or otherwise for asp), or main app..
static void Main() {
var PATH = Assembly.GetExecutingAssembly().GetDirectory() + Path.DirectorySeparatorChar.ToString();
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(PATH + "log4net.config"));
log4net.LogManager.GetRepository().AlterLogPath(PATH, "Logs");
}

Location of |DataDirectory| in WebApi project/template

I do realize there are many questions here on how to change the value of |DataDirectory|. My question is slightly different.
I realized that if you are using a WebApi project with EntityFramework, then the |DataDirectory| points to the App_Data folder which is created by default. I believe the same is true for an MVC project.
So my question is where can I find the code that has specified App_Data as |DataDirectory| in these standard templates. I just want to use the same code for my class library
Thanks
|DataDirectory| is defined in HttpRuntime class under System.Web assembly.
I decompiled System and System.Web assembly and I found source code for AppDomain class and some DataDirectory related following code.
App_Data is a hard coded string in the assembly. However it can be overridden using AppDomain.CreateDomain() method.
Here is the code I found.
Global variable in HttpRuntime class
internal const string DataDirectoryName = "App_Data";
And a function in HttpRuntime class
private void SetUpDataDirectory()
{
string path = Path.Combine(this._appDomainAppPath, "App_Data");
AppDomain.CurrentDomain.SetData("DataDirectory", (object) path, (IPermission) new FileIOPermission(FileIOPermissionAccess.PathDiscovery, path));
}
I found another piece of code in System.Data.Common assembly under DbConnectionOptions.cs file. Here is the code.
internal const string DataDirectory = "|datadirectory|";
And then I found another code in System.Web.DataAccess assembly under SqlConnectionHelper class. Here is the code.
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
internal static string GetDataDirectory()
{
if (HostingEnvironment.IsHosted)
return Path.Combine(HttpRuntime.AppDomainAppPath, "App_Data");
string path1 = AppDomain.CurrentDomain.GetData("DataDirectory") as string;
if (string.IsNullOrEmpty(path1))
{
string path1_1 = (string) null;
Process currentProcess = Process.GetCurrentProcess();
ProcessModule processModule = currentProcess != null ? currentProcess.MainModule : (ProcessModule) null;
string path2 = processModule != null ? processModule.FileName : (string) null;
if (!string.IsNullOrEmpty(path2))
path1_1 = Path.GetDirectoryName(path2);
if (string.IsNullOrEmpty(path1_1))
path1_1 = Environment.CurrentDirectory;
path1 = Path.Combine(path1_1, "App_Data");
AppDomain.CurrentDomain.SetData("DataDirectory", (object) path1, (IPermission) new FileIOPermission(FileIOPermissionAccess.PathDiscovery, path1));
}
return path1;
}
This means, |DataDirectory| is defined and used at multiple places. But it's common place to get the data is from HttpRuntime and AppDomain property.
I am using JetBrain's DotPeek to decompile these assemblies. I hope the same helps you.

How to install a windows font using C#

How can I install a font using C#?
I tried copying the fonts using File.Copy() but I am not allowed due to access rights limitations (UnauthorizedException).
What should I do?
You'll need a different approach installing fonts.
Use an installer (create a setup project) to install the fonts
Another (more easy) approach using a native method.
Declare the dll import:
[DllImport("gdi32.dll", EntryPoint="AddFontResourceW", SetLastError=true)]
public static extern int AddFontResource(
[In][MarshalAs(UnmanagedType.LPWStr)]
string lpFileName);
In your code:
// Try install the font.
result = AddFontResource(#"C:\MY_FONT_LOCATION\MY_NEW_FONT.TTF");
error = Marshal.GetLastWin32Error();
The source:
http://www.brutaldev.com/post/2009/03/26/Installing-and-removing-fonts-using-C
I put it together in a unit test, I hope that helps:
[TestFixture]
public class Tests
{
// Declaring a dll import is nothing more than copy/pasting the next method declaration in your code.
// You can call the method from your own code, that way you can call native
// methods, in this case, install a font into windows.
[DllImport("gdi32.dll", EntryPoint = "AddFontResourceW", SetLastError = true)]
public static extern int AddFontResource([In][MarshalAs(UnmanagedType.LPWStr)]
string lpFileName);
// This is a unit test sample, which just executes the native method and shows
// you how to handle the result and get a potential error.
[Test]
public void InstallFont()
{
// Try install the font.
var result = AddFontResource(#"C:\MY_FONT_LOCATION\MY_NEW_FONT.TTF");
var error = Marshal.GetLastWin32Error();
if (error != 0)
{
Console.WriteLine(new Win32Exception(error).Message);
}
}
}
That should help you on your way :)
internal static void InstalarFuente(string NombreFnt,string RutaFnt)
{
string CMD = string.Format("copy /Y \"{0}\" \"%WINDIR%\\Fonts\" ", RutaFnt);
EjecutarCMD(CMD);
System.IO.FileInfo FInfo = new System.IO.FileInfo(RutaFnt);
CMD = string.Format("reg add \"HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\" /v \"{0}\" /t REG_SZ /d {1} /f", NombreFnt, FInfo.Name);
EjecutarCMD(CMD);
}
public static void EjecutarCMD(string Comando)
{
System.Diagnostics.ProcessStartInfo Info = new System.Diagnostics.ProcessStartInfo("cmd.exe");
Info.Arguments = string.Format("/c {0}", Comando);
Info.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process.Start(Info);
}

Obtaining path of users directory

How to get path of Users folder from windows service on MS Vista?
I think about path of C:\Users directory, but it may be different location depend on system localization.
Take a look at the Environment.SpecialFolder Enumeration, e.g.
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
Adjust for the special folder you want. However, in reading another post found here, it looks like you may need to do a little manipulation of the string if you want exactly c:\users instead of c:\users\public, for example.
System.Environment.SpecialFolder will give you access to all these folders that you want, such as My Documents, Etc..
If you use the UserProfile SpecialFolder, that should give you the path to your profile under Users.
string userPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
The best way as #Neil pointed out is to use SHGetKnownFolderPath() with the FOLDERID_UserProfiles. However, c# doesn't have that. But, it's not that hard to invoke it:
using System;
using System.Runtime.InteropServices;
namespace SOExample
{
public class Main
{
[DllImport("shell32.dll")]
static extern int SHGetKnownFolderPath([MarshalAs(UnmanagedType.LPStruct)] Guid rfid, uint dwFlags, IntPtr hToken, out IntPtr pszPath);
private static string getUserProfilesPath()
{
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457(v=vs.85).aspx#folderid_userprofiles
Guid UserProfilesGuid = new Guid("0762D272-C50A-4BB0-A382-697DCD729B80");
IntPtr pPath;
SHGetKnownFolderPath(UserProfilesGuid, 0, IntPtr.Zero, out pPath);
string path = System.Runtime.InteropServices.Marshal.PtrToStringUni(pPath);
System.Runtime.InteropServices.Marshal.FreeCoTaskMem(pPath);
return path;
}
static void Main(string[] args)
{
string path = getUserProfilesPath(); // C:\Users
}
}
}
I can't see that function exposed to .NET, but in C(++) it would be
SHGetKnownFolderPath(FOLDERID_UserProfiles, ...)
System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal)

FileVersionInfo.GetVersionInfo() incorrect in Console Application

I'm getting some serious weirdness using FileVersionInfo.GetVersionInfo() and was hoping somebody might be able to help.
The basics of the issue is that I am iterating through all the files in a folder calling GetVersionInfo() on each. There are about 300 files. This works ok for all but 2 of the files. For these DLLs I am getting comepletely incorrect info back from GetVersionInfo().
In order to eliminate all other variables, I extracted this call into a simple test app and it still got the same problem. However, if I built the test app as a Windows Application (it was a Console Application initially) then the data came back correct.
Just to clarify, the incorrect data coming back when running as a Console App is not simply null info like you would get if the file didn't contain version data. It contained reasonable data, but just the wrong data. It's as if it's reading it from a different file. I've looked for a file that contains matching version data, but can't find one.
Why is this simple call functioning differently if built as a Console Application rather than a Windows Application?
If anyone can help with this I would be very grateful.
Rgds,
Andy
-- Code Added
using System;
using System.Diagnostics;
namespace test
{
class Program
{
static void Main(string[] args)
{
string file = "C:\\ProblemFile.dll";
FileVersionInfo version = FileVersionInfo.GetVersionInfo(file);
string fileName = version.FileName;
string fileVersion = version.FileVersion;
Console.WriteLine(string.Format("{0} : {1}", fileName, fileVersion));
}
}
}
This behaviour seems weird indeed. Could it be that the Console application does not load the DLL from the same place as the WinForms application does? This would mean that GetVersionInfo uses some other API than just Win32 CreateFile (maybe going through some DLL resolver mechanism, side-by-side or whatever); remember that under the covers, version.dll will be executing your request, not the CLR itself.
Looking at FileVersionInfo through Reflector points in another direction yet:
public static unsafe FileVersionInfo GetVersionInfo(string fileName)
{
// ...
int fileVersionInfoSize = UnsafeNativeMethods.GetFileVersionInfoSize(fileName, out num);
FileVersionInfo info = new FileVersionInfo(fileName);
if (fileVersionInfoSize != 0)
{
byte[] buffer = new byte[fileVersionInfoSize];
fixed (byte* numRef = buffer)
{
IntPtr handle = new IntPtr((void*) numRef);
if (!UnsafeNativeMethods.GetFileVersionInfo(fileName, 0, fileVersionInfoSize, new HandleRef(null, handle)))
{
return info;
}
int varEntry = GetVarEntry(handle);
if (!info.GetVersionInfoForCodePage(handle, ConvertTo8DigitHex(varEntry)))
{
int[] numArray = new int[] { 0x40904b0, 0x40904e4, 0x4090000 };
foreach (int num4 in numArray)
{
if ((num4 != varEntry) && info.GetVersionInfoForCodePage(handle, ConvertTo8DigitHex(num4)))
{
return info;
}
}
}
}
}
return info;
}
As you can see there, some interesting dance is going on with code pages. What if the DLLs you inspected had several version information resources attached to them? Depending on the culture of the program calling into GetVersionInfo, I guess that the code page related calls could return other results?
Take the time to check the resources of the DLLs and make sure that there is only one language/code page for the version information. It might point you at the solution, I hope.
Sure the "files" you're seeing aren't . and .. ? If you iterate through all files, you'll always see entries for . (current dir) and .. (up dir). GetVersion Info might well return anything for these. You'd have to filter these entries out manually by name.
File and Assembly versions are 2 different things.
Are you sure you are not expecting the other?
Update: Tried this. Didn't work.
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace test
{
class Program
{
[DllImport("COMCTL32")]
private static extern int InitCommonControls(int nExitCode);
static void Main(string[] args)
{
InitCommonControls(0);
string file = "C:\\ProblemFile.dll";
FileVersionInfo version = FileVersionInfo.GetVersionInfo(file);
string fileName = version.FileName;
string fileVersion = version.FileVersion;
Console.WriteLine(string.Format("{0} : {1}", fileName, fileVersion));
}
}
}

Categories