InvokeSystems.RestrictedTask: The Delegation Problem

(or: why “just make them local admin” is never the answer)

Every Windows environment eventually faces the same problem. Help desk staff need to restart services. Developers need to clear DNS cache. Support teams need to kill stuck processes. All of these require administrator privileges.

The wrong answer is adding users to the local Administrators group. That grants far too much access. One bad click and they’re deleting production data, installing malware, or disabling security controls.

The less-wrong answer is creating custom scheduled tasks that run as SYSTEM and restricting who can trigger them. This works, but it’s tedious to set up correctly, easy to misconfigure, and provides no payload verification. Users can modify the script files, and the task will happily execute the altered code with full system privileges.

What you actually want is a framework that makes delegation safe: users can run specific pre-approved actions, payload integrity is verified before execution, and every invocation is logged for audit purposes.

InvokeSystems.RestrictedTask is that framework. Administrators define tasks and specify who can run them. Users trigger tasks through a simple interface. Hash verification ensures the payload hasn’t been tampered with. Event logs record every execution.

How It Works

The security model relies on Windows Task Scheduler’s built-in security. Each task runs as SYSTEM, has a trigger set to “manual start,” and specifies which security principals (users or groups) are allowed to start it.

When an authorized user runs Invoke-InvTask -TaskName 'Restart-Spooler', the command invokes the scheduled task. Task Scheduler validates that the user has permission to start this specific task. If authorized, it executes the payload script as SYSTEM. If not, it denies execution.

Before the payload runs, a verification script checks the payload’s SHA256 hash against a stored reference hash. If they match, execution proceeds. If they differ, execution aborts and a security event is logged.

This provides three layers of protection: Windows Task Scheduler access controls determine who can run what, file ACLs prevent non-administrators from modifying task files, and hash verification detects tampering even if ACLs are somehow bypassed.

Creating a Restricted Task

Administrators create tasks using New-InvTask:

New-InvTask -TaskName 'Restart-Spooler' `
            -AllowedPrincipals 'DOMAIN\HelpDesk' `
            -PayloadSourcePath 'C:\Scripts\RestartService.ps1'

This creates the scheduled task, copies the payload script to a protected location, generates the verification hash, sets file ACLs, and optionally initializes event log sources.

The function supports templates for common operations. Want to let users restart specific services? Use the Restart-Service template. Need controlled system reboots? Use Restart-Computer. Templates handle the boilerplate, you customize the specifics.

After creation, if you need to modify the payload, edit it in place and run Update-InvTask -TaskName 'Restart-Spooler'. This regenerates the hash, ensuring verification doesn’t break after legitimate changes.

Running a Task as a User

Non-admin users run tasks through Invoke-InvTask:

Invoke-InvTask -TaskName 'Restart-Spooler'

The command starts the task, waits for completion, retrieves the execution log, and displays the output. Users see what happened, including any errors, without needing direct access to task scheduler or log files.

By default, the last 50 lines of the log are displayed. For verbose debugging, use -TailLines -1 to show everything. For long-running tasks, adjust -TimeoutSeconds to prevent premature timeouts.

The interface is deliberately simple. Users don’t need to understand scheduled tasks, hash verification, or file permissions. They run a command, see the results, and get on with their work.

The Hash Verification Ward

Every task includes a verification script that runs before the payload. This script reads the payload file, computes its SHA256 hash, and compares it to the reference hash stored alongside the payload.

If the hashes match, the verification script exits with code 0, and Task Scheduler proceeds to run the payload. If they differ, the verification script logs a security event (Event ID 9001) and exits with a failure code, aborting execution.

This detects both accidental modifications (someone editing the wrong file) and malicious tampering. Even if an attacker gains write access to the payload directory, they can’t inject malicious code without the hash verification catching it.

SHA256 is more than adequate for this purpose. We’re verifying file integrity, not securing military communications. The hash is stored alongside the file in a protected directory. An attacker with write access to that directory could modify both payload and hash, but at that point they already have admin access and don’t need to compromise scheduled tasks.

The goal is defense in depth, not cryptographic perfection. ACLs prevent casual tampering. Hash verification catches tampering that bypasses ACLs. Audit logging provides a record if both fail.

Event Logging and Audit Trails

Every task execution writes events to the Application event log with source InvokeSystems-Task. Events include:

  • 1000: Task started (who, what, when)
  • 1001: Task completed successfully (duration, exit code)
  • 1002: Task completed with failures (duration, exit code, error summary)
  • 9001: Hash verification failed (security alert)

This provides an audit trail for compliance, troubleshooting, and security monitoring. SIEM systems can ingest these events. Compliance audits can query them. Incident response can review them.

The payload script also writes to a dedicated log file in C:\Program Files\InvokeSystems\Logs\. This captures detailed output separate from Windows Event Log, allowing verbose logging without flooding the event system.

Managing Task Principals

After creating a task, you can modify who has access using Set-InvTaskPrincipal:

# Add a user
Set-InvTaskPrincipal -TaskName 'Restart-Spooler' -Add 'DOMAIN\NewTech'

# Remove a user
Set-InvTaskPrincipal -TaskName 'Restart-Spooler' -Remove 'DOMAIN\FormerEmployee'

# Replace all principals
Set-InvTaskPrincipal -TaskName 'Restart-Spooler' -Replace @('DOMAIN\CurrentTeam')

View current principals with Get-InvTaskPrincipal -TaskName 'Restart-Spooler'.

This lets you adjust access control without recreating tasks or manually editing task scheduler XML exports.

Templates: Don’t Reinvent the Wheel

The module includes templates for common operations in the Templates/ directory:

  • Restart-Service.ps1: Restart one or more Windows services
  • Restart-Computer.ps1: Initiate system restart with countdown
  • Clear-TempFiles.ps1: Clean system temp directories
  • Flush-DnsCache.ps1: Clear DNS resolver cache
  • Stop-Process.ps1: Kill stuck or unresponsive processes
  • Run-Executable.ps1: Run any program or installer as SYSTEM

Deploy a template, customize the parameters, update the hash. You have a working restricted task without writing PowerShell from scratch.

Templates demonstrate best practices: structured logging, error handling, parameter validation, clear output messages. Use them as-is or as a starting point for custom tasks.

What This Isn’t

InvokeSystems.RestrictedTask doesn’t replace proper identity and access management. It’s a delegation mechanism for specific, well-defined tasks. If you need role-based access control across an entire application, use RBAC properly.

It doesn’t provide fine-grained privilege restrictions within payload scripts. Once a script runs as SYSTEM, it has full system privileges. The restriction is which scripts users can trigger, not what those scripts can do.

It doesn’t hide the fact that tasks run as SYSTEM. Users authorized to start a task can examine its payload (read-only access). Security through obscurity isn’t the model. Security through explicit access control and verified payloads is.

It doesn’t replace configuration management or deployment pipelines. This is for operational tasks that need to run on-demand, not for automated configuration enforcement.

Tradeoffs and Limitations

Hash verification prevents tampering but doesn’t prevent legitimate modifications from breaking tasks. If you edit a payload and forget to run Update-InvTask, the next execution fails. Clear error messages make this recoverable, but it’s still an operational dependency.

Event log configuration requires administrator privileges. If the module can’t initialize event sources, event logging is disabled with a warning. Tasks still work, but you lose audit trail. Plan accordingly.

Task names are restricted to alphanumeric characters, spaces, and hyphens. This simplifies file system paths and task scheduler naming but prevents Unicode or special characters. That’s fine for English environments, potentially limiting elsewhere.

Payload logs grow unbounded. Implement log rotation externally if disk space is constrained. The module prioritizes simplicity over built-in log management.

Why This Exists

The help desk ticket pattern is predictable. User reports a problem. Help desk identifies the fix (restart a service, clear a cache, kill a process). Help desk escalates to someone with admin rights. Admin performs the action. Ticket closes.

This wastes time, introduces delays, and frustrates everyone. The fix is trivial. The bureaucracy is not.

InvokeSystems.RestrictedTask breaks that pattern. Define the fix once as a restricted task. Grant help desk access. They apply the fix directly. No escalation, no delays, no wasted admin time.

This isn’t novel. Unix has sudo with fine-grained rules. Windows has scheduled tasks with security principals. The module just makes the pattern accessible, repeatable, and safe.

Where This Fits

Use RestrictedTask for operational actions that non-admin users need to perform regularly: service restarts, cache clearing, process management, controlled reboots, log collection.

Don’t use it for one-off administrative tasks that truly require full admin privileges. Don’t use it as a substitute for proper automation that should run unattended.

Use it to delegate specific, bounded actions to specific teams in a way that’s auditable and verifiable.

Pair it with InvokeSystems.Generator to automate password resets with secure credential generation. Pair it with deployment pipelines to provide self-service operational tasks in production environments.

The Real Point

Security and usability are often in tension. Locking everything down frustrates users. Granting broad access creates risk. The goal is finding the narrow path between them.

InvokeSystems.RestrictedTask is that narrow path for task delegation. Users get the access they need. Administrators retain control. Audit logs provide accountability. Hash verification ensures integrity.

It’s not perfect. No delegation mechanism is. But it’s better than making everyone a local admin or routing every trivial operational task through a ticketing system.

That’s the entire value proposition: controlled delegation that doesn’t make you regret it later.