Pass file name to .exe in registry key - c#

I have a small installer that installs some .exe and DLLs to the target machine, and sets a key in registry so that on right click on Excel files the user sees a new context menu item. This command key has for value something like :
[TARGETDIR]myexecutable.exe %1
The %1 has for effect to pass the file name to my executable in args[0]. I expect this to give to my .exe the full path. But it gives me some sort of compressed path. For example for :
C:\Documents and Settings\user\Desktop\teestqqqq.xls
it will give me :
C:\DOCUME~1\user\Desktop\TEEST~1.XLS
That's a problem for me. How can i solve this ? I need a proper full path.
UPDATE : As requested in comments and in answers, this is an issue to me because from the path I receive I derive some new folder names. These folder names are to be committed so SVN repositories later on by some users. I have noticed that on some machines of my users, the .exe receive the sort path, and on other, the long path. My users consider the folder names to be "unique" for DIFF purposes on SVN. I just need a way to ensure uniqueness of the folder names (I think there is already what I need in answers below at the time of writing)

Replace %1 with "%1", i.e. put quotes around, you'll get the long file name. The thing is 'Documents and Settings' has spaces in its name which can't be passed to your program correctly without quotes. That's why the shell converts the path to the short name, without spaces, and then passes it to your program.

Try
Path.GetFullPath(path)
That should do it. But you should just be able to work with the short path.

You could try:
string shortName = #"C:\DOCUME~1\user\Desktop\TEEST~1.XLS";
string longName = System.IO.Path.GetFullPath(shortName);

Long before "long file names". File names in MS DOS were limited to 8 characters for the name, followed by a dot and maximum of 3 characters for the extension. What you are seeing is the short version of the path with long file names. Here is a longer explanation: http://www.computerhope.com/issues/ch000209.htm
Now explain how the given path is a problem for you? Because windows APIs should be able to work with the short file name versions as well.
As ken2k mentioned, System.IO.Path.GetFullPath(..) will return the expanded path given a short file name path.
Quoting the documentation:
If you pass in a short file name, it is expanded to a long file name.

Related

SSH.NET: How to check if file or folder already exists in other upper-lower-case spelling

I am currently using C# and SSH.NET to upload files or folders to a Unix server. My problem is, that Unix is case-sensitive, whereas Windows is not, or at least not really. So I am having a problem if I copy a folder called "test", and another folder called "Test". The different case is no problem for Unix of course, but under Windows, when accessing the folder via Samba, I can see only the contents of one of them.
What I want to do is the following: If I upload a file or folder over sftp, but that file or folder already exists with another combination of upper- or lowercase, I want to rename the file to be copied to match the name already present.
Is there an easy and convenient way to do that? What I basically need is a case-insensitive check if a folder or parts of a folder already exist.
You can use the Directory.GetFiles and Directory.GetDirectories methods to list all files/directories in a directory and and then do a case insensitive match in your code.
https://msdn.microsoft.com/en-us/library/wz42302f(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/c1sez4sc(v=vs.110).aspx

How to determine whether an relative path points outside an given path

I have the following scenario (C#, WinForms). I have some kind of project file which is saved in some directory. The project file contains a reference to another file. This reference is relative from the place where the project file is saved.
Sample: The project file is saved under c:\projects\project.xyz. The other file is referenced as "\someotherdir\file.abc".
This works fine, but there may be the case someone tried to manipulate that relative path to something like "..\Windows\System32\file.abc". So there's a need to check whether the relative path points outside the path where the project is saved (it's a defined requirement, that all referenced files are inside the project path).
How to detect this scenario?
You could try using the following extension method:
public static bool IsChildOf(this string path, string parentPath)
{
return Path.GetFullPath(path).StartsWith(Path.GetFullPath(parentPath),
StringComparison.InvariantCultureIgnoreCase);
}
Not very pretty but I think it should work.
if (System.IO.Path.GetFullPath(path).IndexOf(projectPath, StringComparison.CurrentCultureIgnoreCase) == -1)
{
// naughty
}
Edited to be a good global citizen.
Windows has posix symlinks: ln -s c:\windows\system32\mshtml.dll c:\projects\project.xyz\innocent.txt. When your program opens c:\projects\project.xyz\innocent.txt you get c:\windows\system32\mshtml.dll. Does System.IO.Path.GetFullPath() work here?
POSIX also supports hardlinks. A file can have zero (when deleted), one, two, ten, one hundred filenames. And all are "The Filename", none more correct or less correct than any other.
Windows supports mounting folders into folders. Again, all names are correct.
You can solve this with filesystem permissions: Create a new user for your application. Give that user permissions to your project path. Do not give that user (or Everyone, or any groups the user is a member of) privileges to anything else in any filesystem. Let Microsoft's kernel team solve your problem for you.

Blank space after file extension -> weird FileInfo behaviour

Somehow a file has appeared in one of my directories, and it has space at the end of its extension -
its name is "test.txt ". The weird thing is that Directory.GetFiles() returns me the path of this
file, but I'm unable to retrieve file information with FileInfo class.
The error manifests here:
DirectoryInfo di = new DirectoryInfo("c:\\somedir");
FileInfo fi = di.GetFileSystemInfos("test*")[0] as FileInfo;
//correctly fi.FullName is "c:\somedir\test.txt "
//but fi.Exists==false (!)
Is FileInfo class broken? Can I somehow retrieve information about this file? I really don't know how did that file appear on my file system, and I am unable to recreate some more of them.
All of my attempts to create a new file with this type of extension have failed, but now my program is
crashing when encoutering it. I can easily handle the exception when finding the file, but boy am I
curious about this!
Ending file names with a space is documented as a Bad Idea.
From MSDN "Naming Files, Paths, and Namespaces (Windows)":
Do not end a file or directory name with a space or a period. Although the underlying file system may support such names, the Windows shell and user interface does not.
Also, the KB article "INFO: Filenames Ending with Space or Period Not Supported":
Problems can arise when a Macintosh client creates a file on a Windows NT server. The code to remove trailing spaces and periods is not carried out and the Macintosh user gets the correctly punctuated filename. The Win32 APIs FindFirstFile() and FindNextFile() return a filename that ends in a space or in a period; however, there is no way to create or open the file using the Win32 API.
DirectoryInfo probably uses FindFirstFile() and friends to produce directory listings. File.Exists is most likely implemented through GetFileAttributes() which probably suffers from the same problem as CreateFile() and will report a nonexistent file.
Hence, not a problem in .NET specifically, but in Windows itself.
Yes i know of these files. I also got once such a beast thing. To get rid of it i don't know about a programming way in C#, but good old command line is your friend:
Open a console window in the given folder (or execute cmd and navigate to the folder with cd command). Now enter dir /x to retrieve the shortname of the files in this directory. Use this name to delete or rename the file by using the del or ren command.
You can manipulate files with trailing spaces (among other edge cases) if you use the \\?\ path syntax: \\?\c:\somedir\test.txt .

Associate File Type and Icon

Ok, I have looked all over and I am having a difficult time trying to figure out how to associate a custom file type with my C# application. I am using Visual Studio 2008 and am limited to .NET 2.0 due to company deployment.
Basically all I really want to do is be able to allow the user to double click my custom file type or drag and drop it on the app icon and it load the data, as well as display a custom icon on the file itself. I can open the file via a dialog box and everything works great.
Trying to read Micosoft's online documents is hard to understand and does not really give me all the details that I need. If anyone out there know's where I can find a good tutorial or can explain it I would appreciate it.
Thank you
Patrick
I wrote this up in the newsgroups a few years ago. Skimming through it, I find that it's not the clearest I've ever written, but it's fairly complete. For what it's worth, I've reprinted it below:
First you need to create a subkey under HKEY_CLASSES_ROOT that will hold the commands that start your application. You should name this key something semi-descriptive. Your file extension[s] will be mapped to this key. For example, TXT files are mapped to a key named txtfile, by default. The benefit of using this setup is that if you have multiple extensions that your app can handle, you can map them all to this key. For example, many
image editting apps create a subkey called something like "imagefile", and map .bmp, .jpg, .gif, etc. to that key. We'll call your key "JoeBlowFile". Next, you need to set the "default" value for your new JoeBlowFile key to a text string describing to the user just what type of file they have. This is what shows up in Windows Explorer under "Type". Again, to use the TXT file example, a good string here would be "Text File" or "Text Document". (It is the latter by default.) Your string might be "Joe Blow Data".
Now, under your new key, you can create another subkey, called "DefaultIcon". This, as its name suggests, sets the icon that is used with files of this type. You should create a custom icon that pictorially represents documents handled by your program. You can save this icon as an ICO file in your app's directory, but even better, you can include it as a resource in either your EXE or DLL. Either way, you'll then need to set the subkey's default value to a string representing the full path and filename of the ICO, EXE, or DLL. If there is more than one icon in the file (particularly likely if you include it as a resource in your EXE or DLL), you'll also need to add a comma, and either a zero-based positive index number representing which icon you'd like to use, or a negative resource ID, using the negative of whatever ID you've assigned your icon in your resource script. So yours could be, for example "C:\Program Files\JoeBlow\JoeBlow.exe, 2".
A note for C# developers on the above paragraph. Unfortunately, C# projects can't have resource scripts. Resources added to .NET applications by adding them to the project and designating them an "Embedded Resource" are included in a new .NET-specific format that's not compatible with previous methods. The only icon you can correctly embed in your application using C# on VS.NET is the application icon, accessible from the project properties. If you need additional icons—e.g. an
icon to represent a document file handled by your app rather than to represent the app itself—you'll need to either include the ICO files themselves, compile a DLL with C++ with your icons embedded, or create and compile a resource script and include it in your project from the project properties.
Whether or not you choose to use the DefaultIcon key, you now need to create a subkey named "shell" under your JoeBlowFile key. Under the shell key, you'll create individual keys for each of the commands you'd like the user to be able to perform on your file type from the context menu (right-click menu). These items are called "verbs". For consistency, one of them should be "open"—this key, if it exists, will be the default (i.e. when a user double clicks on a file of your type, the open command will be performed). Instead, you can set the default value for the "shell" key equal to the verb you'd like to perform by default. You can optionally set the default value for each verb key to the text that you would like to appear in the context menu when a user right-clicks on a file of your type. An ampersand (&) can be used within this text to designate that the following character will be underlined, which means that the user can press the key corresponding to that character to select that command from the context menu. For instance, for the "open" key, you could put "Open with &Joe Blow's app" as the default value. That text then, with an underlined J, will appear in the context menu for files of that type, and a user can press the letter J to start Joe Blow's app.
The only thing you have to do, though, with the "open" (and subsequent) keys, is create
another key as a subkey of that one called "command". The default value for the command key must be set to a string representing just that—the command required to perform that action. For instance, the default string in the command key under the "open" key might be ""C:\Program Files\JoeBlow\JoeBlow.exe" "%1"". Note the quotation marks around the path\filename for your app and around the %1. They are only neccessary around the path\filename of your app if there are any spaces in the path or filename, but they are absolutely a requirement around the %1 for 32-bit apps. The %1 is just like %1 in old MS-DOS batch (.bat) files. %1 is replaced with the first parameter on the command line, which in this case becomes the file name of the file your app is supposed to open. Because you do not know in advance if the path or filename containing the file you're supposed to
open will contain spaces, you must put the quotes around %1.
Other required parameters for your app should also be included. For instance, the default value in the "command" key, under the "print" key might be ""C:\Program Files\JoeBlow\JoeBlow.exe" "%1" /print", or ""C:\Program Files\JoeBlow\JoeBlow.exe" /print "%1"". It's up to you how you want to process command line parameters in your app.
A note on replaceable parameters like "%1", mentioned above. Apparently, the "%1" parameter /may/ be replaced with the short filename to be opened. This isn't always the case, and I haven't figured out what criteria Windows uses to determine which it will pass—short or long. It may be that if the executable path listed in the registry is a long filename, Windows will replace %1 with the long filename to start, but if the executable path is a short filename, or can be interpreted as one, Windows will replace %1 with the short filename. If you want to be sure that you always get the long filename, use "%L" instead. You can use an uppercase L (as I've done) or a lowercase one, but I prefer to use uppercase because lowercase "l" looks way too much like the number "1".
What's more, if your program knows how to deal with Shell Item IDs, you can get that instead of the long filename with the "%i" parameter. Again, upper- or lowercase "i" are equally suitable, but I find uppercase "I" harder to distiguish from lowercase "l" and the number "1". If you don't know what a Shell Item ID is, it's okay. You'll probably never need to use one.
You're finally done with the JoeBlowFile key. The rest is relatively simple. You simply need to create (if it doesn't already exist) another subkey under HKEY_CLASSES_ROOT, and name it the same as the extension of your document type. To use the txtfile example, the name would be ".txt" (with the dot, but without the quotes). Yours (Joe Blow's) might be ".jbf" (for Joe Blow File). The default value for this key must now be set to the name of the first key your created, which in the example we've using is "JoeBlowFile".
That's it. You're done in the registry. Do remember that you'll have to make sure your app
proccesses the command line in a manner consistent with the commands you set under the "shell" key. Window's won't open that file for you automatically when your app starts... you have to do it.
Graphically, it looks like this:
HKEY_CLASSES_ROOT
| +--.jbf = JoeBlowFile
|
+--JoeBlowFile = Joe Blow Data
|
+--DefaultIcon = C:\Program Files\JoeBlow\JoeBlow.exe, 2
|
+--Shell
|
+--open = Open with &Joe Blow's App
| |
| +--command = "C:\Program Files\JoeBlow\JoeBlow.exe" "%1"
|
+--print
|
+--command = "C:\Program Files\JoeBlow\JoeBlow.exe" "%1" /print
If you don't already know how to modify the registry, look in MSDN for all the functions
beginning with "Reg", including RegOpenKeyEx, RegCreateKeyEx, and RegSetValueEx. You can also do it the wimpy way by creating a ".reg" file and simply use ShellExecuteEx() to call "regedit.exe /s" on it. (The /s keeps Regedit from popping up a message box asking "Are you sure you want to add the information in [name of file.reg] to the registry?") The format of the a REG file is simple and straight forward. Here is an example REG file to add the "JoeBlow" example from above:
REGEDIT4
[HKEY_CLASSES_ROOT\.jbf]
#="JoeBlowFile"
[HKEY_CLASSES_ROOT\JoeBlowFile]
#="Joe Blow Data"
[HKEY_CLASSES_ROOT\JoeBlowFile\DefaultIcon]
#="C:\\Program Files\\JoeBlow\\JoeBlow.exe, 2"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell]
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\open]
#="Open with &Joe Blow's app"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\open\command]
#="\"C:\\Program Files\\JoeBlow\\JoeBlow.exe\" \"%1\""
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\print]
#="&Print"
[HKEY_CLASSES_ROOT\JoeBlowFile\Shell\print\command]
#="\"C:\\Program Files\\JoeBlow\\JoeBlow.exe \"%1\" /print"
Make sure that you include "REGEDIT4" as the first line of the file, or it won't work. Also
make sure to press enter on the last line, or that line won't be read in. Altogether, adding your program to the registry this way is not as convenient as it sounds, because you'll have to modify your REG file if your app is installed anywhere except to C:\Program Files\JoeBlow.
The above instructions were aimed at a user programming directly to the Win32 API using C or
C++. For C# on .NET, it's rather a bit easier. See the Registry class, or you can even do much of it graphically using a deployment project in VS.NET.
To add native-accessible resources to a .NET assembly, you will need a resource script. A resource script is a plain text file, like a code file. In fact, it is code; declarative code that is compiled by the resource compiler, rc.exe. A sample resource script follows:
#include <windows.h>
#define IDI_APP 100
#define IDI_FILE 200
#define ID_VERSION 1
IDI_APP ICON "Resources\\Application.ico"
IDI_FILE ICON "Resources\\JowBlowFile.ico"
ID_VERSION VERSIONINFO
FILEVERSION 1, 0, 19, 186 // change this to your version
PRODUCTVERSION 1, 0, 19, 186 // change this to your version
FILEOS VOS__WINDOWS32
FILETYPE VFT_APP {
BLOCK "StringFileInfo" {
BLOCK "040904B0" { // 0x409 = U.S. English, 0x04B0 = dec 1200 = Unicode
VALUE "CompanyName", "Joe Blow, Inc.\0"
VALUE "FileDescription", "Joe Blow's App\0"
VALUE "FileVersion", "1.0.19.186\0" // change this to your version
VALUE "InternalName", "JoeBlow\0"
VALUE "LegalCopyright", "Copyright © 2008-2009 Joe Blow Incorporated\0"
VALUE "OriginalFilename", "JoeBlow.exe\0"
VALUE "ProductName", "Joe Blow\0"
VALUE "ProductVersion", "1.0.19.189\0" // change this to your version
}
}
BLOCK "VarFileInfo" {
VALUE "Translation", 0x409 /*U.S. English*/, 1200 /*Unicode*/
}
}
The biggest drawback to doing this is that you have to add the version information manually to your resource script (unless you want to just forgo the version information altogether). In my applications, I add a custom build step that runs a program I wrote that updates the version information directly in the executable so that I don't have to manually update the version number in the resource script, but that program is not suitable for public release and is otherwise beyond the scope of this post.
Now you need to invoke the resource compiler to build this resource script into a binary resource file. Save this script as JoeBlow.rc, then start a Visual Studio Command Prompt. It's under Tools in the Visual Studio start menu folder. If you don't have Visual Studio installed, I believe you get rc.exe as part of the SDK. Microsoft also seems to be offering the latest version here.
Once at a VS cmd prompt (or otherwise have rc.exe in your path), just type:
rc JoeBlow.rc
Simple as that. The above resource script should compile without errors, given that the icons I've include exist. This will create a new file in the same directory called JoeBlow.res. Now, assuming you are using Visual Studio, all you have to do is edit the project properties to include this resource file.
These directions are for Visual Studio 2005 or 2008. I don't remember how to do this, or even if you can, in older versions, and I haven't tried out 2010, yet, but it's probably similar. Right-click on the project in Solution Explorer and select Properties (or select Properties from the Project menu on the main menu bar). On the Application tab, which is the first tab you should see, at the bottom is a Resources group box. Here, you have two options: "Icon and manifest", or "Resource File". Select the latter option. This will enable the text box where you can type (or browse to) your new resource file, JoeBlow.res.
Lastly, just build your project, and presto, you have embedded icons in native PE format accessible to the shell when browsing files associated to your app. Now if you set the value of HKEY_CLASSES_ROOT\JoeBlowFile\DefaultIcon to either C:\Program Files\JoeBlow\JoeBlow.exe,1 (using the zero-based index number), or C:\Program Files\JoeBlow\JoeBlow.exe,-200 (using the negative of the resource identifier, #defined above as IDI_FILE), your icon will show up in Explorer for .jbf files.
To get your new icons to show up immediately upon installation, you may need to refresh the shell's icon cache. I've found instructions on how to do that here. The basic gist is to change the shell icon size (at HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics) from its current value to a different one and back again, broadcasting a WM_SETTINGCHANGE message after each change.
Good luck. Let me know if you need anything else.
This CodeProject article has some source code demonstrating file association by code.
Here is a discussion about how to use command-line utilities assoc and ftype to do this. You could invoke the command shell during a program deployment (or when the application is run) to do this.

Is it worth it to lookup the default application in the registry when opening a file from a C# application?

I'm building an application (a side project which is likely to enlist the help of the stackoverflow community on more than one occasion) which will need to open a variety of file types (i.e. open Word documents in Word, not natively in my application).
I've been playing with some code for looking up the default application for the file type in the registry and passing this to Process.Start(). There seem to be two issues with this approach:
1) The application name is quoted in some instances, and not in others.
2) Process.Start() requires that the application path and it's arguments are passed separately (i.e. Process.Start("notepad.exe", #"C:\myfile.txt"); rather than Process.Start(#"notepad.exe C:\myfile.txt");).
This means when I retrieve the path from the registry, I have to split it (after determining if I need to split on quotes or spaces) to determine what part is the application path and what parts are arguments, then pass those separately to Process.Start().
The alternative seems to be to just pass the filename, as in Process.Start(#"C:\myfile.txt"), but I think this only works if the application is in the Path environment variable.
Which way is better? In the case of the registry, is there a common solution for how to do the argument parsing?
Thanks for any and all help!
Update:
I guess the short answer is 'No.'
It seems like I was really going the overkill route, and that passing just the filename will work whenever there's an associated value in the registry. I.e. anything I find in the registry myself, Process.Start() already knows how to do.
I did discover that when I try this with a "new" filetype, I get a Win32Exception stating "No application is associated with the specified file for this operation." Fredrik Mörk mentions in a comment that this doesn't occur for him in Vista. What's the proper way to handle this?
If the extension is registered to be opened with a certain application, it doesn't need to be in the PATH in order to run.
The application does not need to be in the PATH if you only specify the filename. The following code worked fine for me:
System.Diagnostics.Process.Start(#"C:\Users\Dan\Desktop\minors.pdf");
You typically do not need to lookup the program for registered types, and the program does not typically need to be in the PATH environment variable. Usually the command in the registry contains the full path. This is how the command for .kml files (Google Earth) looks (in my computer):
C:\Program Files\Google\Google Earth\googleearth.exe "%1"
Given that, you can safely just use Process.Start together with the document file names. Should it be that the file type is not registered you will invoke the default Windows behaviour for this (asking you which program to use, and so on).

Categories

Resources