Most persistence libraries for scheduled tasks either drag in NuGet dependencies, require elevated registration, or leave behind task names that look like they came from a red team checklist.

TaskSchedulerPersist takes the opposite approach. It is a .NET Framework 2.0 library that talks to the Windows Task Scheduler through raw COM interop — every interface declared inline with the correct GUIDs and marshaling attributes, no Microsoft.Win32.TaskScheduler package required. Task names derive from the machine GUID under HKLM\SOFTWARE\Microsoft\Cryptography, so each host gets a unique, bland identifier prefixed with _.

The trigger model is dual: user logon and session unlock, each with a configurable five-minute delay scoped to the current user’s SID. The action runs under the interactive token — no admin required to register. A static constructor calls the registration logic on assembly load, so the library can activate through Assembly.Load without a conventional entry point. Release builds emit placeholder tokens for operator-defined payloads; a post-build step writes the compiled DLL as Base64 for transport.

The design target is T1053.005-style persistence that fits older .NET runtimes and corporate Windows estates where Framework 2.0 is still present and third-party task libraries are not an option.

Built for authorized red team operations, penetration testing, and security research with written permission.

cat redacted-results.log sanitized logs

Task registration trace

redacted build output

Release build encodes the assembly to Base64 for transport; task name derives from the machine GUID.

sanitized-output
machine_guid: [REDACTED]
task_name: _[REDACTED]
triggers:
  - type: Logon
    delay: PT5M
    sid: S-1-5-21-[REDACTED]
  - type: SessionStateChange (unlock)
    delay: PT5M
    sid: S-1-5-21-[REDACTED]
action:
  executable: [REDACTED]
  arguments: [REDACTED]
register: TASK_CREATE_OR_UPDATE
status: ok
artifact: TaskSchedulerPersist.b64.txt

COM interop path

redacted execution flow

Task creation runs from a static constructor — assembly load is enough to register persistence.

sanitized-output
entry: static constructor
com: ITaskService (CLSID 0F87369F-[REDACTED])
connect: local Task Scheduler service
logon_type: interactive token
privileges_required: standard user
network_required: true
multiple_instances: ignore new
execution_time_limit: none