Tools

Run PowerShell Data Loaders on Azure Static Web Apps

• 4 min read
Share:

Observable Framework can run PowerShell scripts during a build and expose their output to interactive pages as static data. The part that needs extra attention is deployment: the automatic Azure Static Web Apps build does not know about your custom PowerShell interpreter.

This article shows how to register .ps1 data loaders, build the site in GitHub Actions where PowerShell is available, and ask Azure Static Web Apps to deploy the already-generated files.

How Observable data loaders work

Observable Framework is a static site generator for data apps, dashboards, and reports. A page can load a CSV file like this:

1
const movies = FileAttachment("movies.csv").csv({typed: true});

If movies.csv does not exist, Framework looks for a data loader with a double extension, such as movies.csv.py or movies.csv.js. It runs the loader during the build, saves its standard output as a static snapshot, and makes that snapshot available to the page as movies.csv.

Framework supports several interpreters by default and lets us register additional ones. Add PowerShell to observablehq.config.js:

1
2
3
4
5
6
export default {
  root: "src",
  interpreters: {
    ".ps1": ["pwsh"]
  }
};

The pwsh executable must be installed and available on PATH wherever the site is built.

Create a PowerShell data loader

Create src/movies.csv.ps1:

1
2
3
4
5
6
$uri = 'https://raw.githubusercontent.com/vega/vega/main/docs/data/movies.json'
$movies = Invoke-RestMethod -Uri $uri

$movies |
    Select-Object -Property Title, 'Worldwide Gross', 'US Gross', 'IMDB Rating' |
    ConvertTo-Csv -NoTypeInformation

The script retrieves JSON, selects the columns needed by the page, and writes CSV to standard output. That last detail is important: standard output becomes the generated file. When pwsh runs as an external process, PowerShell’s information, warning, verbose, and debug streams are also written to standard output, so using them can corrupt the CSV. Write non-fatal diagnostics directly to standard error, for example with [Console]::Error.WriteLine('message'), and use throw when the loader should fail.

You can test the loader independently:

1
pwsh ./src/movies.csv.ps1

Then use it from src/index.md:

1
2
3
4
5
6
7
```js
const movies = FileAttachment("movies.csv").csv({typed: true});
```

```js
Inputs.table(movies)
```

Run the normal Framework build:

1
npm run build

Framework executes the PowerShell loader, caches its result, and includes the generated CSV in the static output.

Why the default Azure build can fail

Azure Static Web Apps normally checks out the source and invokes its automatic build environment. That works until the application requires an interpreter or native tool that the environment does not provide or configure.

Instead of teaching the automatic builder about every dependency, build the site in an ordinary GitHub Actions step and deploy only the output directory. This also makes the failing stage obvious: if a PowerShell loader breaks, the npm run build step fails before deployment starts.

Keep the Azure configuration in the output

Azure looks for staticwebapp.config.json in the deployed directory. If you keep that file at the project root, copy it after the Observable build.

For example, these scripts in package.json build the site into dist and copy the configuration:

1
2
3
4
5
6
{
  "scripts": {
    "build": "rimraf dist && observable build",
    "postbuild": "cp staticwebapp.config.json dist/staticwebapp.config.json"
  }
}

The postbuild script runs automatically after npm run build. For a cross-platform project, replace cp with a small Node.js script.

Build first, then deploy

The relevant part of the GitHub Actions workflow looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Check out the repository
        uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - name: Verify PowerShell
        shell: pwsh
        run: $PSVersionTable.PSVersion

      - name: Install dependencies
        run: npm ci

      - name: Build the Observable site
        run: npm run build

      - name: Deploy the prebuilt site
        uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          action: upload
          app_location: dist
          api_location: ""
          output_location: ""
          skip_app_build: true

The last four settings are the key:

  • app_location points directly to the generated dist directory;
  • output_location is empty because no build happens inside the Azure action;
  • skip_app_build prevents the Azure action from invoking its automatic builder;
  • staticwebapp.config.json is already inside dist.

Keep the pull-request close job and deployment token generated for your Static Web App; only replace the build-and-upload portion of the workflow.

The resulting build pipeline

The finished pipeline has a simple division of responsibility:

  1. GitHub Actions provides Node.js and PowerShell.
  2. Observable Framework runs .ps1 data loaders and produces static files.
  3. Azure Static Web Apps deploys those files without rebuilding them.

This pattern is not limited to PowerShell. It also works when an Observable project depends on another interpreter, command-line tool, or native library that is easier to control in a dedicated build step.

For more detail, see the Observable Framework documentation for data loaders and custom interpreters, plus the Azure documentation for deploying a prebuilt application.

About the Author

Andrey Vernigora avatar

Andrey

Developer platforms, PowerShell, Azure, and observable systems

I am a hands-on software architect with more than 20 years of experience building developer platforms, delivery automation, and production infrastructure. I work primarily with PowerShell, C#/.NET, and Azure, turning infrastructure complexity into application-centric self-service workflows using CI/CD, GitOps, Kubernetes, infrastructure as code, and observability.

I build PowerShell tools and write about Azure automation, graph-based infrastructure analysis, messaging, and data visualization. My open-source projects include PSQuickGraph, PSGraphView, ipmgmt, and pubs.

View profile

Related Articles