PowerShell

Your PowerShell scripts, with the scheduling they never had.

Dagu runs PowerShell, pwsh, and cmd.exe as ordinary workflow steps, so existing scripts keep their shape while gaining order, retries, per-step logs, and a run history you can open in a browser.

PowerShell, pwsh, and cmd.exe in the same workflow
Shell chosen per workflow or per step
Output from one script feeds the next
Runs as a Windows service
01

Pick the shell once, or per step

A workflow declares its shell and every step inherits it. Without one, Dagu prefers PowerShell, then pwsh, then cmd.exe, so a workflow written on one machine behaves predictably on another.

  • The shell accepts a string or an array; the array form avoids quoting problems with arguments like -NoProfile.
  • A step captures its output into a named variable, which later steps and preconditions can read.
  • Preconditions gate a step on that value, so a restart only happens when the service is actually stopped.
Check a service and restart it only if needed
# service-health.yaml
schedule: "*/15 * * * *"
shell: powershell -NoProfile

steps:
  - id: check_service
    run: |
      (Get-Service -Name 'MyAppSvc').Status
    output: SVC_STATUS

  - id: restart_if_stopped
    run: Restart-Service -Name 'MyAppSvc'
    depends: check_service
    preconditions:
      - condition: "${SVC_STATUS}"
        expected: "Stopped"
    retry_policy:
      limit: 2
      interval_sec: 30

mail_on:
  failure: true
02

PowerShell and cmd.exe in one workflow

Legacy batch files rarely get rewritten just because a scheduler changed. A step can override the workflow shell, so a mostly-PowerShell workflow can still call the .bat file that has worked for a decade.

  • Override the shell on a step with a with.shell entry rather than a bare shell field, which the validator rejects alongside run.
  • For an exact argument list with no shell parsing at all, use a structured exec step with command and args.
  • Mixed workflows keep one schedule, one history, and one notification path regardless of which shell each step used.

Windows paths contain backslashes, so prefer block scalars or single quotes in YAML. In a double-quoted YAML string a sequence such as a backslash followed by r is an escape, not a path separator.

PowerShell, cmd, and a direct exec in one file
# maintenance.yaml
schedule: "0 3 * * *"
shell: ["powershell", "-NoProfile"]

steps:
  - id: report_disk
    run: Get-PSDrive -PSProvider FileSystem | Out-String
    output: DISK_REPORT

  - id: legacy_job
    run: |
      @echo off
      call C:\ops\legacy\run.bat
    with:
      shell: cmd
    depends: report_disk

  - id: direct_exec
    action: exec
    with:
      command: C:\Windows\System32\cmd.exe
      args:
        - /c
        - echo
        - done
    depends: legacy_job

mail_on:
  failure: true
03

Multi-line scripts stay readable

A step can hold a full script block rather than a single line, which keeps pipelines and filters in the form a PowerShell author would write them.

  • Block scalars preserve line breaks, so pipelines split across lines survive intact.
  • retry_policy applies to the whole step, which suits scripts that touch flaky network paths or busy files.
  • pwsh and Windows PowerShell are selected by name, so a host with both can run each workflow on the intended one.
A log archival script on a schedule
# archive-logs.yaml
schedule: "30 1 * * *"
shell: pwsh -NoProfile

steps:
  - id: archive
    run: |
      $cutoff = (Get-Date).AddDays(-30)
      Get-ChildItem -Path 'D:\logs' -Filter *.log |
        Where-Object { $_.LastWriteTime -lt $cutoff } |
        Compress-Archive -DestinationPath 'D:\archive\logs.zip' -Update
    retry_policy:
      limit: 2
      interval_sec: 60

  - id: verify
    run: Test-Path 'D:\archive\logs.zip'
    depends: archive

mail_on:
  failure: true
04

Where it runs from

Scheduling is only useful if the scheduler is running. The Windows installer can register Dagu as a service, so workflows start with the machine rather than with a logged-in session.

  • The PowerShell installer registers a Windows service through a version-pinned WinSW wrapper, verifiable with Get-Service.
  • Windows builds are published for amd64, 386, and arm64.
  • The web console shows run history and per-step output from any browser, so checking a job does not require a remote desktop session.

Windows Server 2019 and later is the safe target. On older builds without AF_UNIX support the workflows still run, but the live status and stop-control socket is unavailable and Dagu logs a warning.

FAQ

Practical questions before adopting

Do I have to rewrite my scripts as YAML?

No. The YAML describes when a script runs, what it depends on, and what happens when it fails. The script itself stays a .ps1 file called the same way Task Scheduler called it.

How do I pass a value from one script to the next?

Give the producing step an output name and reference it in later steps. The same reference works in a precondition, which is how a step can be skipped unless an earlier check returned a particular value.

Can a single workflow use both Windows PowerShell and pwsh?

Yes. Set one as the workflow default and override the other on the specific steps that need it. Both are selected by executable name, so a host with both installed runs each step on the intended one.

Why does the validator reject a shell field on my step?

A bare shell field cannot be combined with run on the same step. Put the override under with.shell instead. Running dagu validate catches this before the schedule fires.

Does this work on Linux too?

Yes. pwsh runs on Linux and macOS, and the same workflow definition works there. Steps that call cmd.exe or Windows-only cmdlets are the parts that stay Windows-specific.

Next step

Start with one workflow.

Install Dagu, move one script that runs on cron today into YAML, and decide from a real run history.