Running a Windows Scheduled Task from ASP.net - c#

I have a Windows scheduled task that runs a database import process every hour, but I'd like users to be able to kick it off out-of-schedule by hitting a button in an ASP.net dashboard (running in IIS6 on Windows Server 2003).
The following works perfectly in code-behind ...
var proc = new Process
{
StartInfo =
{
UseShellExecute = false,
FileName = #"C:\Windows\System32\schtasks.exe",
Arguments = "/run /tn Loader",
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
proc.WaitForExit();
... but only if the application pool identity is set to Local System (not ideal!). If I leave it as Network Service, the task does not start. So it is presumably a permissions issue.
Is there a way ASP.net can kick off a scheduled task on the server without running the application as Local System? If not, what good alternatives are there?
Update: if nobody on SO knows, I guess it is not possible so I will go with my idea of having my web application write requests to a database table (doubling as an audit log) and creating a second task to poll that table and kick off the main task.

Update your schedule task to trigger off a specific event. Then have your website log that event when the button is clicked - thus starting your scheduled task.
Ex:
In my installer I create an event log source for my program, since creating the source requires administrative privileges (you can also use the command line to create the source)
if (EventLog.SourceExists("MyApp"))
{
EventLog.CreateEventSource("MyApp", "Application");
}
Then in my application, I create an event log entry when the button is clicked.
private void btnOpenOtherApp_Click (object sender, EventArgs e)
{
try
{
var log = new EventLog
{
Source = "MyApp"
};
log.WriteEntry("Start MyOtherApp", EventLogEntryType.Information, 1337);
}
catch (Exception ex)
{
...
}
}
And the task scheduler set to open MyOtherApp when the event is logged.

You need an administrator user in windows. This code will help you to call the task:
var securePass = new System.Security.SecureString();
foreach (char c in "my_password")
{
pass.AppendChar(c);
}
var proc = new Process
{
StartInfo =
{
UseShellExecute = false,
FileName = #"C:\Windows\System32\schtasks.exe",
Arguments = "/run /tn Loader",
UserName = "myAdminUser", //NEW
Password = securePass, //NEW
RedirectStandardError = true,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};
proc.Start();
proc.WaitForExit();
If your application needs to run as SYSTEM Account you can use this arguments:
If the /RU username and /RP Password parameters match the currently
logged-in user, the task will run interactively (visible in the
foreground).
For the system account, /RU username can be written as "", "NT
AUTHORITY\SYSTEM" or "SYSTEM", a Password is not required. The system
account has full access to the local machine but has no permissions on
any other machines (or mapped drives) across the Network.
Source: http://ss64.com/nt/schtasks.html

You can utilize the cache of ASP.NET to run schedule tasks. I personally used this and it worked like charm. Ofcourse it has some limitation (Accuracy) but you can adjust/change.
https://blog.stackoverflow.com/2008/07/easy-background-tasks-in-aspnet/
Regards

If using a 3rd party scheduler service such as Quartz.NET is not an option, and changing permissions is not allowed either (not a good idea in any case) another possibility is to to write a helper Windows service as a bridge between ASP.NET application and Windows scheduler. You would store list of tasks in a DB and a sequence of events would be something like this:
When you need to run a task, from ASP.NET GUI you set flag in the DB for that task
Helper service runs on schedule to check the DB at given intervals - sees the flag, starts the task, reset the flag.
Oh and there're libraries out there (e.g. http://taskscheduler.codeplex.com/ and others) that wrap Window Task Scheduler, so you don't have to execute them directly, but rather in a nice managed way.

Related

Git for windows seems to create sub-processes that never die

I use the following code to call git from C#:
var pInfo = new ProcessStartInfo
{
FileName = "git",
Arguments = "checkout master",
UseShellExecute = false
};
using var p = Process.Start(pInfo);
Console.WriteLine(p.Id);
p.WaitForExit();
The process ID printed is, lets say 3709. When my program ends, I look at the Task Manager and I see a git.exe process still running with a different ID, say 8865. This process remains running indefinitely.
It seems to me that the git process started by my program spawns a second git process that never exits. Is there any way for me to prevent this behavior? As it is, every time I run my program, there is yet another git.exe process left behind indefinitely, adding up to a lot over time, until I reboot.
(Alternatively, is there any other way to invoke git other than by running the executable?)

Windows task scheduler created programmatically in C# not running automatically

I have created windows task scheduler programmatically in c#. Task is created successfully and is scheduled to run correctly. At scheduled time, it says task is running but without any result and next schedule time is updated.
But last run time and last run result does not update.
Last run result is: The task has not yet run.(0x41303)
But when run manually from task scheduler it executes successfully but not automatically.
Below code that i used to create task
var ts = new TaskService();
var td = ts.NewTask();
td.RegistrationInfo.Author = "My company";
td.RegistrationInfo.Description = "Runs test application";
var trigger = new WeeklyTrigger { StartBoundary = startDate, DaysOfWeek = daysOfWeek, Enabled = enabled };
trigger.Repetition.Interval = TimeSpan.FromSeconds(((minutes == 0) ? 60 : minutes) * 60);
td.Triggers.Add(trigger);
var action = new ExecAction(Assembly.GetExecutingAssembly().Location, null, null);
if (filePath != string.Empty && File.Exists(filePath))
{
action = new ExecAction(filePath);
}
action.Arguments = "AutoRun";
td.Actions.Add(action);
ts.RootFolder.RegisterTaskDefinition(TaskName, td);
Any help would be much appreciated!
Check the execution privileges first.
Then check the task manager if the process is really running when it seems 'running'. If yes, try to use some try-catch blocks and create event logs as exceptions.
I think when you run manually from task scheduler, its executed by a user that belongs to task scheduler (maybe administrator). But at scheduled time, application trying to be executed as a user that won't have enough privileges to do some stuff in your code.
UPDATE
Set Start in (optional) value to target file location. Without it,
the task scheduler runs in system32 folder but like i said before,
target application wouldn't have privileges to run in system32.
Try to change the version of the console application to 32 bit.
i.e. Right click Goto -> Properties -> Build -> Platform Target
= x86.
Turns out, for running any task in scheduler, laptop charger must be plugged in else scheduler does not execute the task.
This is not the case with windows server or desktop systems.
Not sure about this behavior but this is what i figured it out.
For me the issue was the executable crashing with "Application Error". You can't see any error in Task scheduler. It will just show last run result as "The task has not yet run.(0x41303)"
TO get the error please check Event Viewer
EventViewer -> Windows Logs -> Aplication

How to change name of console app as displayed in task manager?

I am programmatically launching console applications. They all appear as conhost.exe in Task Manager.
How can I set a process name such that it appears as such in the task manager?
Example of how I'm setting up my process:
public static Process CreateMultiCoreExeProcess(int coreIndex, int coresToUse, string executableFilepath)
{
//* Create your Process
Process process = new Process
{
StartInfo =
{
FileName = executableFilepath,
Arguments = coreIndex + " " + coresToUse,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
},
EnableRaisingEvents = true
};
return process;
}
...as you can see there isn't an attribute that sets the process name (that I know of). I would like to be able to set the name of the process at the time that I create it.
DUPLICATE QUESTION REMARK: this is not a duplicate as attempted. The solution presented as a duplicate is for a STATIC name for the process, being set within project properties. I am running my process DYNAMICALLY (at run time) and would like to set the process name at that time.
UPDATE: I am convinced that this isn't possible. At least not in a realistic (i.e. permanent) way. I'm fairly certain we are "stuck" with the name as provided in the assembly information and it can't be programmatically changed in the Task Manager. This is unfortunate for my application, which is to spawn 10+ identical process executables. Sometimes I want to kill just one of them, but I can't determine which-is-which in the task manager because they all have the same name.
Use the line:
Console.Title = "My Console Application";
to set the console name
And to set the name/description in task manager, it's been answered before quite a few times. Check this link How can I set the Task Manager description for my program?
You can use P/Invoke to change the process name after creating it. (As you cannot specify it when you create it. The default name is the name specified in the program.)
[DllImport("user32.dll")]
static extern int SetWindowText(IntPtr hWnd, string text);
...
SetWindowText(process.MainWindowHandle, "Process Name/Title");
Edit: Looking around on the net, it appears you may need to wait 100ms or so before setting the name (either through Thread.Sleep or process.WaitForInputIdle in the case of a GUI application).

Creating a Cross-Process EventWaitHandle

I have two windows application, one is a windows service which create EventWaitHandle and wait for it. Second application is a windows gui which open it by calling EventWaitHandle.OpenExisting() and try to Set the event. But I am getting an exception in OpenExisting. The Exception is "Access to the path is denied".
windows Service code
EventWaitHandle wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MyEventName");
wh.WaitOne();
Windows GUI code
try
{
EventWaitHandle wh = EventWaitHandle.OpenExisting("MyEventName");
wh.Set();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
I tried the same code with two sample console application, it was working fine.
You need to use the version of the EventWaitHandle constructor that takes an EventWaitHandleSecurity instance. For example, the following code should work (it's not tested, but hopefully will get you started):
// create a rule that allows anybody in the "Users" group to synchronise with us
var users = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
var rule = new EventWaitHandleAccessRule(users, EventWaitHandleRights.Synchronize | EventWaitHandleRights.Modify,
AccessControlType.Allow);
var security = new EventWaitHandleSecurity();
security.AddAccessRule(rule);
bool created;
var wh = new EventWaitHandle(false, EventResetMode.AutoReset, "MyEventName", out created, security);
...
Also, if you're running on Vista or later, you need to create the event in the global namespace (that is, prefix the name with "Global\"). You'd also have to do this on Windows XP if you use the "Fast User Switching" feature.
This might be caused by the service process running at an elevated privilege level, but the GUI process is not. If you put the same code into two console apps, they'll both be running at user level and won't have any trouble accessing each other's named shared objects.
Try running the GUI app with the "Run as administrator" flag from the Windows start menu. If that solves the issue, you need to read up on how to request elevation within your code. (I haven't done that)

programmatically kill a process in vista/windows 7 in C#

I want to kill a process programmatically in vista/windows 7 (I'm not sure if there's significant problems in the implementation of the UAC between the two to make a difference).
Right now, my code looks like:
if(killProcess){
System.Diagnostics.Process[] process = System.Diagnostics.Process.GetProcessesByName("MyProcessName");
// Before starting the new process make sure no other MyProcessName is running.
foreach (System.Diagnostics.Process p in process)
{
p.Kill();
}
myProcess = System.Diagnostics.Process.Start(psi);
}
I have to do this because I need to make sure that if the user crashes the program or exits abruptly, this secondary process is restarted when the application is restarted, or if the user wants to change the parameters for this secondary process.
The code works fine in XP, but fails in Windows 7 (and I assume in Vista) with an 'access is denied' message. From what the Almighty Google has told me, I need to run my killing program as administrator to get around this problem, but that's just weak sauce. The other potential answer is to use LinkDemand, but I don't understand the msdn page for LinkDemand as it pertains to processes.
I could move the code into a thread, but that has a whole host of other difficulties inherent to it that I really don't want to discover.
You are correct in that it's because you don't have administrative priveleges. You can solve this by installing a service under the local system user and running a custom command against it as needed.
In your windows form app:
private enum SimpleServiceCustomCommands { KillProcess = 128 };
ServiceControllerPermission scp = new ServiceControllerPermission(ServiceControllerPermissionAccess.Control, Environment.MachineName, "SERVICE_NAME");
scp.Assert();
System.ServiceProcess.ServiceController serviceCon = new System.ServiceProcess.ServiceController("SERVICE_NAME", Environment.MachineName);
serviceCon.ExecuteCommand((int)SimpleServiceCustomCommands.KillProcess);
myProcess = System.Diagnostics.Process.Start(psi);
In your service:
private enum SimpleServiceCustomCommands { KillProcess = 128 };
protected override void OnCustomCommand(int command)
{
switch (command)
{
case (int)SimpleServiceCustomCommands.KillProcess:
if(killProcess)
{
System.Diagnostics.Process[] process = System.Diagnostics.Process.GetProcessesByName("MyProcessName");
// Before starting the new process make sure no other MyProcessName is running.
foreach (System.Diagnostics.Process p in process)
{
p.Kill();
}
}
break;
default:
break;
}
}
I'll add the code for Simon Buchan's suggestion. It makes sense and should work as well, assuming your windows form is what launched the process in the first place.
Here's where you create the process. Notice the variable myProc. That's your handle on it:
System.Diagnostics.Process myProc = new System.Diagnostics.Process();
myProc.EnableRaisingEvents=false;
myProc.StartInfo.FileName="PATH_TO_EXE";
myProc.Start();
Later, just kill it with:
myProc.Kill();

Categories

Resources