I am trying to write a small build event utility to read the current assembly version of my code.
Thus, I want that executable to be in my %PATH% (or some central location at least) and be able to take a relative path to my target assembly as an argument (to Assembly.LoadFile() it) but Assembly.LoadFile() complains about receiving a relative path.
How can I do:
C:\calling\path>mytool rel\path\to\target.exe
Without having to type C:\devpath\to\mytool.exe every time?
And be able to get the string "C:\calling\path\rel\path\to\target.exe" in mytool.exe?
It turns out that this is a bit of a pain.
A C# assembly requires its support files to be in the same directory as itself so in the simple case, we can't just copy the exe to our chosen directory.
Another possibility is to create a shortcut to the exe and move that to the directory. Unfortunately, despite having an impressive amount of "current" path getters, C# doesn't have a way to know if it's been started through a shortcut.
When invoked from a shortcut in C:\calling\path, all of the following:
Console.WriteLine(Environment.CurrentDirectory);
Console.WriteLine(Directory.GetCurrentDirectory());
Console.WriteLine(Assembly.GetEntryAssembly().Location);
Console.WriteLine(System.AppDomain.CurrentDomain.BaseDirectory);
Console.WriteLine(System.AppContext.BaseDirectory);
//Console.WriteLine(Process.GetCurrentProcess().StartInfo.WorkingDirectory);
Console.WriteLine(Process.GetCurrentProcess().MainModule.FileName);
Console.WriteLine(Environment.GetCommandLineArgs()[0]);
return a variation of "C:\devpath\to". Except for Process.StartInfo which just crashes. You can't get the StartInfo of a process you didn't Process.Start(), including your own.
The only way that appears to work to be able to retrieve "C:\calling\path" is to create a batch file in "C:\inpath" that calls the original assembly directly.
So:
C:\inpath\exe.bat:
C:\devpath\to\mytool.exe %*
C:\devpath\to\mytool.cs:
Console.WriteLine(Environment.CurrentDirectory);
Console.WriteLine(Directory.GetCurrentDirectory());
These 2 will return "C:\calling\path".
Another option could be to add C:\devpath\to itself to the PATH
Related
While in a search to learn more about the streamreader, i came across this from
StreamReader path changes automatically
post #2 by
Hans Passant
var exedir = Path.GetDirectory(Assembly.GetEntryAssembly().Location);
var path = Path.Combine(exedir, #"Config\launcher.txt"); using (var
reader = new StreamReader(path)) {
//... }
Now I understand the "never hard code the file" part, In delphi, I am able to specify a custom file path based on the files in a directory ie.
Read all file names in a directory, user chooses a file name, and then I read the contents of the file
Delphi, I just used a string and added the file name to the end, how does that differ from the code above, and is there a different method to this above?
and btw. Could someone just explain in a bit more detail, the methods and variables used and why (i am still new to c#)
I think what Hans is saying here is that if you are expecting the files to be relative to your application code, then expliclty look relative to your application code. Don't rely on the current directory being the base location of your application, as that is not guaranteed (and can change during your application's execution). So what the code does is:
obtain the entry-point assembly (the assembly with the Main method that was executed at startup), and obtion the file location of that assembly: Assembly.GetEntryAssembly().Location
obtain the directory from this file path: var exedir = Path.GetDirectory(...);
construct a path relative to this directory: var path = Path.Combine(exedir, #"Config\launcher.txt");
create a reader based on the final path: using (var
reader = new StreamReader(path)) ...
GetEntryAssembly returns, according to the documentation:
The assembly that is the process executable in the default
application domain, or the first executable that was
executed by AppDomain.ExecuteAssembly.
And the Location property returns the full path of the assembly. So
Assembly.GetEntryAssembly().Location
is the full path of the executable. In Delphi you would write ParamStr(0) or Application.ExeFileName.
Path.GetDirectory strips off the file name and leaves the directory. In Delphi you'd use ExtractFilePath.
And Path.Combine simple joins two path components, adding a path separator if necessary. In Delphi you'd use TPath.Combine from the IOUtils unit.
So the code in the question constructs the full path to a file named
<exedir>\Config\launcher.txt
where is the directory containing the main executable assembly which is of course known only at runtime.
There's really nothing particularly different between the way things are done in Delphi and C#. To construct the same path in either language you take the exact same steps modulo syntax/method name differences.
Possibly you are in the habit of assuming that the working directory is the directory containing the executable assembly. If so, lose that habit. The working directory is meaningful for a console application and for such an application it can be considered an input to the program. But don't expect it to be stable in a GUI program. And never assume that the working directory contains the executable. That assumption is not valid.
On the other hand, perhaps you really do want a path relative to the working directory. In which case simply supply the filename and let the system take care of the rest.
I am using WritePrivateProfileString in c# (through DllImport) to store paths taken from textboxes on the interface. And the .ini file name is hardcoded in my application
string ini_file = ".\\config.ini";
However, when the file writing happens, the configuration file is written to the first path taken from the interface instead of writing it to the exe directory. Which is quite odd.
Debugging shows that the values are sent correctly to the WritePrivateProfileString but it still is written to the wrong location. Anyone knows why is that happenening?
I'd guess that something is changing the working directory of your process, most likely your code in the process. Note that the documentation has this to say:
If the lpFileName parameter does not contain a full path and file name for the file, WritePrivateProfileString searches the Windows directory for the file. If the file does not exist, this function creates the file in the Windows directory.
Now my guess is that this applies if you supply just a file name. Because your file name starts with . I believe that will force the function to start from the current working directory.
Having said all of that, and no matter what the cause of the problem is, you should use a fully-qualified path in order to make sure the file is written where you want it to be written. Whenever you want the file to go in a specific directory, it's always easiest to force that by using fully-qualified paths.
You can find the path to your executable using Application.ExecutablePath and then remove the file name part.
Another point to make is that the same directory as the executable may be a bad choice. If your program is installed under the Program Files directory then the directory which contains the executable will not be generally writeable. I think you should consider using a directory under in the user profile. Look for one of the Environment.SpecialFolder values.
Further to David Heffernan's answer - you can use
Path.GetDirectoryName(Application.ExecutablePath);
to safely get just the running application's folder part.
If you're in a dll rather than an executable, you can use
Path.GetDirectoryName(Assembly.GetAssembly(typeof(MyClass)).CodeBase);
Both require System.IO, and were originally posted here. Second example also requires System.Reflection).
Application data files are supposed to be written to the LocalApplicationData special folder.
string path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData);
You typically will not have permissions to write into the Program Files folder etc.
How Would I find another exe's path by knowing its name in .net?
Would I add name to the OS environment variable?
Would the other application have to 'register' itself somewhere else?
I need App A to start-up App B and call some WCF services on it.
Thanks!
To answer your question: you cannot know the path simply by knowing the name. An exe can reside anywhere on the file system. There can be multiple instances of it that don't know about each other. Multiple exe files that are completely different can have the same name.
You could take one of several approaches to get round this, depending on the exe you are targetting:
get the user to browse for the exe using a normal file browse dialog
search the file system
see what traces the target exe leaves on the system (filesystem, registry, environmental variables, etc) and use those traces to locate the exe
For either of these options you save the result so you don't have to execute it again when your app is run the next time.
Searching the filesystem could take some time, you are not guaranteed to find the exe (depending upon the user level your app is running as) and you may get false positives, especially if the app is called something dumb like setup.exe.
Getting the user to locate the exe the first time you run is possibly the most reliable way of locating it, but then you have to decide what to do if your app runs but the target exe is no longer at the specified location, or the user has chosen the wrong exe.
If you have some control over App B (i.e. it is your product), then you could consider adding some info to a known spot in the registry when App B gets installed, so that App A can locate it easily. You still need to have a plan B though in case the info is missing.
Reference a path to a shortcut of the exe in the config setting, that way if the exe ever moves around the shortcut will still be up-to-date. Try it, make a shortcut to a exe, then cut and paste the exe somewhere else, then double click the shortcut and you'll see it points to the exe's new location thus will not require changes to app A if the location app B changes.
Really, just make the App B a windows service and start it up when needed.
UPDATE:
Another suggestion would be to create a hard link to the AppB's EXE:
mklink /H AppB-link.exe path_to_actual_exe
Or a symbolic link to whole directory where App B resides:
mklink /D virtual_directory path_to_actual_directory
I have a problem calling a batch file from another batch file when trying to run everything by using Process.Start. Basically I call the execution of a batch file from my c# program that looks like this:
call include.bat
//execute the rest of the batch file here
The include.bat file sets up paths and can be used by a number of other batch files. When I run the Process.Start sometimes this works and sometimes I get ERROR: cannot find include.bat. First of all any idea why this happens? And ideas on how to fix this from the batch file?
To switch to the directory your batch file is located in, use this:
cd %~dp0
I do this in almost all of my batch scripts. That way relative paths should always work.
I know this is an old question but I thought it would be worth noting that the approach promoted by the accepted answer (i.e. changing the working directory) may not always be appropriate.
A better general approach is to refer to dependencies by full path:
call "%~dp0include.bat"
(Since %~dp0 already ends with a backslash, we don't need to add another one.)
Here are some benefits of not changing the working directory:
The rest of the batch file can still use the original working directory.
The original working directory in the command prompt is preserved, even without "SETLOCAL".
If the first batch file is run via a UNC path (such as "\\server\share\file.bat"), the full-path call will succeed while changing the directory (even with "cd /d") will fail. (Using pushd/popd would handle this point, but they have their own set of problems.)
These benefits are particularly important for alias-type batch files, even if they are not as important for the specific situation that motivated this question.
Before the script, try CD /D %~dp0
First thing I'd try is to use full path information in the call statement for include.bat. If that fixes it, you probably are just not running the batch file from the proper location. I'm sure there's a "working directory" capability in C#, I'm just not sure what it is.
Do you set ProcessStartInfo.WorkingDirectory ( http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.workingdirectory.aspx ) on the ProcessStartInfo that you pass to Process.Start?
Since include.bat sometimes cannot be found, working directory may be wrong (not the folder where include.bat is located).
I'm trying to run a command-line process (which is extraction of a .7z archive) on a file that lies in a temporary folder on the windows user temp directory
(C:\Documents and Settings\User\Local Settings\Temp), using Process in my c# app.
I think the process return error that happens because of "access denied" because I can see a win32Exception with error code 5 when I dig in the prcoess object of .NET.
doing the same on some other location worked fine before, so I guess maybe it's something I'm not supposed to do ? (running a process to use a file on the the %TEMP%)
perhaps I need to pass security somehow?
Assuming that you are using regular .NET (not CF/Silverlight, etc) Accessing files in the user's temp area is entirely expected. I wonder if the problem isn't more that you've accidentally left the file open after creating it, perhaps by not using a "using" or similar?
I probably wouldn't suggest using environment variables (%TEMP% etc) when shelling out to a separate process; ideally you'd pass the full path to the file (less things to get wrong...), making sure to quote any path arguments (in case of space) - i.e. so your args are #"... ""c:\some path\whatever\tmp""..." (if you see what I mean).
Finally, if you are extracting files, you need to think about the existing contents. Path.GetTempFileName() is fine for creating a single file place-holder, but for extracting an archive you probably want to create a directory - guids are handy for this purpoes (while avioding conflicts, and remember to remove it afterwards):
string dir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
running the same process using command-line (cmd) helped to figure out my problem was that I specified path arguments to the process using long-path-name.
Solution to this can be found here:
standard way to convert to short path in .net