---
title: "Write and approve a script"
description: "Add a script to the library, declare its parameters, test the draft on one endpoint, and approve it so it can run on many endpoints and on schedules."
url: "https://support.outpostplatform.com/automation/write-and-approve-scripts/"
product: "depot"
type: "how-to"
status: "draft"
ai_summary: "Steps to add a script under Scripts > Library: New script, choose library scope (MSP), interpreter, Runs on, Runs as, timeout and output limit, write the body, add parameters, optionally make it a collector, Save as a draft, test on one endpoint with script:run_unapproved, then Actions > Approve with script:approve to sign it."
availability:
  plans: ["home-lab", "essentials", "enterprise", "msp"]
  platforms: ["windows", "linux", "macos", "freebsd", "openbsd"]
  permission: "script:manage"
  preview: false
source: "https://gitlab.com/outpostplatform/docs/-/edit/main/src/content/docs/automation/write-and-approve-scripts.md"
license: "CC BY 4.0"
---

# Write and approve a script

This page adds a script to the library and approves it. When you finish, the
script is signed and ready to run on any number of endpoints or on a schedule.
For how the library, signing, and parameters work, see
[Scripting](/automation/scripting/).

## Before you begin

- To write and save a script you need `script:manage`. To approve it you need
  `script:approve`. To test the draft on an endpoint you need
  `script:run_unapproved` and `command:execute`.
- To start from a built-in script, duplicate it: select **Actions** >
  **Duplicate** on its row in **Scripts** > **Library**. The copy is a new draft
  you can edit.
- If the script uses batch, zsh, or Python, make sure
  `script_execution.allowed_interpreters` allows that interpreter on the
  endpoints. See [Interpreters](/automation/scripting/#interpreters).

## Write the script

1. Go to **Scripts** > **Library** and select **New script**.
2. On the MSP plan, under **Who can use this script**, choose **This client
   only** or **Every client**.
3. In **Name**, enter a name. Optionally enter a **Description**, a
   **Category**, and **Tags** separated by commas. The library search matches
   the name, description, and tags, not the body.
4. In **Interpreter**, choose the interpreter.
5. In **Runs on**, choose **Any** or the one operating system the script is for.
6. In **Runs as**, choose **SYSTEM** or **Signed-in user**. This is the default
   for each run, and a run can change it.
7. Set **Timeout (seconds)**, which defaults to `300`, and **Output limit
   (bytes)**, which defaults to `65536`.
8. Under **Script**, write or paste the body.
9. Under **Parameters**, select **Add parameter** for each value the script
   takes. Enter a **Key** and a **Label**, choose a **Kind**, and optionally
   enter a **Default**. For **Choice**, enter the **Choices**. Select
   **Required** when the run must supply a value. Parameters reach the script
   as arguments in the order you add them.
10. If the script reports a value to keep on the endpoint, under **Collector**
    select **This script's output fills in an endpoint field**, then choose the
    **Endpoint field** and how to **Read the output as**: **Whole output**,
    **JSON**, or **Key and value**. See
    [Endpoint custom fields](/automation/custom-fields/).
11. Select **Save**. The script is saved as a draft, version 1.

For example, this PowerShell body takes one **Number** parameter with the key
`minimum_age_days`:

```powershell
param(
    [int]$MinimumAgeDays = 1
)

Write-Output "Removing files older than $MinimumAgeDays days."
```

### Import a script file

In the script editor, select **Import from file**, or drag a file onto the
import area or script body. The file must be UTF-8 text, without NUL bytes,
and no larger than 256 KiB. From the library, **Upload script** opens a new
script ready to import.

Import fills a blank **Name** from the file name without its extension. It
sets **Interpreter** from the extension:

| Extension | Interpreter |
| --- | --- |
| `.ps1`, `.psm1` | **PowerShell** |
| `.bat` | **Batch** |
| `.cmd` | **cmd** |
| `.sh`, `.bash` | **bash** |
| `.zsh` | **zsh** |
| `.py` | **Python** |

An unrecognized extension leaves the interpreter unchanged. If the editor
already contains script text, select **Replace** to confirm replacing it.
Review the imported body and settings, then select **Save**. Importing does
not approve or run the script.

## Test the draft on one endpoint

1. In **Scripts** > **Library**, choose the **Drafts** view. The library opens
   on **Ready to run**, which lists approved scripts only.
2. On the script's row, select **Actions** > **Run**.
3. Under **Where it runs**, choose one endpoint. A draft cannot run on more
   than one endpoint, a site, or a device group.
4. Enter the parameter values and select **Review**, then **Run script**.
5. Open the endpoint and read the result on its **Commands** tab. A draft run
   on one endpoint is a single command, so it does not appear under **Scripts**
   > **Batches**.

If the endpoint runs signed scripts only, the draft cannot run there. Test it
on an endpoint where `script_execution.require_signed_scripts` is off, or
approve it first.

## Approve the script

1. In **Scripts** > **Library**, on the script's row, select **Actions** >
   **Approve**.
2. The library shows that the script is approved and signed, and its status
   changes to **Approved**.

The script can run on many endpoints at once and on schedules.

On endpoints where `script_execution.approval_required` applies, the approval
must come from someone other than the author of this version. If you wrote it,
select **Ask for approval** on the script's **Approval** card or in the row's
**Actions**, and someone else with `script:approve` approves it. You cannot
approve or turn down your own request. If you are the only person who can edit
scripts in the workspace, or nobody else can approve in time, see
[When you are the only script editor](/automation/scripting/#sole-editor) and
[Emergency approval](/automation/scripting/#emergency-approval).

## Verify

Open the script. The line under its name reads **Version 1, approved.**, with
your version number. **History** lists the version you approved.

## If something goes wrong

| Symptom | Cause | Fix |
| --- | --- | --- |
| **Save** is unavailable with **Built-in scripts are read-only. Duplicate to edit.** | The script ships with Outpost. | Select **Duplicate** and edit the copy. |
| **Approve** is unavailable with **You do not have permission to approve scripts.** | You lack `script:approve`. | Ask someone who holds it, such as an owner. |
| An approved script is a draft again | Its body or interpreter was changed, which clears the approval. | Review the change and approve the new version. |
| The draft run is refused with **This endpoint refuses unsigned scripts** | The endpoint requires signed scripts. | Approve the script, or test on another endpoint. |
| **This script needs a second person's approval before it can run on these endpoints.** | `script_execution.approval_required` applies, and the version was approved only by its author, or not at all. | Select **Ask for approval** and have someone else approve it. |
| **Approve** is unavailable with **You asked for this approval, so the decision is somebody else's.** | You asked for this approval. | Have someone else with `script:approve` approve it, or use an [emergency approval](/automation/scripting/#emergency-approval). |
| The run is refused because the interpreter is not allowed | `script_execution.allowed_interpreters` on the endpoint does not include it. | Add the interpreter on a profile that applies. |

## Next steps

- [Run scripts on endpoints](/automation/run-scripts/)
- [Endpoint custom fields](/automation/custom-fields/)
