Update Ghost blogs and pages with PowerShell
Tags: ghost, githubactions, github, powershell
Published: 2022-11-15 18:06:48

---
title: Update Ghost blogs and pages with PowerShell
published: true
date: 2022-09-30 08:17:34 UTC
tags: Ghost,GitHubActions,GitHub,PowerShell
canonical_url: https://jessehouwing.net/update-ghost-blogs-and-pages-with-powershell/
---

 ![Update Ghost blogs and pages with PowerShell](https://jessehouwing.net/content/images/2022/09/Ghost_Key_close-up.webp)

[The samples provided by Ghost are in JavaScript, Curl and Python](https://ghost.org/docs/admin-api/#token-authentication), all languages I'm not fluent in, so I set out to do the same from PowerShell or C#.

The hardest part turned out to be the code to create the correct JWT token to authenticate against Ghost. In their admin panel they provide a set of tokens, but it turns out you can't just use these tokens as-is.

![Update Ghost blogs and pages with PowerShell](https://jessehouwing.net/content/images/2022/09/image.png)
_API Key provided by Ghost_

The API key essentially makes up 2 parts:

1. The Key Identifier, that's the part up to the `:`
2. The Secret, that's the part after the `:`

In order to use this key, we first have to split it into its two parts:

```
$parts = $adminApiKey -split ":"
$id = $parts[0]
$secret = $parts[1]
```

We then need to construct a JWT token from this key. I relied on [the Posh-JWT package](https://github.com/SP3269/posh-jwt) for that part:

```
Install-Module -Name JWT -force

$key = [Convert]::FromHexString($secret) # only works in PowerShell Core

$jwttoken = New-Jwt `
  -Header (
    @{
      "alg" = "HS256"
      "kid" = $id
      "typ" = "JWT"
    } | ConvertTo-Json
  ) `
  -PayloadJson (
    @{
      "exp" = ([DateTimeOffset](Get-Date).AddMinutes(2)).ToUnixTimeSeconds()
      "iat" = ([DateTimeOffset](Get-Date)).ToUnixTimeSeconds()
      "aud" = "/admin/"
    } | ConvertTo-Json) 
  -Secret $key
```

With this token, you can then invoke the Ghost API.

```
$headers = @{
  Authorization = "Ghost $jwttoken"
  "Content-Type" = "application/json"
}

$baseUri = "https://scrumbug.ghost.io/ghost/api"
        
$result = Invoke-RestMethod `
  -Uri "$baseUri/admin/pages/" `
  -Method GET `
  -Headers $headers     
```

As I said, I used this code to automatically sync my Scrum.org classes to this blog. You can find the [(private) Ghost update GitHub Action here](https://github.com/jessehouwing/jessehouwing/blob/master/.github/actions/update-scrum-classes-on-ghost/action.yml). To keep my secrets secure I added a couple of extra lines of code to register my secrets with the runner:

```
Write-Output "::add-mask::$id"
...
Write-Output "::add-mask::$secret"
...
Write-Output "::add-mask::$key"
...
Write-Output "::add-mask::$jwttoken"
```

Make sure to [always register your secrets every time they change their shape or representation to prevent your secrets from leaking into the GitHub Action logs](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#masking-a-value-in-log).

End result? My classes are automatically updated 🎉:

![Update Ghost blogs and pages with PowerShell](https://jessehouwing.net/content/images/2022/09/image-1.png)
_Github actions workflow runs successfully_