Delete currently loaded assembly - c#

In my application comes with an uninstaller.
Everything is working fine, except that I can't find no way to delete the uninstaller.exe file when it's all done.
I tried to copy the current assembly exe into a temp directory, but the file-handle of the original file is still locked.
Any ideas?

You will need to PInvoke to do this. MoveFileEx has the ability to schedule deleting the file on next reboot.
If dwFlags specifies MOVEFILE_DELAY_UNTIL_REBOOT and lpNewFileName is NULL, MoveFileEx registers the lpExistingFileName file to be deleted when the system restarts.
Something like:
[return: MarshalAs (UnmanagedType.Bool)]
[DllImport ("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool MoveFileEx (string lpExistingFileName, string lpNewFileName, int dwFlags);
public static bool ScheduleDelete (string fileFullName) {
if (!File.Exists (fileFullName))
throw new InvalidOperationException ("File does not exist.");
return MoveFileEx (fileFullName, null, 0x04); //MOVEFILE_DELAY_UNTIL_REBOOT = 0x04
}

It would be interesting if you posted some code of how you exactly copy the uninstaller.exe and change execution to that specific executable.
I think unloading the application domain will free the file-handle.

You might be able to achieve what you want by using shadow copying of assemblies, but I haven't tried that for this scenario.

You can use "cmd" with delay:
internal static void ExitAndDelete()
{
var f = Application.ExecutablePath;
Process.Start(new ProcessStartInfo("CMD.exe", "/C timeout 2&del \"" + f + "\"") { WindowStyle = ProcessWindowStyle.Hidden });
}

Related

How to reliably start a process in C#?

I'm trying to start .lnk shortcuts from my application. However, I'm struggling with the infamous automatic filesystem redirection for 32/64-bit processes.
I'm searching for a way to simply start an application from the shortcut and I don't care, what happens to that process later. Effectively I'd like to start the shortcut the same way as if user doubleclicked it in the Explorer.
Currently I'm using the following method, but it still doesn't work (ie. I'm unable to start Word this way):
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);
private static void Start(string shortcut)
{
IntPtr temp = IntPtr.Zero;
try
{
Wow64DisableWow64FsRedirection(ref temp);
var error = Marshal.GetLastWin32Error();
ProcessStartInfo info = new ProcessStartInfo(shortcut);
info.UseShellExecute = true;
Process.Start(info);
}
finally
{
Wow64RevertWow64FsRedirection(temp);
}
}
How can I reliably start an application, knowing its direct location on the drive in C#?
Edit:
I used SysInternals' ProcMon to check, what is done behind scenes. It looks like the proper path is searched, but then system for some reason still falls back to the forced 32-bit one.

C#: How to get the executable path that Process.Start will use when given a file with no path?

The System.Diagnostics.Process.Start() method accepts a ProcessStartInfo class instance initialized with an executable with no path, such as Notepad.exe. After the process starts one can find the full path it used, such as C:\Windows\SysWOW64\notepad.exe. This is perfect, except when you would like to know the full path without actually launching the program. In my case, I'd like to get the icon from the executable ahead of time.
This is similar to the behavior of the windows "where" command, for example:
C:>where notepad.exe
C:>\Windows\System32\notepad.exe
C:>\Windows\notepad.exe
The first response C:\Windows\System32\notepad.exe is essentially the same as that used by "Process".
The order in which paths are searched is actually registry-dependent, so simply enumerating through the PATH environment variable is not guaranteed to produce the expected result, particularly where there is a file of the expected name in the current working directory. To reliably get the executable path, you will want to call the SearchPath Win32 function in Kernel32.
There is no framework .NET function that exposes SearchPath, but the function can be invoked directly through P/Invoke.
The following sample program illustrates the usage of this function. If notepad.exe exists in the system search paths, per the system configuration, it will print the path; if it does not exist, it will print "File not found" instead.
using System;
using System.Text;
using System.Runtime.InteropServices;
class Program
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint SearchPath(string lpPath,
string lpFileName,
string lpExtension,
int nBufferLength,
[MarshalAs ( UnmanagedType.LPTStr )]
StringBuilder lpBuffer,
out IntPtr lpFilePart);
const int MAX_PATH = 260;
public static void Main()
{
StringBuilder sb = new StringBuilder(MAX_PATH);
IntPtr discard;
var nn = SearchPath(null, "notepad.exe", null, sb.Capacity, sb, out discard);
if (nn == 0)
{
var error = Marshal.GetLastWin32Error();
// ERROR_FILE_NOT_FOUND = 2
if (error == 2) Console.WriteLine("No file found.");
else
throw new System.ComponentModel.Win32Exception(error);
}
else
Console.WriteLine(sb.ToString());
}
}
If you enter an application name (like notepad.exe) in the command line, it searches in the current directory and all paths which are specified in the PATH environment variable. This works similarly when you use Process.Start.
So you would need to search in all paths of the PATH environment variable for your executable file and then extract the icon from it.

How To Add Resources Without Compatibility Error?

The code below creates a copy of the application and adds resources to the copy. When you run the copy that has resources in it, it does it's job first. But when it exits, it exits with Program Compatibility Assistant error:
Image is from Google.
class Program
{
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern IntPtr BeginUpdateResource([MarshalAs(UnmanagedType.LPStr)] string filename, bool deleteExistingResources);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool UpdateResource(IntPtr resource, [MarshalAs(UnmanagedType.LPStr)] string type, [MarshalAs(UnmanagedType.LPStr)] string name, ushort language, IntPtr data, uint dataSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool EndUpdateResource(IntPtr resource, bool discardChanges);
private static void modifyResources(string filename)
{
IntPtr handle = BeginUpdateResource(filename, true);
UpdateResource(handle, "10", "1", 0, Marshal.StringToHGlobalAnsi("hello world"), (uint) 11);
EndUpdateResource(handle, false);
}
static void Main(string[] args)
{
string exeFilename = Process.GetCurrentProcess().MainModule.FileName;
string filename = Path.GetFileName(exeFilename);
string anotherFilename = exeFilename.Replace(filename, "_" + filename);
File.Copy(exeFilename, anotherFilename, true);
modifyResources(anotherFilename);
}
}
I don't get it. What mistakes do I make ?
More infos: Win 7 64x, App 86x
Notes (some of these made me think the error was gone):
maybe cleaning up imported libraries might help
maybe Assemblyname or namespace
seems the Compatibility Assistant checks too much and thinks something is wrong when the program does something different than the assistant expects.
project as single exe, no extra dlls (since extinguishing my extra dll, no error occurred)
definitely: error is not running-code related (empty main method)
definitely: error is filename related
Problem is the acquisitiveness of people! There are file names that are unwanted on Windows. Microsoft seems to prevent people from writing new installers. They spawn compatibility errors to prevent certain software from becoming popular and spreading.
Example:
If you use Resource Hacker (tested on Win7):
Go to the installation directory of Resource Hacker.
Run it, close it. No problem,
Rename ResHacker.exe to ResH Installer acker.exe
Run it, close it, see the problem.
Rename it to ResH 4kj545ui45kj4 acker.exe
Run it, close it. No problem.
Rename it back to ResHacker.exe
Punchline:
if (exeFilename.Contains("Installer") && exeFile.isCapableOfResourceManipulation())
makeProblem();
else
ignore();
// assembly info is checked too
// confirmed: after removing all unwanted keywords the error stays away
// even in my old project

Updating images in resource section of an exe (in c#/C)

I have few images embedded in my executable in resource section.
I followed these steps to create my executable:
Generated .resx file for all the images (.jpg) in a directory using some utility. The images are named image1.jpg, image2.jpg and so on.
created .resources file from .resx file using: resgen myResource.resx
Embedded the generated .resource file using /res flag as: csc file.cs /res:myResource.resources
4 I am accessing these images as:
ResourceManager resources = new ResourceManager("myResource", Assembly.GetExecutingAssembly());
Image foo = (System.Drawing.Image)(resources.GetObject("image1"));
This all is working fine as expected. Now I want to change embedded images to some new images. This is what I am currently doing:
class foo
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, string wLanguage, Byte[] lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle.ToInt32() == 0)
throw new Exception("File Not Found: " + fileName + " last err: " + Marshal.GetLastWin32Error());
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
int fileSize = imgData.Length;
Console.WriteLine("Updaing resources");
if (UpdateResource(handle, "Image", "image1", "image1", imgData, (uint)fileSize))
{
EndUpdateResource(handle, false);
Console.WriteLine("Update successfully");
}
else
{
Console.WriteLine("Failed to update resource. err: {0}", Marshal.GetLastWin32Error());
}
}
}
The above code is adding a new resource for the specified image (inside IMAGE title with some random number, as seen in Resource hacker), but I want to modify the existing resource data for image1.
I am sure that I am calling UpdateResource with some invalid arguments.
Could any one help pointing that out?
I am using .NET version 2
Thank you,
Vikram
I think you are making a confusion between .NET resources, and Win32 resources. The resources you add embedding with the /res argument to csc.exe are .NET resources that you can successfully read using you ResourceManager snippet code.
Win32 resources are another beast, that is not much "compatible" with the managed .NET world in general, athough you can indeed add them to a .NET exe using the /win32Res argument - note the subtle difference :-)
Now, if you want to modify embedded .NET resources, I don't think there are classes to do it in the framework itself, however you can use the Mono.Cecil library instead. There is an example that demonstrates this here: C# – How to edit resource of an assembly?
And if you want to modify embedded Win32 resources, you code needs some fixes, here is a slightly modified version of it, the most important difference lies in the declaration of UpdateResource:
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName, bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, short wLanguage, byte[] lpData, int cbData);
[DllImport("kernel32.dll")]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public static void Main(string[] args)
{
IntPtr handle = BeginUpdateResource(args[0], false);
if (handle == IntPtr.Zero)
throw new Win32Exception(Marshal.GetLastWin32Error()); // this will automatically throw an error with the appropriate human readable message
try
{
byte[] imgData = File.ReadAllBytes("SetupImage1.jpg");
if (!UpdateResource(handle, "Image", "image1", (short)CultureInfo.CurrentUICulture.LCID, imgData, imgData.Length))
throw new Win32Exception(Marshal.GetLastWin32Error());
}
finally
{
EndUpdateResource(handle, false);
}
}
This is impossible. You cant modify compiled file that you are running.
I believe you can add new images in run time but can't update a resource that is essentially just held in memory.
If you add a resource in run time, it exists but I don't think it is compiled and therefore I don't think it is accessible to you.
Is there a reason you aren't using content instead?

C#: System.Diagnostics.Process.Start("Explorer.exe", #"/select" + FilePath). Can not open file when file's name is unicode character

I want to open file's location with window Explorer. I am using C# with code
System.Diagnostics.Process.Start("Explorer.exe", #"/select," + FilePath)
it works well with simple English character, but it could not open the file's location if the file's name is Unicode character (Thia language).
Anyone could help please?
Try putting it in quotes:
System.Diagnostics.Process.Start("Explorer.exe", #"/select,""" + FilePath + "\"")
No trouble with this code snippet:
static void Main(string[] args) {
string path = #"c:\temp\លួចស្រលាញ់សង្សារគេ.DAT";
System.IO.File.WriteAllText(path, "hello");
string txt = System.IO.File.ReadAllText(path);
}
Windows 7, the file is created and displays correctly in Explorer. You didn't document your operating system version so that's one failure mode, albeit a very small one. Much more likely is trouble with the file system that's mapped to your E: drive. Like a FAT32 volume on a flash drive or a network redirector. Ask questions about that, respectively, at superuser.com and serverfault.com. Do not forget to document those essential details.
The following code works for me with files with korean characters (are unicode characters). Please try it and let me know if it works.
...
if (this.IsDirectory())
{
OpenFileWith("explorer.exe", this.FullPath, "/root,");
}
else
{
OpenFileWith("explorer.exe", this.FullPath, "/select,");
}
...
public static void OpenFileWith(string exePath, string path, string arguments)
{
if (path == null)
return;
try
{
System.Diagnostics.Process process = new System.Diagnostics.Process();
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
if (exePath != null)
{
process.StartInfo.FileName = exePath;
//Pre-post insert quotes for fileNames with spaces.
process.StartInfo.Arguments = string.Format("{0}\"{1}\"", arguments, path);
}
else
{
process.StartInfo.FileName = path;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(path);
}
if (!path.Equals(process.StartInfo.WorkingDirectory))
{
process.Start();
}
}
catch(System.ComponentModel.Win32Exception ex)
{
FormManager.DisplayException(ex, MessageBoxIcon.Information);
}
}
Explorer will go to a default folder in this case 'My Documents', if the folder you are attempting to open is not there. Make sure it exists.
Here's the deal as far as I've determined: at least as of Windows 8.1, "Explorer.exe" appears to strip out all combining characters before looking for the file. You can test this either in c# or a console (do chcp 65001 first to get in unicode mode). If you try to open a target named ปู (thai for "crab") it won't work, but if you remove the vowel mark under so that you have just ป, it will work. Further, if you have a folder named ป and you as it to open ปู, it will open the ป folder!
This explains why some other devs had no problem; the problem is not non-ascii: rather, it is filenames with composable characters. Not all languages use them, and even in languages that do, not all file names have them.
The good news is, there's a different way to open these that doesn't have this problem, which is described by #bert-huijben in this answer.
For completeness, here's the version similar to what I ended up using:
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern void ILFree(IntPtr pidlList);
[DllImport("shell32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
public static extern IntPtr ILCreateFromPathW(string pszPath);
[DllImport("shell32.dll", ExactSpelling = true)]
public static extern int SHOpenFolderAndSelectItems(IntPtr pidlList, uint cild, IntPtr children, uint dwFlags);
public void SelectItemInExplorer(string path)
{
var pidlList = ILCreateFromPathW(path);
if(pidlList == IntPtr.Zero)
throw new Exception(string.Format("ILCreateFromPathW({0}) failed",path));
try
{
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
}
finally
{
ILFree(pidlList);
}
}

Categories

Resources