I know this question was already asked, but I couldn't find the solution so far.
What I'm trying to do is uninstall a windows service and delete the folder with the windows service using C#.
Windows service uninstall
public static void Uninstall(string exeFilename)
{
var commandLineOptions = new string[1] { "/LogFile=uninstall.log" };
if (!Directory.Exists(exeFilename)) return;
var fileNames = Directory.GetFiles(exeFilename);
var serviceFile = fileNames.FirstOrDefault(f => f.EndsWith(".exe"));
var serviceFileName = Path.GetFileName(serviceFile);
var serviceName = Path.GetFileNameWithoutExtension(serviceFile);
var serviceExists = ServiceController.GetServices().Any(s => s.ServiceName == serviceName);
if (!serviceExists) return;
var installer =
new AssemblyInstaller($"{exeFilename}\\{serviceFileName}", commandLineOptions)
{
UseNewContext = true
};
installer.Uninstall(null);
installer.Dispose();
}
Folder and files delete
public static void DeleteFolder(string folderPath)
{
if(!Directory.Exists(folderPath)) return;
try
{
foreach (var folder in Directory.GetDirectories(folderPath))
{
DeleteFolder(folder);
}
foreach (var file in Directory.GetFiles(folderPath))
{
var pPath = Path.Combine(folderPath, file);
File.SetAttributes(pPath, FileAttributes.Normal);
File.Delete(file);
}
Directory.Delete(folderPath);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
Error that I get
Access to the path 'c:\services\x\x.exe' is denied.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.File.InternalDelete(String path, Boolean checkHost)
at System.IO.File.Delete(String path)
This error occurs randomly. The .exe file is not readonly, sometimes the files get deleted.
Does anyone know what's wrong?
Stopping a Windows service does not equate to exiting a process. An executable can house multiple Windows services (it's essentially the physical shell for the services).
So what you're running into, it looks like, is that the service likely stopped just fine, and uninstall can proceed and the deletion of files can proceed, but only up until the point where it reaches the executable, which hasn't yet had a chance to exit.
Stopping, exiting, uninstalling are all asynchronous and need time to complete before moving to the next step.
What you have to do is follow this formula
Ensure your code is running with elevated privileges if you can; if you can't you may run into Access Denied. You can also try changing the ownership of the target executable.
Stop or ensure the service is stopped. If there are multiple services, stop all of them.
Wait for stop to actually occur. It's not always immediate.
Call the Uninstall().
Wait some amount of time. Check to see if the process is running. If it is you will call Process.Kill() (see an implementation below).
Finally, you can call the DeleteFolder() for which your implementation looks adequate to me.
Exiting the Process
Write a method that looks something like this. You might want to tweak it.
void Exit(Process p)
{
for (int i = 0; i <= 5; i++)
{
// doesn't block
if (p.HasExited) return;
// doesn't block; pass true to terminate children (if any)
p.Kill(true);
// wait 5 seconds then try again
Thread.Sleep(5000);
}
}
After all that you should be good to go.
Is the service stopped / executable stopped and does the program have Administrator access? If you don't have administrator access, refer to:
How do I force my .NET application to run as administrator?
to run as administrator. When uninstalling a program, administrator permissions are almost always required. If this still fails, you are not the owner of the file and you must change the owner to yourself or Administrators. You can do this programatically here: Getting / setting file owner in C#
I think you have to stop the service first.
try using
sc stop <service name>
to stop the service.
Then uninstall should work.
try using installutil.exe if your uninstall code does not work. It can give your error output also.
The exe cannot be deleted if it is currently executing so make sure exe is not executing when you try to delete it.
You can use setup project, it generates the uninstaller automatically and gives you a range of possibilities with it.
Example of the service installer
You must download the addon in the market place
Related
I am working on an application in C# which does the following things:
Write an EXE to disk
Execute the EXE through Process.Start()
I am now trying to ensure that the EXE will be deleted once it is closed.
The easiest way to do so is to set the FileOptions.DeleteOnClose parameter when creating the EXE using File.Create.
However, this means that the EXE can not be executed while is in use. Once the file handle is closed, the EXE is immediately deleted before it can be executed.
Is there any way to retain a "weak reference" to the EXE in my application which does not lock the file and allows it to be executed? Alternatively, is there any way to unlock the EXE for execution with the file handle still open? Are there any other obvious solutions I am missing?
CLARIFICATION A: I am aware of other methods to delete files in use which will delete the file eventually (e.g. upon reboot). I am however looking for a method to delete the file immediately once it starts executing which is handled by the OS (e.g. when running a batch that first executes the file and then deletes it, the file would remain on disk if the batch job is terminated).
CLARIFICATION B: To explain the bigger picture: The application receives and decrypts an executable file. After decryption, the file should be executed. However, I want to make sure the decrypted version of the EXE does not stay on disk. Ideally, I also want to prevent users from copying the decrypted EXE. However, since the decryption application runs as the same user, this will be impossible to achieve in a truly secure fashion as both have the same privileges on the system.
You could use Process.WaitForExit:
var process = Process.Start(processPath);
process.WaitForExit();
// File.Delete(processPath); // Not strong enough (thanks to Binary Worrier)
DeleteOrDie(processPath); // Will attempts anything to delete the file.
But it gives the possibility to copy the exe from where you writed it.
A good solution is to run it from memory.
If your target exe is a CLR program, you can use the Assembly.Load function:
// read the file and put data in bin
...
Assembly a = Assembly.Load(bin);
MethodInfo method = a.EntryPoint;
if (method == null) throw new NoEntryPointException();
object o = a.CreateInstance(method.Name);
method.Invoke(o, null);
More details here.
If you want to load/execute any exe in memory, you could use the Nebbett’s Shuttle approach but you will need to code it in C/C++ and make a call to it from C#.
Also it looks like Microsoft doesn't like it (security issues) and I don't think you can achieve it from C# only. Good anti-virus will probably detect it.
In a not very good way but a way that can give you what you want, I suggest this solution:
(I use a Console Application with some input arguments for this solution)
[1: ] Write a function to check opened processes:
/// <summary>
/// Check application is running by the name of its process ("processName").
/// </summary>
static bool IsProcessOpen(string processName)
{
foreach (Processing.Process clsProcess in Processing.Process.GetProcesses())
{
if (clsProcess.ProcessName.ToUpper().Contains(processName.ToUpper()))
return true;
}
return false;
}
[2: ] Define some variables:
static bool IamRunning = true;
static int checkDuration = 1; // in seconds
[3: ] Use a Thread for run a loop for checking:
Thread t = new Thread(delegate() {
DateTime lastCheck = DateTime.MinValue;
while (IamRunning)
{
var now = DateTime.Now;
int dd = (now.Hour - lastCheck.Hour) * 3600 + (now.Minute - lastCheck.Minute) * 60 + now.Second - lastCheck.Second;
if (dd >= checkDuration)
if (!IsProcessOpen("ProcessName"))
{
delApplication(); // You have a function to delete ...
break;
}
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
[4: ] Use a loop at the end of the program:
while (t.ThreadState == ThreadState.Running)
{
// just wait.
}
Note: This solution by Console Application in my low computer have 50% usage of CPU.
I have a windows service that is intended to do the following:
Monitor a folder on the server for PDF files
When file arrives, run a third party exe to convert the PDF to Excel. No text output is generated. The third party tool simply uses the input file path and generates an output excel file. No need for a window launch. No need to track sessions.
Windows service then reads the data from the Excel, processes it, and outputs an xml into a folder.
All this works fine in debug mode. However, when I try to run the windows service on my local machine in release mode (using installutil) as a service (as opposed to in visual studio), it does not work. When I attach-to-process, I notice the cursor just hangs on waitforexit and no excel is generated. Since it works in debug but not in release mode, I suspect it's a permissions issue. Any feedback will be appreciated.
Already tried checking "Allow service to interact with desktop". Didn't help.
EDIT: correction - cursor actually hangs on exeProcess.WaitForExit()
ProcessStartInfo sInfo = new ProcessStartInfo();
sInfo.FileName = ConfigurationManager.AppSettings["FileName"];
sInfo.Arguments = GetArguments();
sInfo.UseShellExecute = false;
sInfo.CreateNoWindow = true;
sInfo.ErrorDialog = false;
sInfo.WindowStyle = ProcessWindowStyle.Hidden;
//sInfo.RedirectStandardError = true; //didn't work
//sInfo.RedirectStandardInput = true; //didn't work
//sInfo.RedirectStandardOutput = true; //didn't work
using (Process exeProcess = Process.Start(sInfo))
{
//StreamWriter inputWriter = exeProcess.StandardInput;
//StreamReader outputReader = exeProcess.StandardOutput;
//StreamReader errorReader = exeProcess.StandardError;
exeProcess.WaitForExit();
}
The problem is almost certainly that steps 2 and 3 do not work in the non-interactive session 0. The big difference is not between debug and release builds. But between running on the interactive desktop, and the service running in session 0.
To get out of this, and continue using a service, you need to make sure that all steps can operate in session 0. Step 2 we know nothing about. Step 3 looks like it involves automating Excel. That is officially not supported and known not to work under session 0. You'll need to read the Excel file using something other than Excel. As for step 2, that depends on the third party tool that converts from PDF to Excel.
I would put into the code proper exception handling and logging to e.g. event log to find out if there is anything different than expected.
Next step may be to configure the service with credentials which you know will have access to executable you try to run, to the directories where you export the files generated etc.
If possible / available then try to use assembly which you can reference instead of running another executable from your code. This way you will have it more under control;)
EDIT:
In general it is possible to run command line tool by windows service. There just need to be clear how the tool expects to get the data, if it is from command line and tool is not waiting for user to enter anything. Do you have problem just with this command line utility or in general?
Service:
private FileSystemWatcher watcher;
public Service1()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
watcher = new FileSystemWatcher();
watcher.Path = #"c:\temp\service";
watcher.Created += watcher_Created;
watcher.EnableRaisingEvents = true;
}
protected override void OnStop()
{
}
private void watcher_Created(object sender, FileSystemEventArgs e)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo(pathToExe, e.Name);
EventLog.WriteEntry(e.Name);
Process process = Process.Start(startInfo);
process.EnableRaisingEvents = true;
}
catch (Exception ex)
{
EventLog.WriteEntry(ex.ToString());
}
}
Command line tool - expects to get name of the file as parameter
public class Program
{
public static void Main(string[] args)
{
File.AppendAllText(pathToLog, string.Format("{0} - File got created in watched folder - doing conversion of {1}\n", DateTime.Now, args[0]));
}
}
Im making a small loader for the game PSOBB but for the offline version, im lazy to go in a folder called "servers" and launch all 3 server executable files, so thats y im making this program. So my problem is that i made a button that launches the .exe from another folder (a sub dir).
private void startLoginServerButton_Click(object sender, EventArgs e)
{
Process[] killLoginServer = Process.GetProcessesByName("login_server");
if (killLoginServer.Length == 0)
{
Process.Start("servers\\login_server.exe");
System.Threading.Thread.Sleep(1000);
refresh(); // This goes to another method and checks if the program is running
}
else if (killLoginServer.Length > 0)
{
foreach (Process stop in killLoginServer)
{
stop.Kill();
}
System.Threading.Thread.Sleep(1000);
refresh();
}
}
it checks if the process is running or not and if its not it launches it. the problem is that when i press the button, it start BUT it doesn't load completely, so then it closes, but if i start the process manually, it works fine. I tried running my loader as Administrator but nothing... i even tried specifying the full path but the problem still there.
your path is wrong..
you must fully qualify that path when starting any process found in that location. see msdn
in your pc : Process.Start("C:\\myprocess.exe");
in other pc : Process.Start("OtherPcNetAddress\\SharedFolder\\myprocess.exe");
I don't think your path is correct. You either need to specify an absolute path like this:
Process.Start(#"C:/servers/login_server.exe");
Or a relative path like this:
Process.Start(Path.Combine(Environment.CurrentDirectory, #"servers/login_server.exe"));
I have this function that i check if the process is started and when its exited.
But im using on a breakpoint on the IF and the List Count is all the time 0.
And when im running the task amanager i see there bf3.exe *32
So whats wrong here ?
private void isProcessRunning()
{
Process[] proclist = Process.GetProcessesByName("bf3.exe");
if (proclist.Length > 0)
{
Logger.Write("Battlefield 3 Started");
alreadyRun = true;
}
else if (alreadyRun == true)
{
Logger.Write("Battlefield 3 Exited");
}
}
From the documentation:
The process name is a friendly name for the process, such as Outlook, that does not include the .exe extension or the path. GetProcessesByName is helpful for getting and manipulating all the processes that are associated with the same executable file. For example, you can pass an executable file name as the processName parameter, in order to shut down all the running instances of that executable file.
Emphasis mine.
Remove the .exe. That appears to work for me.
Good day!
I'm working on installer, which installs additional dependencies for my software using Process.Start.
foreach dependency:
var process = System.Diagnostics.Process.Start(processStartInfo);
process.WaitForExit();
The problem is when another msi installation is runned, WaitForExit hangs (and when I close this another msi installation, WaitForExit also exits).
I can't use timeouts, because dependencies are different with very different installation time.
Is there any ways to handle this situation and correctly kill process (actually I want to know is dependency is installing or just hanging)?
Many thanks.
Solution: in my case the problem is solved by checking if 'msiexec' process is running.
The solution to my problem - check global mutex, created by msiexec. This is also a correct way to check if another msi installation is running.
public static bool WaitAnotherMsiInstallation(int timeout)
{
const string MsiMutexName = "Global\\_MSIExecute";
try
{
using (var msiMutex = Mutex.OpenExisting(MsiMutexName, MutexRights.Synchronize))
{
return msiMutex.WaitOne(timeout);
}
}
catch (WaitHandleCannotBeOpenedException)
{
// The named mutex does not exist.
return true;
}
catch (ObjectDisposedException)
{
// Mutex was disposed between opening it and attempting to wait on it
return true;
}
}
Here is some details http://msdn.microsoft.com/en-us/library/aa372909(VS.85).aspx