I recently faced an interesting challenge at work: I needed to create a web interface that would allow authorized users to trigger a Windows scheduled task with a simple button click. The task in question was a SARC (Secure Archive Request Consolidation) monitoring process that checks for new folders, processes files, uploads them to OneDrive, and sends email notifications.
What started as a "simple" request turned into a journey through IIS authentication, Windows security, permission escalation, and the intricacies of running scheduled tasks from a web context. Here's how I built a secure, user-friendly ASP.NET web interface that can trigger any Windows scheduled task.
The Scope
This solution creates a web interface that:
- Authenticates users against Active Directory
- Only allows specifically authorized users to access the interface
- Triggers Windows scheduled tasks on demand
- Provides visual feedback on task execution
- Logs all user actions for audit purposes
- Prevents rapid repeated executions with a cooldown timer
- Works with any existing Windows scheduled task
The Journey: From Concept to Working Solution
Day 1: The Initial Attempt
I started with what I thought would be straightforward - create an ASP.NET page that runs schtasks.exe:
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "schtasks.exe";
psi.Arguments = "/run /tn \"\\SARC-Auto\\SARC-Task\"";
Process.Start(psi);
Result: Access denied.
Day 2: The Authentication Maze
I discovered that IIS application pools run with limited privileges. Even after setting up Windows Authentication, the default ApplicationPoolIdentity couldn't see or trigger scheduled tasks. I tried various approaches:
- File permissions - Didn't help, as
schtasks.exerequires actual administrator privileges - PowerShell alternatives - Same permission issues
- COM API - Still needed elevation
Day 3: The Service Account Solution
I realized I needed a dedicated service account with appropriate permissions. Here's what finally worked:
- Created a local service account (
TaskProcess) - Added it to the Administrators group (required for triggering scheduled tasks)
- Configured the IIS application pool to run as this account
- The scheduled task itself continues to run as the original user
This separation is important: the service account only has permission to trigger the task, not to modify it or access the resources the task uses.
The ASP.NET Page
Here's the final working code that handles authentication, authorization, and task execution:
<%@ Page Language="C#" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.Collections.Generic" %>
<!DOCTYPE html>
<script runat="server">
// Authorized users list - case insensitive matching
private List<string> AuthorizedUsers = new List<string>
{
@"domain\user1",
@"domain\user2",
@"domain\admin1"
};
protected void Page_Load(object sender, EventArgs e)
{
string currentUser = User.Identity.Name;
// Case-insensitive authorization check
bool isAuthorized = false;
foreach(string user in AuthorizedUsers)
{
if (string.Equals(currentUser, user, StringComparison.OrdinalIgnoreCase))
{
isAuthorized = true;
break;
}
}
if (!isAuthorized)
{
MainPanel.Visible = false;
DeniedPanel.Visible = true;
DeniedUser.InnerText = currentUser;
}
else
{
MainPanel.Visible = true;
DeniedPanel.Visible = false;
CurrentUser.InnerText = currentUser;
}
}
protected void RunTaskButton_Click(object sender, EventArgs e)
{
try
{
// Run the scheduled task
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "schtasks.exe";
psi.Arguments = "/run /tn \"\\YourFolder\\YourTaskName\"";
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
psi.CreateNoWindow = true;
Process process = Process.Start(psi);
string output = process.StandardOutput.ReadToEnd();
string errors = process.StandardError.ReadToEnd();
process.WaitForExit();
if (process.ExitCode == 0)
{
Result.InnerHtml = "<div id='successMsg' class='success-message'>" +
"<strong>✓ Task Running</strong><br/>" +
"The scheduled task has been triggered successfully." +
"</div>";
LogTaskRun(User.Identity.Name, true, "Task started successfully");
}
else
{
Result.InnerHtml = "<div class='error-message'>" +
"<strong>Failed to start task</strong><br/>" +
Server.HtmlEncode(errors) + "</div>";
LogTaskRun(User.Identity.Name, false, errors);
}
}
catch (Exception ex)
{
Result.InnerHtml = "<div class='error-message'>" +
"<strong>Error:</strong> " + Server.HtmlEncode(ex.Message) + "</div>";
LogTaskRun(User.Identity.Name, false, ex.Message);
}
}
private void LogTaskRun(string user, bool success, string message)
{
try
{
string logPath = Server.MapPath("~/App_Data");
if (!Directory.Exists(logPath))
Directory.CreateDirectory(logPath);
string logFile = Path.Combine(logPath, "TaskRuns.log");
string logEntry = string.Format("{0} | {1} | {2} | {3}{4}",
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
user,
success ? "SUCCESS" : "FAILED",
message,
Environment.NewLine);
File.AppendAllText(logFile, logEntry);
}
catch
{
// Silent fail on logging
}
}
</script>
The User Interface
The interface is clean and professional, with clear visual feedback:
<style>
.container {
background: white;
border-radius: 10px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
padding: 40px;
max-width: 500px;
margin: 0 auto;
text-align: center;
}
.run-button {
background: #28a745;
color: white;
border: none;
padding: 18px 50px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
border-radius: 8px;
transition: all 0.3s ease;
}
.success-message {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}
.error-message {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}
</style>
The JavaScript Enhancement
To prevent users from repeatedly clicking the button (and potentially overwhelming the system), I added a 120-second cooldown timer:
var countdownTimer;
var remainingSeconds = 120;
function startButtonCooldown() {
var btn = document.getElementById('RunTaskButton');
btn.disabled = true;
btn.value = 'Please Wait...';
remainingSeconds = 120;
updateCountdown();
countdownTimer = setInterval(updateCountdown, 1000);
}
function updateCountdown() {
remainingSeconds--;
var btn = document.getElementById('RunTaskButton');
var countdownDiv = document.getElementById('countdown');
if (remainingSeconds > 0) {
countdownDiv.innerHTML = 'Button will be available in ' + remainingSeconds + ' seconds';
} else {
btn.disabled = false;
btn.value = 'Start Task';
countdownDiv.innerHTML = '';
clearInterval(countdownTimer);
}
}
// Auto-hide success message after 5 seconds
window.onload = function() {
var successMsg = document.getElementById('successMsg');
if (successMsg) {
setTimeout(function() {
successMsg.style.display = 'none';
}, 5000);
startButtonCooldown();
}
}
Setup Guide
Step 1: Create a Service Account
Open PowerShell as Administrator and run:
# Create local service account
$Password = ConvertTo-SecureString "YourSecurePassword123!" -AsPlainText -Force
New-LocalUser -Name "TaskRunnerService" -Password $Password -Description "Service account for task runner"
# Add to necessary groups
Add-LocalGroupMember -Group "Users" -Member "TaskRunnerService"
Add-LocalGroupMember -Group "IIS_IUSRS" -Member "TaskRunnerService"
Add-LocalGroupMember -Group "Administrators" -Member "TaskRunnerService"
# Set password to never expire
Set-LocalUser -Name "TaskRunnerService" -PasswordNeverExpires $true
Step 2: Configure IIS
- Install IIS with ASP.NET support:
Install-WindowsFeature -Name Web-Server -IncludeManagementTools
Install-WindowsFeature -Name Web-Asp-Net45
Install-WindowsFeature -Name Web-Windows-Auth
Create a new Application Pool:
- Open IIS Manager
- Right-click Application Pools → Add Application Pool
- Name:
TaskRunnerPool - .NET CLR Version:
v4.0 - Managed Pipeline Mode:
Integrated
Set Application Pool Identity:
- Select
TaskRunnerPool→ Advanced Settings - Identity → Custom Account
- Username:
.\TaskRunnerService - Password:
YourSecurePassword123!
- Select
Step 3: Deploy the Website
- Create website directory:
New-Item -Path "C:\inetpub\wwwroot\TaskRunner" -ItemType Directory
Copy your ASPX file to this directory
Create App_Data folder for logs:
New-Item -Path "C:\inetpub\wwwroot\TaskRunner\App_Data" -ItemType Directory
- Set permissions:
# Grant IIS permissionsicacls "C:\inetpub\wwwroot\TaskRunner" /grant "IIS_IUSRS:(OI)(CI)R"icacls "C:\inetpub\wwwroot\TaskRunner\App_Data" /grant "IIS AppPool\TaskRunnerPool:(OI)(CI)M"
Step 4: Create IIS Website
- In IIS Manager, right-click Sites → Add Website
- Configure:
- Site name:
TaskRunner - Application pool:
TaskRunnerPool - Physical path:
C:\inetpub\wwwroot\TaskRunner - Port:
8080(or your preference)
- Site name:
Step 5: Configure Authentication
- Select the TaskRunner site in IIS
- Double-click Authentication
- Set:
- Anonymous Authentication: Disabled
- Windows Authentication: Enabled
Step 6: Create Web.config
Create a Web.config file in the website directory:
<?xml version="1.0"?>
<configuration>
<system.web>
<authentication mode="Windows" />
<authorization>
<deny users="?" />
<allow users="*" />
</authorization>
<compilation debug="false" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.webServer>
<security>
<authentication>
<windowsAuthentication enabled="true" />
<anonymousAuthentication enabled="false" />
</authentication>
</security>
</system.webServer>
</configuration>
Step 7: Update the ASPX File
Modify the ASPX file to:
- Add your authorized users to the list
- Update the task path to match your scheduled task
- Customize any messages or styling
Conclusion
What started as a simple request to "add a button that runs a task" turned into a comprehensive solution that addresses security, usability, and maintainability. The journey taught me valuable lessons about Windows security, IIS configuration, and the importance of user experience in administrative tools.
The final solution provides a secure, user-friendly way to trigger Windows scheduled tasks from a web interface. It's been running successfully in production, saving time and reducing the need for server access while maintaining security and audit compliance.