WPF application file association: DefaultIcon is not working - c#

I want to association ".abc" file to my WPF application.
I add the association using this code:
public class FileAssociation
{
static RegistryKey Root
{
get
{
return Registry.CurrentUser;
}
}
// Associate file extension with progID, description, icon and application
public static void Associate(string extension,
string progID, string description, string application)
{
Require.NotNullOrEmpty(extension, "extension");
Require.NotNullOrEmpty(progID, "progID");
Require.NotNullOrEmpty(application, "application");
Require.NotNullOrEmpty(description, "description");
Root.CreateSubKey(extension).SetValue("", progID);
using (var key = Root.CreateSubKey(progID))
{
key.SetValue("", description);
key.CreateSubKey("DefaultIcon").SetValue("", ToShortPathName(application).Quote() + ",0");
key.CreateSubKey(#"Shell\Open\Command").SetValue("", ToShortPathName(application).Quote() + " \"%1\"");
// Tell explorer the file association has been changed
SHChangeNotify(0x08000000, 0x0000, IntPtr.Zero, IntPtr.Zero);
}
}
// Return true if extension already associated in registry
public static bool IsAssociated(string extension)
{
return (Root.OpenSubKey(extension, false) != null);
}
[DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
[DllImport("Kernel32.dll")]
private static extern uint GetShortPathName(string lpszLongPath,
[Out] StringBuilder lpszShortPath, uint cchBuffer);
// Return short path format of a file name
private static string ToShortPathName(string longName)
{
StringBuilder s = new StringBuilder(1000);
uint iSize = (uint)s.Capacity;
uint iRet = GetShortPathName(longName, s, iSize);
return s.ToString();
}
}
Note: The Quote() extension method is used just to make string abc to "abc".
Now the file association works fine! I can double click the ".abc" files to open my WPF app.
But the DefaultIcon is not working. The DefaultIcon Registery key is set to "D:\path\to\MyWPFApp.exe",0. The application icon of my WPF app is set to an icon in the properties page (I can see that the icon of MyWPFApp.exe is already changed). What's wrong? Thanks!
BTW: I'm using .NET 4 in Windows 8

You don't need the DefaultIcon entry. The first icon is used by default.
remove it and it should work ^^
If I remove the ToShortPathName (long name is ok with quotes) and
change the Root property returns Registry.ClassesRoot the code works here.

Related

Open embedded Textfile using Notepad

I have the following problem. I want to open an embedded textfile (agb.txt) using "notepad.exe". I've got the following code:
private void linkLabel4_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
string editorPath = Environment.SystemDirectory + "\\notepad.exe";
var startInfo = new ProcessStartInfo(editorPath)
{
//Start Maximized
WindowStyle = ProcessWindowStyle.Maximized,
Arguments = "agb.txt"
};
//Start notepad.exe (agb.txt)
Process.Start(startInfo);
}
When I start the program and click the Linklabel, Notpad.exe open up but can't find the embedded file (obviously). So is there a kinda 'Workaround'?
What about saving the file to %TEMP% and then simply calling
Process.Start(#"c:\temp\agb.txt");
(this will actually open the file in whatever application is registered to load it)
You can start Nodepad.exe and then "send" the text from the embedded file to it.
Instead of my hardcoded string, you would read the content of the file into a string and then call DoSendMessage(stringWithYourFileContent);
This question and answer is a good resource for reading the content of the embedded file: How to read embedded resource text file
class Program
{
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll")]
public static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
static void Main(string[] args)
{
DoSendMessage("Sending a message, a message from me to you");
}
private static void DoSendMessage(string message)
{
Process notepad = Process.Start(new ProcessStartInfo("notepad.exe"));
notepad.WaitForInputIdle();
if (notepad != null)
{
IntPtr child = FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessage(child, 0x000C, 0, message);
}
}
}
The constant 0x000c of SendMessage is documented here
http://msdn.microsoft.com/en-us/library/windows/desktop/ms632644(v=vs.85).aspx
. The constant says SETTEXT which really means that the text in
notepad will be replaced if you send more than one message using this
constant.

How to add an icon to an existing exe file (without an icon by default)?

When I was searching on Google I found a useful class which let us change the icon of any .exe file using the following line of code :
WindowsFormsApplication1.IconInjector.InjectIcon("myfile.exe", "myicon.ico", 200, 1);
Where 200 and 1 are respectively icon GroupID and icon BaseID which I can determine using Resource Hacker. In this case the file's icon changes successfully without corrupting the file.
So i planned to use this class on my program which is a SFX / Software protector, the output file always hasn't an icon, all what I can see on Resource hacker is the below :
i can't see icon group id nor the base id, anyway, (I don't know what to put instead of 200 and 1 in this case) So I tried to change the icon using the same line of code mentioned above, I used the following line of code (same as above):
WindowsFormsApplication1.IconInjector.InjectIcon("myfile.exe", "myicon.ico", 200, 1);
The file icon was successfully changed but the file doesn't work anymore!
When I tried to reopen the file using ResourceHacker, I found the below:
It seems that the icon resources were successfully added, but i can't figure out why the file doesn't work anymore, it seems that is corrupted.
Any help would be appreciated.
Note : I tried using this class with unprotected file and it works like a charm!
The class I am using is the below:
// IconInjector.cs
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace WindowsFormsApplication1
{
/// <summary>
/// IconInjectorクラスの定義
/// </summary>
public class IconInjector
{
[DllImport("kernel32.dll", SetLastError = true)]
//static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage, IntPtr lpData, uint cbData);
static extern int UpdateResource(IntPtr hUpdate, uint lpType, uint lpName, ushort wLanguage, byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
/// <summary>
/// アプリケーションのメイン エントリ ポイントです。
/// </summary>
[STAThread]
public static void InjectIcon(string execFileName, string iconFileName, uint iconGroupID, uint iconBaseID)
{
const uint RT_ICON = 3;
const uint RT_GROUP_ICON = 14;
// アイコンファイルの読み込み
IconFile iconFile = new IconFile();
iconFile.Load(iconFileName);
// リソースの更新開始
IntPtr hUpdate = BeginUpdateResource(execFileName, false);
Debug.Assert(hUpdate != IntPtr.Zero);
// RT_GROUP_ICON 書き込み
byte[] data = iconFile.CreateIconGroupData(iconBaseID);
UpdateResource(hUpdate, RT_GROUP_ICON, iconGroupID, 0, data, (uint)data.Length);
// RT_ICON書き込み
for (int i = 0; i < iconFile.GetImageCount(); i++)
{
byte[] image = iconFile.GetImageData(i);
UpdateResource(hUpdate, RT_ICON, (uint)(iconBaseID + i), 0, image, (uint)image.Length);
}
// リソースの更新終了
EndUpdateResource(hUpdate, false);
}
}
}
Any help or suggestion on adding the icon to the protected file without corrupting it?
It sounds like the protection application is verifying that the contents of the file haven't been tampered with. Injecting an icon is definitely a form of tampering, and unless the protection software is updated to ignore it, it will always fail. Alternatively if you own the protection software you could update it to not strip the icons.
I just experienced the same issue with a 7zip Self-Extractor exe.
Updating the icon of the 7zS.sfx (instead of the exe) before creating the Self-Extractor exe does the trick and the exe is not corrupted.
Your application's icon can be added to this executable with a tool like Resource Hacker.
and visit http://georezo.net/jparis/MI_Enviro/Icons/adding_w_RH.htm

How to open text in Notepad from .NET?

When I click a button on a Windows Forms form, I would like to open a Notepad window containing the text from a TextBox control on the form.
How can I do that?
You don't need to create file with this string. You can use P/Invoke to solve your problem.
Usage of NotepadHelper class:
NotepadHelper.ShowMessage("My message...", "My Title");
NotepadHelper class code:
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Notepad
{
public static class NotepadHelper
{
[DllImport("user32.dll", EntryPoint = "SetWindowText")]
private static extern int SetWindowText(IntPtr hWnd, string text);
[DllImport("user32.dll", EntryPoint = "FindWindowEx")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
public static void ShowMessage(string message = null, string title = null)
{
Process notepad = Process.Start(new ProcessStartInfo("notepad.exe"));
if (notepad != null)
{
notepad.WaitForInputIdle();
if (!string.IsNullOrEmpty(title))
SetWindowText(notepad.MainWindowHandle, title);
if (!string.IsNullOrEmpty(message))
{
IntPtr child = FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);
SendMessage(child, 0x000C, 0, message);
}
}
}
}
}
References (pinvoke.net and msdn.microsoft.com):
SetWindowText: pinvoke | msdn
FindWindowEx: pinvoke | msdn
SendMessage: pinvoke | msdn
Try this out:
System.IO.File.WriteAllText(#"C:\test.txt", textBox.Text);
System.Diagnostics.Process.Start(#"C:\test.txt");
Save the file to disk using File.WriteAllText:
File.WriteAllText("path to text file", myTextBox.Text);
Then use Process.Start to open it in notepad:
Process.Start("path to notepad.exe", "path to text file");
For non ASCII user.
[DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
private static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
Based on #Peter Mortensen answer
Add CharSet = CharSet.Unicode to the attribute for supporting Unicode characters
I was using the NotepadHelper solution until I discovered it doesn't work on Windows 11. Writing the file to disk and starting with the default text editor seems to be the best solution. This has already been posted, but I discovered you need to pass UseShellExecute=true.
System.IO.File.WriteAllText(path, value);
System.Diagnostics.ProcessStartInfo psi = new() { FileName = path, UseShellExecute = true };
System.Diagnostics.Process.Start(psi);
I write to the System.IO.Path.GetTempPath() folder and run a cleanup when the application exits - searching for a unique prefix pattern for file names used by my app. Something like this:
string pattern = TempFilePrefix + "*.txt";
foreach (string f in Directory.EnumerateFiles(Path.GetTempPath(), pattern))
{
File.Delete(f);
}

Using RegSetKeySecurity to avoid registry redirection

In order to avoid registry redirection to Wow64 keys, how to translate the following code that uses Microsoft.Win32 APIs
public void SetKeyAccessControl(
RegistryKey rootKey, string subKeyName, string identity,
RegistryRights rights, InheritanceFlags inheritanceFlags,
PropagationFlags propagationFlags, AccessControlType accessType)
{
using (RegistryKey regKey = rootKey.OpenSubKey(subKeyName, true))
{
RegistrySecurity acl = new RegistrySecurity();
RegistryAccessRule rule = new RegistryAccessRule(identity, rights, inheritanceFlags, propagationFlags, accessType);
acl.AddAccessRule(rule);
regKey.SetAccessControl(acl);
}
}
into using advapi32 RegSetKeySecurity API
[DllImport(#"advapi32.dll", EntryPoint = "RegSetKeySecurity", SetLastError = true)]
internal static extern int RegSetKeySecurity(IntPtr handle, uint securityInformation, IntPtr pSecurityDescriptor);
To avoid Registry redirection, you could do something like this...
SafeRegistryHandle handle = rootKey.Handle;
RegistryKey rootKey32 = RegistryKey.FromHandle(handle, RegistryView.Registry32);
RegistryKey rootKey64 = RegistryKey.FromHandle(handle, RegistryView.Registry64);
You can then use rootKey32 or rootKey64 to open a subkey and you'll get the subkey of the view requested.
At least it works in the few test cases I've tried. And, according to the documentation for FromHandle...
The view parameter for this method is used in subsequent operations,
such as opening subkeys
Another native method needs to be involved and given an SDDL, the following code sets ACLs on the right registry key:
[DllImport("Advapi32.dll", CallingConvention = CallingConvention.Winapi, SetLastError = true, CharSet = CharSet.Auto)]
internal static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string stringSecurityDescriptor, int stringSDRevision, out IntPtr ppSecurityDescriptor, ref int securityDescriptorSize);
string sddl = "...";
IntPtr secDescriptor = IntPtr.Zero;
int size = 0;
ConvertStringSecurityDescriptorToSecurityDescriptor
(
sddl,
1, // revision 1
out secDescriptor,
ref size
);
// get handle with RegOpenKeyEx
RegSetKeySecurity
(
handle,
0x00000004, // DACL_SECURITY_INFORMATION
secDescriptor
);

ExtractAssociatedIcon returns null

I'm using the ExtractAssociatedIcon method to retrieve the icon for the file. My hope is to retrieve the same icon that a user would see in their explorer window.
public static Icon GetIcon(string fileName)
{
try
{
Icon icon = Icon.ExtractAssociatedIcon(fileName);
return icon;
}
catch
{
return null;
}
}
This works 99% of the time. However, if the user has linked to a file on a shared path, such as \\SOME_SERVER\my documents\this file.pdf it returns null. It falls through the "catch" with the error that the file path is not a valid path.
It is a valid URI (I've verified the file exists, is readable, etc.), but not a valid fully-qualified drive path with the X:\some\folder notation.
How can I get around this, if at all?
Thanks.
Re-UPDATE
Here's the solution I ended up with. It's much cleaner than the first update. Many thanks to Chris Haas, whose answer was a comment, and not a direct answer. If/when he makes it a direct answer, I will update this as such.
I still had to go down to a lower level and fetch the icon through C++ libraries, but the only library I needed is listed below:
#region Old-School method
[DllImport("shell32.dll")]
static extern IntPtr ExtractAssociatedIcon(IntPtr hInst,
StringBuilder lpIconPath, out ushort lpiIcon);
public static Icon GetIconOldSchool(string fileName)
{
ushort uicon;
StringBuilder strB = new StringBuilder(fileName);
IntPtr handle = ExtractAssociatedIcon(IntPtr.Zero, strB, out uicon);
Icon ico = Icon.FromHandle(handle);
return ico;
}
#endregion
Once I had defined the above method, the GetIcon() method becomes:
public static Icon GetIcon(string fileName)
{
try
{
Icon icon = Icon.ExtractAssociatedIcon(fileName);
return icon;
}
catch
{
try
{
Icon icon2 = GetIconOldSchool(fileName);
return icon2;
}
catch
{
return null;
}
}
}
(Comment turned into post - CTIP)
Check out the link here which eventually leads to P/Invoke.net with the following code:
[DllImport("shell32.dll")]
static extern IntPtr ExtractAssociatedIcon(IntPtr hInst, StringBuilder lpIconPath, out ushort lpiIcon);
[DllImport("shell32.dll")]
static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);
_
ushort uicon;
StringBuilder strB = new StringBuilder(YOUR_FILE_PATH);
IntPtr handle = ExtractAssociatedIcon(this.Handle, strB, out uicon);
Icon ico = Icon.FromHandle(handle);
return ico.ToBitmap();

Categories