Self deletable application in C# in one executable - c#

Is it possible to make an application in C# that will be able to delete itself in some condition.
I need to write an updater for my application but I don't want the executable to be left after the update process.
There is an official .Net OneClick but due to some incompatibilities with my HTTP server and some problems of OneClick itself I'm forced to make one myself.
George.
[EDIT]
In more details:
I have:
Application Executable which downloads the updater ("patch", but not exactly) this "patch" updates the application executable itself.
Application executes as folowed:
Application: Start -> Check Version -> Download new Updater -> Start Updater -> exit;
Updater: Start -> do it's work -> start Application Executable -> self delete (this is where I get stuck);

If you use Process.Start you can pass in the Del parameter and the path to the application you wish to delete.
ProcessStartInfo Info=new ProcessStartInfo();
Info.Arguments="/C choice /C Y /N /D Y /T 3 & Del "+
Application.ExecutablePath;
Info.WindowStyle=ProcessWindowStyle.Hidden;
Info.CreateNoWindow=true;
Info.FileName="cmd.exe";
Process.Start(Info);
Code snippet taken from this article

I suggest you use a batch file as a bootstrap and have it delete itself and the exe afterwards
public static class Updater
{
public static void Main()
{
string path = #"updater.bat";
if (!File.Exists(path))
{
// Create a file to write to.
using (StreamWriter sw = File.CreateText(path))
{
sw.WriteLine("updater.exe");
sw.WriteLine("delete updater.exe /y");
sw.WriteLine("delete updater.bat /y");
}
System.Process.Start(path);
}
else
{
RunUpdateProcess();
}
}
private void RunUpdateProcess()
{
.....
}
}

It's tricky without introducing yet another process (that you'd then want to delete as well, no doubt). In your case, you already have 2 processes - updater.exe and application.exe. I'd probably just have the Application delete updater.exe when it's spawned from there - you could use a simple command line arg, or an IPC call from updater.exe to application.exe to trigger it. That's not exactly a self deleting EXE, but fulfills the requirements I think.
For the full treatment, and other options you should read the definitive treatment of self deleting EXEs. Code samples are in C (or ASM), but should be p/invokable.
I'd probably try CreateFile with FILE_FLAG_DELETE_ON_CLOSE for updater.exe with something like (psuedo code):
var h = CreateFile(
"updater.exe",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE
);
byte[] updaterBytes = GetUpdaterBytesFromWeb();
File.WriteAllBytes("updater.exe", updaterBytes);
Process.Start("updater.exe");
Once application.exe exits, updater.exe has a file handle of 1. When updater.exe exits, it drops to 0 and should be deleted.

Couldn't you simply delete the updater from within the application? i.e.:
Application: Start -> [Delete old updater if present] -> Check version -> Download new updater -> Start updater -> exit;
Updater: Start -> Perform update -> Start application -> exit;
Application: Start -> [Delete old updater if present] -> ...

Mhh so let me get this straight. You got some application.exe and your updater application updater.exe?
So when you start your application.exe it checks some webserver for a newer version and then starts updater.exe. And you want updater.exe to delete itself after it has finished updating? Or do you want to delete the downloaded patch (or similar)? Please be a bit more precise.
Consider that when you are deleting updater.exe you must recreate it for the next update process.

your second line can be
Updater: Star -> do it's work -> start Application Executable -> Updater Exits -> Application deletes your Updater.exe

public void uninstall() {
string app_name = Application.StartupPath + "\\" + Application.ProductName + ".exe";
string bat_name = app_name + ".bat";
string bat = "#echo off\n"
+ ":loop\n"
+ "del \"" + app_name + "\"\n"
+ "if Exist \"" + app_name + "\" GOTO loop\n"
+ "del %0";
StreamWriter file = new StreamWriter(bat_name);
file.Write(bat);
file.Close();
Process bat_call = new Process();
bat_call.StartInfo.FileName = bat_name;
bat_call.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
bat_call.StartInfo.UseShellExecute = true;
bat_call.Start();
Application.Exit();
}
self delete by an external executable file ".bat" for windows form applications.

Related

How can I change two items in one string via Windows command prompt and C#

Total newbie at C# and Windows command prompt, so please be patient with me.
This is my first time to write code to create an executable designed to alter the registry, as well as, the preferences in Chrome.
A little background first. The engineers at the company that I am contracted to, use an old program called Cadkey, which is used to view files of things that the company has been manufacturing since the 40s.
As many of you probably know, Chrome no longer allows for applets and other type of files to be viewed in the browser for security purposes, but most engineers at this company would rather use Chrome than IE.
As a result, I have been charged with the task of giving them the ability to open the file via an "application link" and some have also referred to this as "custom url protocol" like the following example:
some file
This allows the engineers to click on the file name in the browser, which then opens the file in the program Cadkey.
To accomplish this, I have to register the key within the user's registry, as well as, alter the preference file of Chrome, so that they are not bothered with the little window that alerts them that this file is about to use the Windows command prompt. I had no issue with the later, but my boss wanted the process to be as smooth as possible.
With all of this said, I was able to accomplish this with the following code:
using System;
using System.IO;
using Microsoft.Win32;
using Newtonsoft.Json.Linq;
namespace CadkeyRegAndPrefs
{
class Program
{
static void Main()
{
try
{
RegistryKey hkCurUsr = Registry.CurrentUser.OpenSubKey("Software\\Classes", true);
// Create a subkey named cadkey under HKEY_CURRENT_USER.
RegistryKey cadkey = hkCurUsr.CreateSubKey("cadkey", true);
cadkey.SetValue("", "URL:cadkey Protocol");
cadkey.SetValue("URL Protocol", "");
// Create data for the defltIcn subkey.
RegistryKey cadkeyDefltIcn = cadkey.CreateSubKey("DefaultIcon", true);
cadkeyDefltIcn.SetValue("", "");
cadkeyDefltIcn.SetValue("C:\\CK19\\Ckwin.exe", "-1");
// Create data for the cadkeyShell subkey.
RegistryKey cadkeyShell = cadkey.CreateSubKey("shell", true);
RegistryKey cadkeyShellOpen = cadkeyShell.CreateSubKey("open", true);
// Create data for the cadkeyCommand subkey.
RegistryKey cadkeyCommand = cadkeyShellOpen.CreateSubKey("command", true);
cadkeyCommand.SetValue("", "");
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=!\"");
// Retrieve path of the current user
string path = System.Environment.ExpandEnvironmentVariables("%userprofile%");
string pathToPrefs = path + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Preferences";
// Have to create a JObject of the json file
JObject jsonObj = JObject.Parse(File.ReadAllText(pathToPrefs));
// Determine if the user has a protocol handler set and append cadkey set to false, otherwise, create node and set cadkey to false
var isExlcudedSchemes = jsonObj.SelectToken("protocol_handler.excluded_schemes");
if (isExlcudedSchemes != null)
{
jsonObj["protocol_handler"]["excluded_schemes"]["cadkey"] = false;
} else {
jsonObj.Add(new JProperty("protocol_handler", new JObject(
new JProperty("excluded_schemes", new JObject(
new JProperty("cadkey", new JObject()))))));
jsonObj["protocol_handler"]["excluded_schemes"]["cadkey"] = false;
}
// set the variable output and write the json content to the preferences file for Chrome
string output = Newtonsoft.Json.JsonConvert.SerializeObject(jsonObj, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText(pathToPrefs, output);
// Let the end user know that the operation was successful
Console.WriteLine("Cadkey registration installed successfully");
Console.WriteLine("\nPress any key to exit");
Console.ReadKey();
}
catch (Exception e)
{
Console.WriteLine("{0} Exception caught.", e.ToString());
}
}
}
}
The engineers have a little bootstrap modal, which gives them the instructions to download the exe, followed by double clicking the exe to install the registry keys and alter the Chrome prefs.
So what's the problem? The code registers the keys to the user's registry, as well as altering the Chrome preferences file, along with informing the user at the end that it was successful.
The problem is that some of the files have a space in the name, which then makes Cadkey prompt the user that the file cannot be found. Mainly because "%20" appears in the place of the space. I guess Cadkey was made at a time when urlencoding was not part of the design.
I have tried to alter the url via the command prompt, as I do with removing the string "cadkey:" from the param being passed to the command prompt:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! & !r:%20= !\"
I have tried:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! | !r:%20= !\"
I have tried:
cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=! & !r:%%20= !\"
I have tried using another var
cmd /V:ON /C \"SET r=%1 & !r:cadkey:=! & SET s=r start C:\\CK19\\Ckwin.exe !s:%20= !\"
While the command is successful in replace the string "cadkey:" - I have yet to replace both strings at the same time. I have tried too many things, but I am a newbie at this, any help would be appreciated.
Thanks in advance
After working with my boss on this last night, we finally found an answer, which is the following:
Change
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & start C:\\CK19\\Ckwin.exe !r:cadkey:=!\"");
To:
cadkeyCommand.SetValue("", "cmd /V:ON /C \"SET r=%1 & SET s=!r:cadkey:= ! & SET t=!s:%%20= ! & start C:\\CK19\\Ckwin.exe !t!\"");
The result is that the both the string "cadkey:" and the string "%20" are removed, with the string "%20" replaced by a space, which results in the following being passed to cadkey where you see the variable "t"

Launching C# program from another C# program

Due to me having knowledge of launching apps I am aware that you have multiple ways of launching an application in C# .NET, but I'm running into a issue that occurs when attempting to launch a SDL2 application.
I have attempted the following using the Process class to:
Start the .exe file of the build.
Start the application using "cmd.exe /K" or "cmd.exe /c" followed by "exec" or "call" or "start" followed by "{path to file}" or "{path to batch file to launch the application}". Launching the application via a batch file and CMD works fine. But, whenever I attempt to even launch the application (even in a new instance of Command-Prompt launched from cmd.exe /? start cmd.exe ?params) it will yield no result.
What I can observe is that the application tries to open. It takes forever to launch into the Window mode (starting the 3D environment). After a timeout it will either, render a couple of frames of a blank window before closing or close immediately after opening the window.
So my question is, does anyone have succesfully made a launcher application for a SDL app written in C# .NET? Or knows a way to debug this behaviour? Because unfortunately, the app does not send out a error message and since SDL safely closes the application I can't observe a crash either.
Edit #1
I'm not doing anything fancy with parameters as there shouldn't be any. I already have another one functioning that launches a normal C# application as my launcher requires to open 2 programs. 1 SLD application, 1 COM:VBA controlling application.
Given:
string audioSpectrumProgram = "AudioSpectrum.exe";
string audioSpectrumBatchProgram = "AudioSpectrum.bat";
private void BtnLaunchPPTApp_OnClick()
{
//Powerpoint controlling application
pVBAApp = Process.Start(presenterProgram, $"\"{this.path}\" {this.audioFormatParams[0]} {((this.ckboxGenerate.Checked) ? "--create" : "")} lang={this.languageCodesParams[this.cboxLanguage.SelectedIndex]}");
}
Method 1:
private void BtnLaunchSDLApp_OnClick()
{
pVizualizer = Process.Start(audioSpectrumProgram); //file launched from local path (is correct)
}
Method 2:
pVizualizer = Process.Start(audioSpectrumBatchProgram); //file launched from local path (is correct)
Method 3:
ProcessStartInfo info = new ProcessStartInfo("cmd.exe");
FileInfo spectrumFileInfo = new FileInfo(audioSpectrumProgram);
if (spectrumFileInfo.Exists)
info.Arguments = $"/c \"{spectrumFileInfo.FullName}\"";
pVizualizer = Process.Start(info);
Method 4:
based on senario of method 3. You don't have to parse arguments using ProcessStartInfo.
pVizualizer = Process.Start($"cmd.exe /K call \"{spectrumFileInfo.FullName}\"") //to observe what happens to the application
Edit #2
Not affected by changing the UseShellExecute to true or false
private void btnOpenVisualizer_Click(object sender, EventArgs e)
{
FileInfo spectrumFileInfo = new FileInfo(audioSpectrumProgram);
ProcessStartInfo info = new ProcessStartInfo(spectrumFileInfo.FullName);
info.UseShellExecute = true;
pVizualizer = new Process();
pVizualizer.StartInfo = info;
pVizualizer.EnableRaisingEvents = true;
pVizualizer.Exited += new EventHandler(myProcess_Exited);
pVizualizer.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
Console.WriteLine(
$"Exit time : {pVizualizer.ExitTime}\n" +
$"Exit code : {pVizualizer.ExitCode}\n"
);
}
A general way of analyzing startup issues is to use SysInternals Process Monitor.
Record the application that is not starting up properly. Use a filter for your application. Then go through all items which don't have SUCCESS in the result column. Typically you want to do that bottom-up, since the last error is the one stopping your application from loading.
Like this you'll find common startup issues like:
missing DLLs or other dependencies
old DLLs or DLLs loaded from wrong location (e.g. registered COM components)
wrong working directory, e.g. access to non-existent config files
Ok For Future reference:
Pathing to the files can be correct and everything might be in order but if you are using DLLs for imports. Change the process's working directory.
The project will run, libs can "sometimes" be found but can cause a weird unknown bug like this one. So the most optimal way of running another C# instance with SDL or any other kind of library:
private void RunSDLProgram()
{
FileInfo spectrumFileInfo = new FileInfo("pathToFile.exe");
ProcessStartInfo info = new ProcessStartInfo(spectrumFileInfo.FullName);
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
info.WorkingDirectory = spectrumFileInfo.DirectoryName;
pVizualizer = new Process();
pVizualizer.StartInfo = info;
pVizualizer.EnableRaisingEvents = true;
pVizualizer.Exited += new EventHandler(myProcess_Exited);
pVizualizer.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
Console.WriteLine(
$"Exit time : {pVizualizer.ExitTime}\n" +
$"Exit code : {pVizualizer.ExitCode}\n" +
$"output : {pVizualizer.StandardOutput}\n" +
$"err : {pVizualizer.StandardError}\n"
);
}
Running a batch file will look at it's own directory and makes all references local, but it won't alter the working directory. (already had my suspicions about changing the work directory but I didn't see a way to call 2 opperations in process.start("cmd.exe");)

File copy started from aspnet webforms and implemented using batch, pstools and robocopy doesn't work

I have 4 independent servers (not in domain):
IIS, SQL1, SQL2, SQL3
I want to copy a database backup from SQL1 to SQL2 or SQL3 (depending on parameters) by button click on webpage hosted on IIS
I wrote a button click method for that, which is calling batch file located in inetpub folder on IIS
Batch is using pstools to run robocopy on SQL1 which should copy required file to destination server (SQL2 or SQL3)
This solution works if I execute batch directly on IIS (cmd as Administrator) or when I debug it on my local machine, but it doesn't if it is called from the running site.
It even doesn't spend any time between the following lines:
batchProcess.Start();
batchProcess.WaitForExit();
Here is my copy method:
private bool ProcessCopy(string file, string destinationIp)
{
SecureString password = ConvertToSecureString("myPassword");
try
{
string batchPath = Server.MapPath(".") + "\\CopyFile.bat";
string cmd = #"c:\Windows\System32\cmd.exe";
ProcessStartInfo processInfo = new ProcessStartInfo
{
FileName = cmd,
UseShellExecute = false
};
Process batchProcess = new Process {StartInfo = processInfo};
batchProcess.StartInfo.Arguments = $"/C {batchPath} {file} {destinationIp}";
batchProcess.StartInfo.Domain = "";
batchProcess.StartInfo.UserName = "Administrator";
batchProcess.StartInfo.Password = password;
batchProcess.StartInfo.RedirectStandardOutput = true;
batchProcess.StartInfo.RedirectStandardError = true;
batchProcess.StartInfo.CreateNoWindow = true;
batchProcess.Start();
batchProcess.WaitForExit();
string response = batchProcess.StandardOutput.ReadToEnd();
response += batchProcess.StandardError.ReadToEnd();
statusStringAppend($"response: {response}");
return true;
}
catch (Exception ex)
{
statusStringAppend($"Failed: {ex.Message}. {ex.StackTrace}");
}
return false;
}
Batch body is:
#echo off
c:\qa\tools\pstools\psexec64.exe -accepteula -u Administrator -p myPassword \\SourceIP robocopy \\SourceIP\qa\db_backup\ \\%2\qa\db_backup\ %1 /is
My questions are:
1. Why the file was not copied?
2. Is there any better way to get it copied?
CODE UPDATED ACCORDING TO SUGGESTIONS BELOW
My guess is that you never executed pstools as the user that your IIS service is running as before, so the EULA dialog is blocking your execution.
If you remember, you always got a window and needed to press the accept button when running any sysinternals tool like pstools the first time.
I guess this should work for you:
c:\qa\tools\pstools\psexec64.exe -accepteula -u Administrator -p myPassword \\SourceIP robocopy \\SourceIP\qa\db_backup\ \\%2\qa\db_backup\ %1 /is
[EDIT]
You would most likely have hit this problem later on, anyway it did not work for you, so i have to list what else could be wrong with your code:
starting a .bat file needs cmd.exe as mother process, you cannot just start a .bat file as process directly. Instead you can for example use another method than ProcessStartInfo that spawns the system default script interpreter automatically: Executing Batch File in C#
the process for executing batch files is "cmd.exe", first parameter "/C", second parameter the batch file you are executing
when executing typical commandline tools, you might consider reading the SDTOUT (standard output) of the process you are executing, like this: Capturing console output from a .NET application (C#)

How to make my application still running after restart

I have a Login application that will disable after 3 times of wrong attempts..I just wanted to know how to make that application still running after i restart my computer..my application is already in .exe ...thanks in advance :)
Going on the assumption that you're talking about Windows here, a simple google search for "windows run application at startup" would yield a plethora of simple ways to make an application run when the OS starts.
Here is one of those results...
http://windows.microsoft.com/en-us/windows/run-program-automatically-windows-starts#1TC=windows-7
I guess you can add application executable in windows startup. Following method help to create app shortcut.
public static void CreateStartupShortcut(string applicationName)
{
string _startupFolder = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
WshShell _shell = new WshShell();
//
string _shortcutAddress = _startupFolder + #"\" + applicationName + ".lnk";
IWshShortcut _shortcut = (IWshShortcut)_shell.CreateShortcut(_shortcutAddress);
_shell.RegDelete(_shortcutAddress);
//
_shortcut.Description = "Start up shortcut link for application " + applicationName + ". Delete shortcut to if you dont want to run application on stat up";
_shortcut.WorkingDirectory = Application.StartupPath;
_shortcut.TargetPath = Application.ExecutablePath;
_shortcut.Save();
}
Simply pass your application exe path to the method and it will add shortcut in windows startup.

How to get command line from a ClickOnce application?

Before publishing I went to Project -> Properties -> Options -> File Associations and added the extension ".hsp". Set an icon and a ProgID ("MyCompany.Document.1" for testing). After I published and installed, my .hsp files had the icon I set, so the file association should be properly set, but when I double clicked one of these files the application run and I expected the name of the file I double clicked to be in the command line. I tried reading the parameter passed to my Main function, tried Environment.CommandLine, and tried Environment.GetCommandLineArgs(), but the only thing I found was the application path. By the way I'm doing all this check before creating my main form in the Main function, just to test. The args parameter is empty and the other two only contain my app path.
This is the beginning of my Main function:
static void Main(string[] args)
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
MessageBox.Show("CommandLine -> " + Environment.CommandLine);
foreach (string str in args) MessageBox.Show("args -> " + str);
foreach (string str in Environment.GetCommandLineArgs()) MessageBox.Show("GetCommandLineArgs -> " + str);
When you publish an app with ClickOnce and then launch it by double-clicking an associated file, the path to that file actually gets stored here:
AppDomain.CurrentDomain.SetupInformation.ActivationArguments.ActivationData[0]
See MSDN's documentation for it here:
http://msdn.microsoft.com/en-us/library/system.runtime.hosting.activationarguments.aspx
Plus a tutorial on adding file associations to "Published" projects:
http://blogs.msdn.com/b/mwade/archive/2008/01/30/how-to-add-file-associations-to-a-clickonce-application.aspx

Categories

Resources