Automation Scripts: How to Write and Use Them
Learn how to choose a scripting environment, write a reusable script, run it safely, and troubleshoot common failures across Bash, PowerShell, and Python.
An automation script is a saved set of instructions that a shell or language runtime executes to repeat or coordinate a task. To write one, choose a runtime available on the target systems, test the commands on safe sample data, save them in that environment’s script format, add inputs and failure handling, and run the script manually before scheduling it.
Use Bash or another shell for small jobs that mostly call existing command-line tools. Use PowerShell when the task and its modules belong to the PowerShell administration ecosystem. Use Python when the work needs more substantial data handling or libraries. These are choices based on the task and environment, not universal rankings. Google’s Shell Style Guide describes shell as appropriate for small utilities and simple wrapper scripts; Microsoft documents PowerShell’s script files, parameters, scope, and invocation in about_Scripts.
1. Choose the scripting environment
Before writing code, check the systems the script must run on, which runtime and modules are installed there, what permissions it needs, and how it will be distributed or scheduled. Also consider whether the task mostly invokes existing utilities or transforms structured data.
| Environment | Good fit | Check before sharing or scheduling |
|---|---|---|
| Bash or another shell | Small utilities that orchestrate command-line tools, move files, or make straightforward text changes. | The target shell, operating system, installed utilities, quoting rules, and file paths. A Bash script is not automatically portable to every shell. |
| PowerShell | Tasks that already use PowerShell commands, modules, and administration workflows. | PowerShell edition and version, module availability, execution policy, and organization controls. Use the .ps1 extension. |
| Python | Tasks with richer data transformations or Python libraries, including some hosted automation services. | Python interpreter version, installed packages, operating-system differences, and the host service’s currently supported runtime. |
For hosted jobs, consult the service’s current runtime documentation before deployment. For example, Azure Automation’s runbook documentation describes supported runbook types and runtime-specific details. Hosted runtimes and package support can change.
2. Plan a small, repeatable task
- Define the input and result. Write down what the script receives, what it should produce, and which systems or files it changes.
- Set a safe boundary. Start with a single folder, a test account, or sample data. Avoid beginning with a broad delete, overwrite, or production change.
- Try the commands manually. Confirm what each command does, what output it produces, and how it signals failure.
- Check prerequisites. Confirm the runtime, modules, permissions, paths, and network access on the machine that will execute the script.
- Save and run the script manually. Inspect its output and its effects before scheduling it or connecting it to another automated system.
The example below lists regular files in a chosen directory and writes a CSV report. It does not change the input files. Run each version against a test directory first.
3. Write a first script in Bash
Save this as list-files.sh. It uses Bash syntax and common Unix-style utilities; check their availability on the target system.
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 2 ]]; then
echo "Usage: $0 INPUT_DIRECTORY OUTPUT_CSV" >&2
exit 2
fi
input_dir=$1
output_csv=$2
if [[ ! -d "$input_dir" ]]; then
echo "Input directory does not exist: $input_dir" >&2
exit 1
fi
# This simple example writes a CSV header and filenames. It is intended
# for ordinary filenames; use a CSV library for arbitrary names or data.
printf 'name\n' > "$output_csv"
find "$input_dir" -maxdepth 1 -type f -printf '%f\n' | while IFS= read -r name; do
printf '"%s"\n' "${name//\"/\"\"}" >> "$output_csv"
done
printf 'Wrote report to %s\n' "$output_csv"
Run it by passing the input directory and output file:
bash list-files.sh ./sample-files ./files.csv
The script uses set -euo pipefail so common command failures, unset variables, and failures inside pipelines are less likely to pass silently. It does not make every command safe: check each command’s behavior and test on disposable data. The find -printf option is not available in every implementation of find; on a system without it, use that system’s documented alternative or choose Python for more portable filename handling.
4. Write a first script in PowerShell
Save this as List-Files.ps1. PowerShell scripts are plain-text files containing PowerShell commands. This version writes a CSV with the built-in Export-Csv command.
param(
[Parameter(Mandatory = $true)]
[string]$InputDirectory,
[Parameter(Mandatory = $true)]
[string]$OutputCsv
)
$ErrorActionPreference = 'Stop'
if (-not (Test-Path -LiteralPath $InputDirectory -PathType Container)) {
throw "Input directory does not exist: $InputDirectory"
}
Get-ChildItem -LiteralPath $InputDirectory -File |
Select-Object Name, Length, LastWriteTime |
Export-Csv -LiteralPath $OutputCsv -NoTypeInformation
Write-Output "Wrote report to $OutputCsv"
From PowerShell, run it with explicit paths:
./List-Files.ps1 -InputDirectory ./sample-files -OutputCsv ./files.csv
From another working directory, pass the full path to the script. You can inspect the execution-policy scopes with Get-ExecutionPolicy -List. On Windows, the default Restricted policy prevents scripts from running; organization policy may also control the effective setting. Read and trust a script before executing it, and follow local policy. Do not change a machine-wide setting just to get a sample script running. Microsoft explains policy scopes and behavior in about_Execution_Policies.
5. Write a first script in Python
Save this as list_files.py. It uses only Python’s standard library and handles filenames through a CSV writer rather than constructing CSV rows by hand.
import csv
import sys
from pathlib import Path
def main() -> int:
if len(sys.argv) != 3:
print("Usage: python list_files.py INPUT_DIRECTORY OUTPUT_CSV", file=sys.stderr)
return 2
input_dir = Path(sys.argv[1])
output_csv = Path(sys.argv[2])
if not input_dir.is_dir():
print(f"Input directory does not exist: {input_dir}", file=sys.stderr)
return 1
try:
with output_csv.open("w", newline="", encoding="utf-8") as output_file:
writer = csv.writer(output_file)
writer.writerow(["name", "size_bytes"])
for path in sorted(input_dir.iterdir()):
if path.is_file():
writer.writerow([path.name, path.stat().st_size])
except OSError as error:
print(f"Could not write report: {error}", file=sys.stderr)
return 1
print(f"Wrote report to {output_csv}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Run it with a Python interpreter available on the target system:
python list_files.py ./sample-files ./files.csv
# On systems where Python 3 is invoked as python3:
python3 list_files.py ./sample-files ./files.csv
The executable name differs across installations. Check it with python --version or python3 --version, and check installed packages when the script depends on third-party libraries.
6. Add reuse, documentation, and failure handling
A script becomes easier to reuse when it makes its inputs, requirements, and side effects clear.
- Use explicit parameters. Pass directories, dates, or modes as arguments instead of editing the source for each run. Validate required values before doing work.
- Explain expected use. Document purpose, prerequisites, an example command, outputs, and any changes the script makes. PowerShell supports help text and a
#Requiresstatement for declaring requirements. - Handle errors deliberately. Decide which failures should stop the job, which can be retried, and what information should be shown. Return a nonzero exit status when a caller needs to detect failure.
- Make reruns safe where possible. Avoid repeating irreversible side effects. If a task creates or updates resources, consider how it will detect existing state before acting again.
- Keep secrets out of source files. Do not store passwords as plain text. Use the credential or secret mechanism provided by the target environment and limit access to it.
- Record the target runtime. Note the shell or language version and required modules. Pin or otherwise manage dependencies using the conventions of the runtime and deployment system.
For PowerShell-specific quality and security guidance, see Microsoft’s PSScriptAnalyzer rules, which include recommendations about version documentation, help, and avoiding plain-text passwords. PowerShell functions and variables have script scope behavior; invoking a script does not automatically leave its internal definitions in the caller’s scope. See about_Scripts before relying on scope or dot-sourcing.
7. Run scripts safely and automate them later
- Review the file and understand the commands, especially commands that delete, overwrite, send, or change access.
- Run it against test inputs with the same runtime and user identity that will be used later.
- Check both output and side effects. Confirm the output file, changed resources, and process exit status.
- Move to a staging or limited-scope target before production, if available.
- Only after manual runs behave as expected, configure a scheduler or hosted runner. Set its working directory, runtime, environment variables, permissions, and log destination explicitly.
An interactive terminal can have a different current directory, profile, environment, credentials, or loaded modules from a scheduled job. If a script works manually but fails unattended, compare those conditions first. Hosted automation is its own deployment environment; consult its current documentation for runtime versions, permissions, and package support.
8. Troubleshoot common failures
| Symptom | Likely cause | What to check or fix |
|---|---|---|
| “Command not found,” “not recognized,” or interpreter not found | The runtime, utility, or module is missing, or the unattended environment has a different PATH. |
Check the runtime and command versions in the same environment that runs the job. Install or provision dependencies through the approved method, or use an available command. |
| Script file cannot be found | The current directory differs from the assumed directory, or the path contains spaces or is mistyped. | Print or inspect the working directory. Use a correct full path or quote paths that contain spaces. |
| PowerShell says running scripts is disabled | An execution policy or organization setting blocks the script. | Inspect policy scopes with Get-ExecutionPolicy -List, verify the script source, and follow the administrator’s policy. Do not make broad policy changes without authorization. |
| Permission denied or access denied | The executing identity lacks read, write, network, or administrative permission. | Check which identity runs the script and grant only the access required. Do not assume an interactive user’s permissions carry over to a scheduled job. |
| Works in a terminal, fails in a scheduled task | Different working directory, environment variables, profile, module set, credentials, or runtime version. | Log the runtime version and relevant non-secret environment details; use explicit paths and provision dependencies for the runner. |
| Unexpected filenames or broken CSV output | Hand-built quoting does not handle every filename or delimiter correctly, or shell utilities differ. | Use a CSV library, such as Python’s csv module or PowerShell’s Export-Csv. Check platform-specific command options. |
| Script reports success after a failed step | Failure status was ignored, pipeline errors were not propagated, or exceptions were caught and discarded. | Check each command’s exit status, set deliberate error handling, and return a nonzero status for failure when another tool depends on it. |
| Hosted runbook fails importing a package | The package is absent, incompatible with the hosted interpreter, or not available in that runbook environment. | Check the service’s current runtime and package instructions, then install or import compatible dependencies using its documented process. |
9. Performance, reliability, and cost
For small tasks, startup time and runtime choice matter less than avoiding repeated work and making failures visible. For large file trees or API workloads, process data in batches, avoid launching a separate process for every item, and use the libraries and bulk operations available in the chosen runtime. Measure on representative inputs rather than assuming a language is faster.
Reliability comes from predictable inputs, explicit dependencies, least-privilege access, safe reruns, useful logs, and checking exit statuses. Scheduling adds another failure surface: the host may have different versions, permissions, network access, timeouts, and retry behavior. Review those limits in the actual scheduler or hosted service documentation.
A local script may have no per-run service charge, but it still uses compute, storage, network, and maintenance time. Hosted automation services can have their own pricing and quotas; consult the selected provider’s current terms. Avoid retry loops that multiply API calls or other billable actions.
10. Automate website screenshots from a script
Screenshot automation is one example of coordinating a browser-related task. You can run a browser yourself through a script or call a screenshot API. If you build your own browser workflow, confirm that the browser and driver versions, fonts, network access, and headless settings are installed in the runtime that executes it.
DIY: call a browser from Python
This example uses Playwright’s Python package and Chromium. Install the dependency and browser first; the commands below are intended for a local development environment. The script captures a full-page screenshot and closes the browser even if navigation or capture fails.
python -m pip install playwright
python -m playwright install chromium
# capture_page.py
import asyncio
import sys
from pathlib import Path
from playwright.async_api import async_playwright
async def main() -> int:
if len(sys.argv) != 3:
print("Usage: python capture_page.py URL OUTPUT.png", file=sys.stderr)
return 2
url, output_path = sys.argv[1], Path(sys.argv[2])
if not url.startswith(("https://", "http://")):
print("URL must start with http:// or https://", file=sys.stderr)
return 2
browser = None
try:
async with async_playwright() as playwright:
browser = await playwright.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1440, "height": 900})
response = await page.goto(url, wait_until="networkidle", timeout=60000)
if response is not None and response.status >= 400:
print(f"Page returned HTTP {response.status}", file=sys.stderr)
return 1
await page.screenshot(path=str(output_path), full_page=True)
print(f"Saved screenshot to {output_path}")
return 0
except Exception as error:
print(f"Capture failed: {error}", file=sys.stderr)
return 1
finally:
if browser is not None:
await browser.close()
if __name__ == "__main__":
raise SystemExit(asyncio.run(main()))
python capture_page.py https://example.com ./page.png
networkidle can wait indefinitely on pages with continuous network activity; for those pages, choose a specific selector or a deliberate delay instead. Full-page capture can consume significant memory on very long pages. Treat the target URL as untrusted input if it comes from users, and restrict which destinations the script can access in a server environment. Browser installation and package versions must be managed alongside the script.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation for parameters and response details. Here is a runnable cURL example, followed by Python and Node.js equivalents.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
Cookie and consent banners, newsletter popups, and chat widgets are removed before capture, and each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients. The API also supports options such as full-page or element capture, device and viewport settings, dark mode, PDF settings, custom CSS and JavaScript, waiting conditions, request blocking, caching, and asynchronous jobs. One thousand screenshots per month are free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account to get started.
Frequently asked questions
Is an automation script the same as a scheduled task?
No. A script contains instructions for a runtime to execute. A scheduler or hosted runner decides when and where to start it.
Can a script run on another person’s computer?
Only when that computer has a compatible runtime, dependencies, access, and configuration. Document these requirements and test on the target environment.
Should a beginner start with Bash, PowerShell, or Python?
Start with the runtime available where the task must run and the tools the task already uses. Keep the first task small enough to understand and verify.
Do scripts need a graphical interface?
No. These examples accept command-line inputs. A graphical interface is a separate design choice and is usually unnecessary for a repeatable background task.


