Schedule PowerShell scripts with approvals
Windmill runs PowerShell scripts on a cron schedule, and every run is listed in the Runs page with its inputs, logs and result. When a person must approve before the script changes anything, put the script in a flow with an approval step in front of it, and schedule the flow. The flow then stops at the approval step until someone approves or rejects it in the browser, or in Microsoft Teams.
This guide builds a flow that runs every night: a PowerShell step lists log files older than a retention period, an approval step shows how many files will be deleted, and a second PowerShell step deletes them once approved.
Schedule a script without approval
If the script does not need an approval, schedule it directly.
In the script's settings, toggle on the primary schedule, set the cron expression and the arguments, then deploy.
You can also create more schedules for the same script from the Schedules page.
The rest of this guide adds the approval.
Write the PowerShell steps
PowerShell runs on Linux workers built from the Windmill Docker image. To run a step on a Windows machine instead, use Windows workers (Self-Hosted Enterprise) and give the step a worker group tag that only those workers listen to. Set the tag on the PowerShell steps only: a tag set on the whole flow overrides the tag of every step.
Create two PowerShell scripts, or write them inline as steps of the flow.
The first one lists the files and writes the list to ./result.json, which becomes the step's result:
param(
[Parameter(Mandatory)]
[string]$Folder,
[int]$RetentionDays = 30,
[string]$Filter = '*.log'
)
$cutoff = (Get-Date).AddDays(-$RetentionDays)
$old = @(Get-ChildItem -Path $Folder -Filter $Filter -File -Recurse |
Where-Object { $_.LastWriteTime -lt $cutoff })
$paths = @($old | ForEach-Object { $_.FullName })
[PSCustomObject]@{
folder = $Folder
cutoff = $cutoff.ToString('o')
count = $paths.Count
files = $paths
} | ConvertTo-Json -Depth 3 | Set-Content -Path ./result.json
The second one deletes the files it receives:
param(
[Parameter(Mandatory)]
[string[]]$Files
)
foreach ($file in $Files) {
Remove-Item -LiteralPath $file -Force
Write-Output "Deleted $file"
}
Write-Output "Deleted $($Files.Count) files"
The folder must be reachable from the worker that runs the steps.
The param() blocks become the steps' inputs: Folder and Files are required, RetentionDays and Filter have defaults.
See Turn a PowerShell script into a web form for how each declaration is read.
Build the flow
From the Home page, click New and select Flow.
- In the flow inputs, add
Folder(string) andRetentionDays(integer). - Add the first PowerShell script as step
a. Connect its inputs to the flow inputs, for exampleFoldertoflow_input.FolderandRetentionDaystoflow_input.RetentionDays(see input transforms). - On step
a, openRun settings, thenEarly stop / break, and stop the flow ifresult.count == 0, so that nobody is asked to approve when there is nothing to delete. See Early stop. - Add the approval step
b, described below. - Add the second PowerShell script as step
c, withFilesconnected toresults.a.files.
Add the approval step
Add a step and pick Approval/Prompt.
This creates a step with Suspend until approval/resume enabled: the flow runs the step, then waits for an approval before step c.
Write the step in TypeScript, with folder connected to results.a.folder and count to results.a.count.
- Approve in Windmill
- Approve in Microsoft Teams
export async function main(folder: string, count: number) {
return {
description: `${count} files older than the retention period will be deleted from ${folder}.`
};
}
The returned description is shown to approvers on the approval page.
Approvers open the suspended run from the Runs page, where the Suspended filter lists the runs waiting for them, and approve or reject it there.
import * as wmill from 'windmill-client';
export async function main(folder: string, count: number) {
await wmill.requestInteractiveTeamsApproval({
teamName: 'IT Operations',
channelName: 'Approvals',
message: `${count} files older than the retention period will be deleted from ${folder}.`
});
}
The Windmill Teams bot posts a card to the channel, with buttons to approve or reject. This needs a workspace connected to Teams, which is an Enterprise Edition feature. See Approve workflow steps from Microsoft Teams.
To send the approval link by email or another channel instead, call getResumeUrls() from the TypeScript client in the approval step and send its approvalPage URL; see Add approval script.
The Slack approval helper works the same way as the Teams one.
In the approval step's settings, you can also:
- Set the number of approvals required before the flow continues.
- Set a timeout after which the flow is canceled if nobody answered. A new approval step starts with a 30-minute (1800 seconds) timeout, and a step with no timeout also times out after 30 minutes, so for a run scheduled at night and approved the next morning, set a timeout long enough to cover the wait.
- Continue on disapproval or timeout instead of failing the flow, to handle a rejection in a following step.
- Add a form that approvers fill in, and restrict who can approve to logged-in users or members of a group with the permission options. Both are Cloud and Enterprise Self-Hosted features.
The next steps can read who approved from the approvers input, as described in Get the users who approved the flow.
Schedule the flow
Deploy the flow, then schedule it, either from the flow's settings or from the Schedules page with New schedule:
- Set the cron expression, for example
0 0 2 * * *for every day at 02:00 in the schedule's timezone. Windmill's cron syntax has six fields and starts with seconds: a five-field crontab expression such as0 2 * * *is rejected. The simplified builder writes the expression for you. - Pick the flow and fill in its inputs, for example
FolderandRetentionDays. - Optionally, add a schedule error handler, for example the pre-set Microsoft Teams one, to be notified when a run fails. The schedule error handler is an Enterprise Edition feature.
Each scheduled run starts the flow with these inputs. Runs waiting for an approval show as suspended in the Runs page, and finished runs keep the logs and result of each step. To also run the flow on demand, open its page: the form generated from its inputs starts a run with the values you enter.