I'd like to to associate a file extension to the current executable in C#.
This way when the user clicks on the file afterwards in explorer, it'll run my executable with the given file as the first argument.
Ideally it'd also set the icon for the given file extensions to the icon for my executable.
Thanks all.
There doesn't appear to be a .Net API for directly managing file associations but you can use the Registry classes for reading and writing the keys you need to.
You'll need to create a key under HKEY_CLASSES_ROOT with the name set to your file extension (eg: ".txt"). Set the default value of this key to a unique name for your file type, such as "Acme.TextFile". Then create another key under HKEY_CLASSES_ROOT with the name set to "Acme.TextFile". Add a subkey called "DefaultIcon" and set the default value of the key to the file containing the icon you wish to use for this file type. Add another sibling called "shell". Under the "shell" key, add a key for each action you wish to have available via the Explorer context menu, setting the default value for each key to the path to your executable followed by a space and "%1" to represent the path to the file selected.
For instance, here's a sample registry file to create an association between .txt files and EmEditor:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.txt]
#="emeditor.txt"
[HKEY_CLASSES_ROOT\emeditor.txt]
#="Text Document"
[HKEY_CLASSES_ROOT\emeditor.txt\DefaultIcon]
#="%SystemRoot%\\SysWow64\\imageres.dll,-102"
[HKEY_CLASSES_ROOT\emeditor.txt\shell]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\open\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" \"%1\""
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print]
[HKEY_CLASSES_ROOT\emeditor.txt\shell\print\command]
#="\"C:\\Program Files\\EmEditor\\EMEDITOR.EXE\" /p \"%1\""
Also, if you decide to go the registry way, keep in mind that current user associations are under HKEY_CURRENT_USER\Software\Classes. It might be better to add your application there instead of local machine classes.
If your program will be run by limited users, you won't be able to modify CLASSES_ROOT anyway.
If you use ClickOnce deployment, this is all handled for you (at least, in VS2008 SP1); simply:
Project Properties
Publish
Options
File Associatons
(add whatever you need)
(note that it must be full-trust, target .NET 3.5, and be set for offline usage)
See also MSDN: How to: Create File Associations For a ClickOnce Application
Here's a complete example:
public class FileAssociation
{
public string Extension { get; set; }
public string ProgId { get; set; }
public string FileTypeDescription { get; set; }
public string ExecutableFilePath { get; set; }
}
public class FileAssociations
{
// needed so that Explorer windows get refreshed after the registry is updated
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;
public static void EnsureAssociationsSet()
{
var filePath = Process.GetCurrentProcess().MainModule.FileName;
EnsureAssociationsSet(
new FileAssociation
{
Extension = ".binlog",
ProgId = "MSBuildBinaryLog",
FileTypeDescription = "MSBuild Binary Log",
ExecutableFilePath = filePath
},
new FileAssociation
{
Extension = ".buildlog",
ProgId = "MSBuildStructuredLog",
FileTypeDescription = "MSBuild Structured Log",
ExecutableFilePath = filePath
});
}
public static void EnsureAssociationsSet(params FileAssociation[] associations)
{
bool madeChanges = false;
foreach (var association in associations)
{
madeChanges |= SetAssociation(
association.Extension,
association.ProgId,
association.FileTypeDescription,
association.ExecutableFilePath);
}
if (madeChanges)
{
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
}
}
public static bool SetAssociation(string extension, string progId, string fileTypeDescription, string applicationFilePath)
{
bool madeChanges = false;
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + extension, progId);
madeChanges |= SetKeyDefaultValue(#"Software\Classes\" + progId, fileTypeDescription);
madeChanges |= SetKeyDefaultValue($#"Software\Classes\{progId}\shell\open\command", "\"" + applicationFilePath + "\" \"%1\"");
return madeChanges;
}
private static bool SetKeyDefaultValue(string keyPath, string value)
{
using (var key = Registry.CurrentUser.CreateSubKey(keyPath))
{
if (key.GetValue(null) as string != value)
{
key.SetValue(null, value);
return true;
}
}
return false;
}
There may be specific reasons why you choose not to use an install package for your project but an install package is a great place to easily perform application configuration tasks such registering file extensions, adding desktop shortcuts, etc.
Here's how to create file extension association using the built-in Visual Studio Install tools:
Within your existing C# solution, add a new project and select project type as Other Project Types -> Setup and Deployment -> Setup Project (or try the Setup Wizard)
Configure your installer (plenty of existing docs for this if you need help)
Right-click the setup project in the Solution explorer, select View -> File Types, and then add the extension that you want to register along with the program to run it.
This method has the added benefit of cleaning up after itself if a user runs the uninstall for your application.
To be specific about the "Windows Registry" way:
I create keys under HKEY_CURRENT_USER\Software\Classes (like Ishmaeel said)
and follow the instruction answered by X-Cubed.
The sample code looks like:
private void Create_abc_FileAssociation()
{
/***********************************/
/**** Key1: Create ".abc" entry ****/
/***********************************/
Microsoft.Win32.RegistryKey key1 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key1.CreateSubKey("Classes");
key1 = key1.OpenSubKey("Classes", true);
key1.CreateSubKey(".abc");
key1 = key1.OpenSubKey(".abc", true);
key1.SetValue("", "DemoKeyValue"); // Set default key value
key1.Close();
/*******************************************************/
/**** Key2: Create "DemoKeyValue\DefaultIcon" entry ****/
/*******************************************************/
Microsoft.Win32.RegistryKey key2 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key2.CreateSubKey("Classes");
key2 = key2.OpenSubKey("Classes", true);
key2.CreateSubKey("DemoKeyValue");
key2 = key2.OpenSubKey("DemoKeyValue", true);
key2.CreateSubKey("DefaultIcon");
key2 = key2.OpenSubKey("DefaultIcon", true);
key2.SetValue("", "\"" + "(The icon path you desire)" + "\""); // Set default key value
key2.Close();
/**************************************************************/
/**** Key3: Create "DemoKeyValue\shell\open\command" entry ****/
/**************************************************************/
Microsoft.Win32.RegistryKey key3 = Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software", true);
key3.CreateSubKey("Classes");
key3 = key3.OpenSubKey("Classes", true);
key3.CreateSubKey("DemoKeyValue");
key3 = key3.OpenSubKey("DemoKeyValue", true);
key3.CreateSubKey("shell");
key3 = key3.OpenSubKey("shell", true);
key3.CreateSubKey("open");
key3 = key3.OpenSubKey("open", true);
key3.CreateSubKey("command");
key3 = key3.OpenSubKey("command", true);
key3.SetValue("", "\"" + "(The application path you desire)" + "\"" + " \"%1\""); // Set default key value
key3.Close();
}
Just show you guys a quick demo, very easy to understand. You could modify those key values and everything is good to go.
The code below is a function the should work, it adds the required values in the windows registry. Usually i run SelfCreateAssociation(".abc") in my executable. (form constructor or onload or onshown) It will update the registy entry for the current user, everytime the executable is executed. (good for debugging, if you have some changes).
If you need detailed information about the registry keys involved check out this MSDN link.
https://msdn.microsoft.com/en-us/library/windows/desktop/dd758090(v=vs.85).aspx
To get more information about the general ClassesRoot registry key. See this MSDN article.
https://msdn.microsoft.com/en-us/library/windows/desktop/ms724475(v=vs.85).aspx
public enum KeyHiveSmall
{
ClassesRoot,
CurrentUser,
LocalMachine,
}
/// <summary>
/// Create an associaten for a file extension in the windows registry
/// CreateAssociation(#"vendor.application",".tmf","Tool file",#"C:\Windows\SYSWOW64\notepad.exe",#"%SystemRoot%\SYSWOW64\notepad.exe,0");
/// </summary>
/// <param name="ProgID">e.g. vendor.application</param>
/// <param name="extension">e.g. .tmf</param>
/// <param name="description">e.g. Tool file</param>
/// <param name="application">e.g. #"C:\Windows\SYSWOW64\notepad.exe"</param>
/// <param name="icon">#"%SystemRoot%\SYSWOW64\notepad.exe,0"</param>
/// <param name="hive">e.g. The user-specific settings have priority over the computer settings. KeyHive.LocalMachine need admin rights</param>
public static void CreateAssociation(string ProgID, string extension, string description, string application, string icon, KeyHiveSmall hive = KeyHiveSmall.CurrentUser)
{
RegistryKey selectedKey = null;
switch (hive)
{
case KeyHiveSmall.ClassesRoot:
Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.ClassesRoot.CreateSubKey(ProgID);
break;
case KeyHiveSmall.CurrentUser:
Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(#"Software\Classes\" + ProgID);
break;
case KeyHiveSmall.LocalMachine:
Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + extension).SetValue("", ProgID);
selectedKey = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(#"Software\Classes\" + ProgID);
break;
}
if (selectedKey != null)
{
if (description != null)
{
selectedKey.SetValue("", description);
}
if (icon != null)
{
selectedKey.CreateSubKey("DefaultIcon").SetValue("", icon, RegistryValueKind.ExpandString);
selectedKey.CreateSubKey(#"Shell\Open").SetValue("icon", icon, RegistryValueKind.ExpandString);
}
if (application != null)
{
selectedKey.CreateSubKey(#"Shell\Open\command").SetValue("", "\"" + application + "\"" + " \"%1\"", RegistryValueKind.ExpandString);
}
}
selectedKey.Flush();
selectedKey.Close();
}
/// <summary>
/// Creates a association for current running executable
/// </summary>
/// <param name="extension">e.g. .tmf</param>
/// <param name="hive">e.g. KeyHive.LocalMachine need admin rights</param>
/// <param name="description">e.g. Tool file. Displayed in explorer</param>
public static void SelfCreateAssociation(string extension, KeyHiveSmall hive = KeyHiveSmall.CurrentUser, string description = "")
{
string ProgID = System.Reflection.Assembly.GetExecutingAssembly().EntryPoint.DeclaringType.FullName;
string FileLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
CreateAssociation(ProgID, extension, description, FileLocation, FileLocation + ",0", hive);
}
The file associations are defined in the registry under HKEY_CLASSES_ROOT.
There's a VB.NET example here that I'm you can port easily to C#.
There are two cmd tools that have been around since Windows 7 which make it very easy to create simple file associations. They are assoc and ftype. Here's a basic explanation of each command.
Assoc - associates a file extension (like '.txt') with a "file type."
FType - defines an executable to run when the user opens a given "file type."
Note that these are cmd tools and not executable files (exe). This means that they can only be run in a cmd window, or by using ShellExecute with "cmd /c assoc." You can learn more about them at the links or by typing "assoc /?" and "ftype /?" at a cmd prompt.
So to associate an application with a .bob extension, you could open a cmd window (WindowKey+R, type cmd, press enter) and run the following:
assoc .bob=BobFile
ftype BobFile=c:\temp\BobView.exe "%1"
This is much simpler than messing with the registry and it is more likely to work in future windows version.
Wrapping it up, here is a C# function to create a file association:
public static int setFileAssociation(string[] extensions, string fileType, string openCommandString) {
int v = execute("cmd", "/c ftype " + fileType + "=" + openCommandString);
foreach (string ext in extensions) {
v = execute("cmd", "/c assoc " + ext + "=" + fileType);
if (v != 0) return v;
}
return v;
}
public static int execute(string exeFilename, string arguments) {
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.CreateNoWindow = false;
startInfo.UseShellExecute = true;
startInfo.FileName = exeFilename;
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.Arguments = arguments;
try {
using (Process exeProcess = Process.Start(startInfo)) {
exeProcess.WaitForExit();
return exeProcess.ExitCode;
}
} catch {
return 1;
}
}
I'm using the System.Net.FtpClient assembly to upload a file to a test FTP site. When I run the below code the file doesn't appear in the remote location unless I use a Thread.Sleep as per below (which I'd prefer not to use):
using System;
using System.IO;
using System.Net;
using System.Net.FtpClient;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace FtpsUploadTest
{
/// <summary>
/// The ftp publisher.
/// </summary>
public class FtpPublisher
{
private readonly FtpsSettings _settings;
private readonly IFtpClient _ftpClient;
/// <summary>
/// Initializes a new instance of the <see cref="FtpPublisher"/> class.
/// </summary>
public FtpPublisher()
{
_ftpClient = new FtpClient();
_settings = SettingsReader.GetMySettings();
Init();
}
/// <summary>
/// The publish.
/// </summary>
/// <param name="fileToUpload">
/// The input file path.
/// </param>
public void Publish(string fileToUpload)
{
var remoteFileName = Path.GetFileName(fileToUpload);
Console.WriteLine("FTPS host: {0} remote path: {1}", _settings.FtpsRemoteHost, _settings.FtpsRemotePath);
if (!_ftpClient.IsConnected)
{
_ftpClient.Connect();
}
var fullRemotePath = string.Format("{0}/{1}", _settings.FtpsRemotePath, remoteFileName);
using (var ftpStream = _ftpClient.OpenWrite(fullRemotePath))
using (var inputStream = new FileStream(fileToUpload, FileMode.Open))
{
inputStream.CopyTo(ftpStream);
Thread.Sleep(5000); // <------------------- DOESNT WORK IF REMOVE THIS SLEEP!!
}
Console.WriteLine("File '{0}' published successfully", fileToUpload);
}
private void Init()
{
_ftpClient.Host = _settings.FtpsRemoteHost;
_ftpClient.Port = _settings.FtpsRemotePort;
_ftpClient.DataConnectionConnectTimeout = 60000;
_ftpClient.ConnectTimeout = 60000;
_ftpClient.Credentials = new NetworkCredential(_settings.FtpsUserId, string.Empty);
_ftpClient.DataConnectionType = 0;
if (string.IsNullOrEmpty(_settings.CertFile) || string.IsNullOrEmpty(_settings.CertPassword))
{
return;
}
_ftpClient.ClientCertificates.Add(CreateCertificate(_settings.CertFile, _settings.CertPassword));
_ftpClient.EncryptionMode = (FtpEncryptionMode)2;
_ftpClient.DataConnectionEncryption = true;
}
private X509Certificate CreateCertificate(string certFile, string certPassword)
{
return new X509Certificate(certFile, certPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
}
}
}
Anyone know how I can get it to work without using a Thread.Sleep? I've tried flushing, closing streams but that doesn't help.
The remote FTP server is acting completely asynchronously from your code. Depending on the configuration of the server it might do things like scan for viruses or other bookkeeping before it makes the file available. There may be nothing you can do about this unless you have direct control over the FTP server. Even then it might require some pretty in depth configuration changes, or even a different software package.
One thing that might work for you is to "poll" for the file after completing the upload. Make a loop that checks for the file, waits 1 second, then repeats until it finds the file or gives up. The Async / Await pattern, or a call-back from a different thread, can help you get rid of any UI freezes from this if it's a problem.
Thanks to Bradley's answer I managed a workaround: Uploading the file with FtpClient.OpenWrite(...) and after that I constantly check the file size on the server, which solved the problem of code executing during the upload process that should be executed after it.
Then I ran into another problem which I managed a workaround for: when checking the file size of the uploading file on the remote FTP server, you can't always get the file size via FtpClient.GetFileSize(...) due to a FTP command (SIZE) that's not supported by all FTP servers. The solution was the following:
If FtpClient.GetFileSize(...) returns is greater than -1, the SIZE command works and can be used.
If the function returns -1, you can just traverse over a list of files in the specified folder on the server. There you get an array of FtpListItem which has a property called "Size". Size return the same value as FtpClient.GetFileSize(...) actually should.
Example Code:
const string folderPath = "/media/";
string fileName = "image.png";
string fullRemotePath = folderPath + fileName;
string fileToUpload = #"C:\image.png";
FtpClient ftpClient = new FtpClient();
// start FtpClient connection etc
FileInfo fileInfo = new FileInfo(fileToUpload);
long actualFileSize = fileInfo.Length;
using (var ftpStream = ftpClient.OpenWrite(fullRemotePath))
{
using (var inputStream = new FileStream(fileToUpload, FileMode.Open))
{
inputStream.CopyTo(ftpStream);
//Thread.Sleep(5000); <-- not necessary anymore
}
}
long fileSizeOnFtp = ftpClient.GetFileSize(fullRemotePath);
while (fileSizeOnFtp < actualFileSize)
{
if (fileSizeOnFtp > -1)
{
fileSizeOnFtp = ftpClient.GetFileSize(fullRemotePath);
}
else
{
var ftpListItem = ftpClient.GetListing(folderPath).Where(x => x.Name == fileName).FirstOrDefault();
if (ftpListItem != null)
{
fileSizeOnFtp = ftpListItem.Size;
}
else
{
// the program only could run into this issue if the folder couldn't be listed
throw new Exception();
}
}
}
// execute everything else after ftp upload has finished
Background:
Each year we run and then archive all the reports from a particular ASP.Net application. The archive provides a ‘snapshot’ of what the system’s data looked like at a particular time of year. We’ve built a GUI using .Net Forms that uses System.Net.WebClient to call the report server and download the reports to a particular directory. This has worked well in the past.
This year we are including the Excel files in our archive. The Excel files are created by an .aspx page (Windows Sever 2003, IIS6, .Net 4.0) that takes in a querystring, then returns an Excel file. This works great for 100 or so Excel files, but after that we start having problems. We archive approximately 300,000 files each year.
Mechanics:
We use WebClient.DownloadFileAsync to pull down our files, so as not to lock the UI thread. We rely on the WebClient.DownloadFileCompleted event to tell us when each file is done downloading. When DownloadFileCompleted is raised we then start downloading the next file.
Problem:
We’re locking up the webserver. Each file is only taking a fraction of a second to download and after approximately 167 files the webserver locks up (pages timeout) and the archive process pauses for several minutes. Then the archive process downloads another 100 files or so and stalls again for several minutes. This continues for several hours until the archive process starts to crawl with one file every minute or so.
It appears that IIS6 is running out of threads, but how can we stop this from happening?
The following is a slimmed down version of the code we’re running. I’ve stripped out logging and other items not related to our problem. Does anyone have any tips?
public class DownloadExample
{
private WebClient _WebClient = new WebClient();
public string DownloadDirectory { get; set; }
public List<Report> ReportList { get; set; }
/// <summary>
/// Constructor - sets all the attributes needed to access the report server, download, and archive the reports
/// </summary>
/// <param name="userName">Username</param>
/// <param name="userPassword">Password for the user's domain username</param>
/// <param name="userDomain">Domain of the username</param>
/// <param name="downloadDirectory">Network path where the files will be archived</param>
public DownloadExample(string userName, string userPassword, string userDomain, string downloadDirectory, List<Report> reportList)
{
DownloadDirectory = downloadDirectory;
_WebClient.Credentials = new NetworkCredential(userName, userPassword, userDomain);
_WebClient.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(WebClient_DownloadFileCompleted);
ReportList = reportList;
}
/// <summary>
/// Kicks off the archive process
/// </summary>
public void StartDownloading()
{
if (ReportList.Count > 0)
{
Report rpt = ReportList[0];
DoDownload(rpt.URL, CreateFileName(rpt), rpt.ReportTitle, rpt.ReportFormatType);
}
}
/// <summary>
/// Run the report and then download it to the archive directory
/// </summary>
/// <param name="url">URL of the Report</param>
/// <param name="fileName">File name used to name the report file once it is downloaded</param>
/// <param name="folderName">Name of the folder where the report will be downloaded to</param>
/// <param name="reportFormatType">Type of report being run, PDF or Excel</param>
private bool DoDownload(string url, string fileName, string folderName, ReportFormatTypes reportFormatType)
{
bool isSuccess = false;
string folderPath = DownloadDirectory + "\\" + folderName;
DirectoryInfo dir = new DirectoryInfo(folderPath);
if (!dir.Exists)
{
dir.Create();
dir = null;
dir = new DirectoryInfo(folderPath);
}
if (dir.Exists)
{
string path = folderPath + "\\" + fileName + ".xls";
System.Uri uri = new Uri(url);
try
{
_WebClient.DownloadFileAsync(uri, path);
}
catch (Exception exp)
{
//log error
}
FileInfo file = new FileInfo(path);
isSuccess = file.Exists;
}
return isSuccess;
}
/// <summary>
/// This event is fired after a file is downloaded
/// After each file is downloaded, we remove the downloaded file from the list,
/// then download the next file.
/// </summary>
void WebClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
//Remove the report that was just run
ReportList.RemoveAt(0);
if (ReportList.Count > 0)
{
//Download the next report
Report rpt = ReportList[0];
DoDownload(rpt.URL, CreateFileName(rpt), rpt.ReportTitle, rpt.ReportFormatType);
}
}
/// <summary>
/// Does a bunch of stuff to create the file name...
/// </summary>
string CreateFileName(Report rpt)
{
return rpt.FileName;
}
}
I'm using the following code with System.Deployment to return the ClickOnce version of my .NET 3.5 C# application:
public string version
{
get
{
System.Reflection.Assembly _assemblyInfo = System.Reflection.Assembly.GetExecutingAssembly();
string ourVersion = string.Empty;
if (System.Deployment.Application.ApplicationDeployment.IsNetworkDeployed)
{
ourVersion = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.CurrentVersion.ToString();
}
else
{
if (_assemblyInfo != null)
{
ourVersion = _assemblyInfo.GetName().Version.ToString();
}
}
return ourVersion;
}
}
If I launch the application normally (from the Start menu, for example), this value is always returned correctly. However, if I start the application automatically with Windows using a registry key, the application returns the default hardcoded value of 1.0.0.0.
If I close the automatically-started application and re-open it manually, it then returns the correct ClickOnce version number again.
Any ideas on why this might be? Here is the code I'm using to set the registry key:
string keyName = "MyApp";
string assemblyLocation = Assembly.GetExecutingAssembly().Location;
Util.SetAutoStart(keyName, assemblyLocation);
And
public class Util
{
private const string RUN_LOCATION = #"Software\Microsoft\Windows\CurrentVersion\Run";
/// <summary>
/// Sets the autostart value for the assembly.
/// </summary>
/// <param name="keyName">Registry Key Name</param>
/// <param name="assemblyLocation">Assembly location (e.g. Assembly.GetExecutingAssembly().Location)</param>
public static void SetAutoStart(string keyName, string assemblyLocation)
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(RUN_LOCATION);
key.SetValue(keyName, assemblyLocation);
}
/// <summary>
/// Returns whether auto start is enabled.
/// </summary>
/// <param name="keyName">Registry Key Name</param>
/// <param name="assemblyLocation">Assembly location (e.g. Assembly.GetExecutingAssembly().Location)</param>
public static bool IsAutoStartEnabled(string keyName, string assemblyLocation)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(RUN_LOCATION);
if (key == null)
return false;
string value = (string)key.GetValue(keyName);
if (value == null)
return false;
return (value == assemblyLocation);
}
/// <summary>
/// Unsets the autostart value for the assembly.
/// </summary>
/// <param name="keyName">Registry Key Name</param>
public static void UnSetAutoStart(string keyName)
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(RUN_LOCATION);
key.DeleteValue(keyName);
}
}
I've isolated the problem. I won't pretend to understand exactly how ClickOnce works, but basically, if you launch the executable file directly it won't run in "ClickOnce mode". This means it won't check for updates and won't get the correct version number (since it isn't actually network deployed).
The best solution I've found so far is to point to the ClickOnce .appref-ms file rather than the .exe file. This file is like a shortcut of sorts, and is in the start menu.
Here's the code I'm using to get the location of my app's .appref-ms file:
string allProgramsPath = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
string shortcutPath = Path.Combine(allProgramsPath, keyName);
shortcutPath = Path.Combine(shortcutPath, keyName) + ".appref-ms";
And then I combine that with my previous code to set that location in the registry.
I need to test if a file is a shortcut. I'm still trying to figure out how stuff will be set up, but I might only have it's path, I might only have the actual contents of the file (as a byte[]) or I might have both.
A few complications include that I it could be in a zip file (in this cases the path will be an internal path)
Shortcuts can be manipulated using the COM objects in SHELL32.DLL.
In your Visual Studio project, add a reference to the COM library "Microsoft Shell Controls And Automation" and then use the following:
/// <summary>
/// Returns whether the given path/file is a link
/// </summary>
/// <param name="shortcutFilename"></param>
/// <returns></returns>
public static bool IsLink(string shortcutFilename)
{
string pathOnly = System.IO.Path.GetDirectoryName(shortcutFilename);
string filenameOnly = System.IO.Path.GetFileName(shortcutFilename);
Shell32.Shell shell = new Shell32.ShellClass();
Shell32.Folder folder = shell.NameSpace(pathOnly);
Shell32.FolderItem folderItem = folder.ParseName(filenameOnly);
if (folderItem != null)
{
return folderItem.IsLink;
}
return false; // not found
}
You can get the actual target of the link as follows:
/// <summary>
/// If path/file is a link returns the full pathname of the target,
/// Else return the original pathnameo "" if the file/path can't be found
/// </summary>
/// <param name="shortcutFilename"></param>
/// <returns></returns>
public static string GetShortcutTarget(string shortcutFilename)
{
string pathOnly = System.IO.Path.GetDirectoryName(shortcutFilename);
string filenameOnly = System.IO.Path.GetFileName(shortcutFilename);
Shell32.Shell shell = new Shell32.ShellClass();
Shell32.Folder folder = shell.NameSpace(pathOnly);
Shell32.FolderItem folderItem = folder.ParseName(filenameOnly);
if (folderItem != null)
{
if (folderItem.IsLink)
{
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject)folderItem.GetLink;
return link.Path;
}
return shortcutFilename;
}
return ""; // not found
}
You can simply check the extension and/or contents of this file. It contains a special GUID in the header.
Read [this document][1].
Link deleted, for me it goes to a porn site
If you are using .NET 6.0, then you can use FileInfo or DirectoryInfo. Both have a property named LinkTarget. If it is a shortcut, then LinkTarget will be a string targeting the original file/folder. Otherwise, it will be null.
Suppose there is a folder shortcut named "folder", then:
var info = new DirectoryInfo("path/to/the/folder/shortcut");
bool isShortcut = info.LinkTarget != null;
Check the extension? (.lnk)