This post series is indexed at NgateSystems.com. You'll find a super-useful keyword search facility there too.
Last reviewed: Mar '26
1. Introduction
Post 4.2 floated the concept of pre-rendering a web page. The idea was that if a page never changes (or at least doesn't change too often), then it might as well be converted to HTML during the project's "build".
This is fine, but if the underlying data changes too often, running builds to bring pre-rendered pages up to date manually will become annoying. Automation is surely the answer.
This post describes how you might record thenpm run build and gcloud app deploy commands to run your build/deploy sequence for your webapp as a "script" file. A "script" file is just a text file with a .ps1 extension that can be triggered locally by opening the script for editing and selecting "Terminal/Run active file" from the VSCode menu bar. But, more usefully, it can also be configured to be run automatically by the Windows scheduler.
2. A PowerShell Build/Deploy script
The script below contains much unfamiliar syntax, but it shouldn't be too difficult to work out what most of it does. Comments have been added to decode the more complex bits, and for the rest, you might find it useful to check out Post 10.2 on "PowerShell Essentials" in this series.
The script is also rather more complicated than you might expect. This is because it incorporates several "bonus" ideas:
While you're currently thinking primarily about the routine production operation of scheduled pre-rendered builds, there will still be occasions when you want to run a deployment from the terminal. Scheduled builds will send their output to a log file, but this will be unsatisfactory for an ad hoc run. Here, you will want to see the output in the terminal. The answer is to instrument the script with an optional
-logFile - " .. your log-file .."parameter, and switch output between this and the terminal based on whether the parameter is present.A "feature" of Google App Engine hosting is that when you deploy a new version, it doesn't overwrite any pre-existing hosting; it simply adds it to a growing list. This is great in one way: if things go badly wrong, you can quickly delete the erroneous version and fall back to a previous "known-good" deployment. But Google doesn't let you configure a limit on the number of versions (unlike Firebase hosting). Eventually, the number of versions will reach Google's limit (currently 200), and further deployments will fail. Of course, you could delete unwanted versions manually, but to save you the trouble (and, also, the charges for unnecessary version storage), the script below limits the number of deployed versions to 10.
When your job is being submitted by the scheduler, you might like to be automatically informed should it error. Failure in a PowerShell script can be handled by a "try/catch" block, exactly as you've seen in JavaScript. But how will you know that a run has failed? The example script below shows how you might use a service like PushOver to send a notification to your phone. It's much easier than you might imagine and, in the case of PushOver, will cost you juts $5 for a lifetime license.
Enough said - here's the script I'm currently using to maintain the NgateSystems website
param(
[string]$logFile = $null # Set to null if not supplied
)
function Write-Log {
param([string[]]$message)
if ($logFile) {
# write to the logfile
Add-Content -Path $logPath -Value $message
}
else {
# write to the terminal
Write-Output $message
}
}
$projectId = ... your project id ...
$projectEmail = ... your Google account address ...
$projectPath = ... the full Windows path to your project ...
# set the account for the run
gcloud config set account $projectEmail
# ensure failure of "build" prevents execution of "deploy"
$errorActionPreference = "Stop"
# Define log file path
$logPath = Join-Path $projectPath $logFile
try {
# Clear out any existing logFile content
if ($logFile) {
Clear-Content -Path $logPath
}
# Write a "Starting run " message
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
Write-Log "Starting ... your run ... at $timestamp"
# Set the project ID - see notes below for "2>&1" explanation
$output = gcloud config set project $projectId 2>&1
Write-Log $output
# Build the app. Set the Output Encoding first - npm's output
# uses UTF-8 encoding, but PowerShell interprets it
# differently, mangling the line endings.
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Set-Location $projectPath
npm run build 2>&1 | ForEach-Object { Write-Log $_ }
# See post 10.2 for background on how PowerShell's "|" pipe
# syntax passes the output lines to Write-Log
# Deploy the app.
gcloud app deploy "$projectPath\build\app.yaml" --quiet 2>&1 | ForEach-Object { Write-Log $_ }
# Now prune the app engine versions
# Fetch all versions ordered by creation date,
# excluding the most recent 10.
$oldVersions = gcloud app versions list `
--sort-by="~version.createTime" `
--format="value(version.id)" | Select-Object -Skip 10
if ($oldVersions.Count -gt 0) {
# Log the old versions to be deleted
$output = "Old versions to delete: $($oldVersions -join ', ')" 2>&1
Write-Log $output
}
# Delete any old versions
if ($oldVersions.Count -gt 0) {
try {
$oldVersions | ForEach-Object {
# delete the old version. The --service parameter
# ensures that gcloud looks in the default
# service (hosting offers multiple services) and
# -quiet suppresses the interactive
# confirmation prompt that gcloud would normally
# show before deleting something
$output = gcloud app versions delete --service=default --quiet $_ 2>&1
Write-Log $output
}
}
catch {
Write-Log "Batch deletion encountered the following error: $_"
}
}
else {
Write-Log "No old versions to delete. The limit of 10 is not exceeded."
}
}
catch {
# This is where you tell the script what to do if things have
# gone wrong - in this case, send a notification to your phone
curl -s --form-string "priority=1"`
--form-string "token=aiu - your account token - q6ix" `
--form-string "user=ue - your user-id - v54u22" `
--form-string "message=Something has gone wrong with the scheduled deployment of $projectId" `
https://api.pushover.net/1/messages.json
}
Notes
It takes a while to "get your eye in" with PowerShell syntax, but I find that it grows on you. You might be interested to know, for example, that it is possible for a script to run Firestore database queries and read Firebase storage. If you're struggling, you might find Post 10.2 in this series useful. Personally, I've also found AI extremely helpful, both in writing code and resolving issues.
The 2>&1 syntax that appears frequently above looks particularly intimidating, but it simply tells PowerShell to capture both standard and error output in your $output variable. The array of objects thus created is then processed by a ForEach-Object arrangement in which $_ represents the current object.
A feature of many terminal commands is that, at certain points, they halt to request approval before proceeding with a critical process. The idea is to give the user a chance to take stock of a summary of what the job is just about to do and assure themselves that this is really what they want. However, when a job is run automatically by a scheduler, this doesn't make much sense. You override this behaviour by including a --quiet flag to the command.
3. Configuring a Windows Schedule to run the PowerShell script
Here's the procedure for registering a script with the Windows Scheduler. My own scripts run automatically on a laptop (where I'm permanently logged-in on all my Google accounts). They perform happily even when the lid is closed.
- Type "Task Scheduler" in the Windows search bar and open the app.
- In the Actions menu, click on “Create a Basic Task”.
- Supply a name and description for the task
- On the Triggers tab, select the interval you want to run the program, such as “Daily”, “Weekly”, etc.
- Specify the start date/time and frequency for the task.
- Select the “Start a program” option button.
- Now, in the “Start a Program” window: In Program/script: Use "browse" to help you enter the path of pwsh.exe (the terminal shell you use in VSCode - "C:\Program Files\PowerShell\7\pwsh.exe"). In Arguments: Enter the full path of the script. eg: [full path to the script][my script filename].ps1. Leave “Start in” empty - your script defines the full logPath
- In the next window, select the checkbox “Open the Properties dialogue for this task when I click Finish”, and click the Finish button.
- In the General tab of the properties dialogue, ensure that the “Run when user is logged on or not” and “Run with highest privileges” checkboxes are selected. This ensures you are running the script with Administrator rights.
- Click the OK button and confirm your right to save your new scheduler task by responding to a login prompt with your Microsoft username and password.
- Test the new task by opening the Task Schedule Library, right-clicking on the task's entry here and selecting "Run"
Notes
The Windows Task Scheduler is a very powerful tool, but its interface is idiosyncratic. The procedure for creating a new task is straightforward, but to edit it later, right-click its entry in the task list and select "properties". You can then select individual windows such as "Actions" (though be prepared to quote your Window password to authenticate field changes- no face-id or pin number here). Another good thing to know is that you can test a task by right-clicking it in the scheduler's list of available scripts and selecting "run". This will ignore whatever scheduling you've specified and fire the script immediately. Finally, to copy a Task, export it and re-import it under a new name.
Good luck. Just remember, this is all usually good fun. And if it's not, AI always has your back!
Top comments (0)