Windows PowerShell Script to update Hugo

Many thanks to @pgr for the inspiration for this.

An easy way to update Hugo to the latest version. Saves messing with finding the announcement, going to GitHub, finding the right download link, downloading, unpacking, …

Here is a link to my blog where I detail how to do this. But to save you a trip, I’ve reproduced the PowerShell code here (note however, that I will only update the blog post if there are any updates).

If you are not using Windows 10 v1709 or later (PowerShell v5), you will need to change the unpack command.

# get-hugo.ps1
# Get the latest copy of Hugo for Windows (64bit) from GitHub and install it

# -- CHANGE THESE IF NEEDED -- #

# The folder containing the Hugo `bin` subfolder
$hugoRoot = "c:\Hugo"
# A regular expression to find the right platform link name
$hugoPlatform = "Windows\-64bit\.zip"
# Location of the Hugo executable (relative to $hugoRoot or use absolute path)
$hugoBin = "bin"

# ---------------------------- #

# ================================================== #
# -- Shouldn't need to change anything below here -- #
# ================================================== #

Set-LocationEx -Path $hugoRoot    # AKA `cd ...`
# Make TLS 1.2 the primary version otherwise get from most sites will fail
[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
# Get the GitHub page containing the latest version of Hugo
$latest = Invoke-WebRequest -Uri https://github.com/gohugoio/hugo/releases/latest
# Find the file details for the Windows 64bit version
$file = $latest.ParsedHtml.links | Where-Object{$_.nameProp -match $hugoPlatform} | select pathname, nameProp
# Download the actual file
Invoke-WebRequest -Uri ("https://github.com/" + $file.pathname) -OutFile $file.nameProp
# Unpack the zip file into the `bin` folder - NOTE: This requires PowerShell v5 or above (Windows 10 1709)
Microsoft.PowerShell.Archive\Expand-Archive -Path $file.nameProp -DestinationPath $hugoBin -Force
# Delete the zip file
Remove-Item $file.nameProp

#EOF
5 Likes

You know there is a Chocolatey package for this? Save yourself a lot of grief and use that (for this and everything else!)

1 Like

Thanks. I kind-of did know. However, I don’t use Chocolatey any more, I had loads of problems with it in the past and it ended up not really saving me any effort. Sometimes packages don’t get updated fast enough, sometimes key software is missing or is configured in strange ways. You just can never tell.

So I tend to write direct support scripts these days. The script above should continue to work until GitHub make a significant change to their site which hasn’t happened in a long time - or maybe if Hugo decides to move to a different hub which seems unlikely.

It also puts things where I (or you) want them, not wherever the chocolatey package maintainer put them.

Thanks. I kind-of did know. However, I don’t use Chocolatey any more, I had loads of problems with it in the past and it ended up not really saving me any effort.

I’ve been using it for years and how it would cost you time and not save any doesn’t really make much sense. I can install 120 software packages in a matter of a few hours and upgrade them, daily, in a matter of minutes. If you are having issues with it there are a number of support areas where you can get help.

Sometimes packages don’t get updated fast enough, sometimes key software is missing or is configured in strange ways. You just can never tell.

Sometimes packages don’t get updated fast enough but then comes down to volunteer maintainers on the community feed. There is an automatic way to update packages which you can customise so any packages you create will always be up to date.

The community package feed isn’t Chocolatey (but that’s another thread entirely). If you need a package to be updated or work in a slightly different way then join the community, work on the package, work with the maintainer or create your own! Add in parameters that you would normally use to configure the software. Change where it is installed to. However the VAST majority of packages simply install to the same location as if you installed it manually (so Program Files). It doesn’t matter where the Hugo package is installed to though as long as it’s on the path.

If you get involved with the community everybody benefits. The Hugo Chocolatey package is updated fairly regularly (0.41 was approved on June 1st) and is installed on the path so all you have to do is type hugo and it works. It also supports PowerShell version 2 onwards (which comes out of the box on Windows 7 and Server 2008).

So I tend to write direct support scripts these days. The script above should continue to work until GitHub make a significant change to their site which hasn’t happened in a long time - or maybe if Hugo decides to move to a different hub which seems unlikely.

I have concerns about running a random script from the internet, especially for non-technical people as they can’t assess whether what they are running is safe or not. A Chocolatey package goes through several stages before it’s approved for use including human moderation so it’s checked to make sure it’s not doing anything it shouldn’t.

I think you need to give Chocolatey another go. If you need any help then check out the Gitter channel or the usergroup.

Anyway, that’s not talking about your script, I’m just trying to help people get Hugo installed.

Your script looks good. It basically does what the automatic update for Chocolatey packages does - checks the webpage using a regular expression and updates the package (or not) based on what it finds. What is Set-LocationEx in your script?

1 Like

That was certainly not my experience when last I tried. Quite possibly things have moved on since then. This wasn’t a comment about the Hugo package anyway, it was a comment about chocolatey. I don’t use it because it was causing more issues than it resolved, that’s a simple statement of fact. I’m sure that the Hugo package is fine and it seems to be supported OK. I just don’t want to add the complexity of chocolatey to my already extremely packed PC just for Hugo when a simple script is all I need. This had the advantage of being quite fun to write & adding to my skills with PowerShell. Specifics are sometimes better than generics and this gives people choice.

Urm, I don’t think you can preach that at me I’m afraid. I am already active in a number of open source projects: supporting people, updating documentation and even adding code through PRs and plugins. None of this is my day-job though which is also very busy.

I even contributed a package to chocolatey when I was trying to adopt that for myself.

That’s the way the vast majority of packages are written and have always been written. Most Chocolatey packages are a wrapper around the existing installers to make them silent. That’s the way they have always been. Individual packages can change that but that’s not typical.

So your experience of Chocolatey is packages that are not created the way you expect them to be. Again, to labour the point, packages and Chocolatey are not the same thing. So your issues are with packages, not Chocolatey.

That wasn’t a preach. That was a statement.

You complained about community packages being out of date. My suggestion was to get involved and help that situation by contacting the maintainers or working on them. The package repository you were likely installing from was the Community repository. So the Community supports it. Without people working on it to fix things they don’t like or are broken, it doesn’t exist.

As this has strayed into the non-Hugo territory completely I’ll leave it here. However to make it clear for everybody else your issues with Chocolatey are fairly non-typical.

Thanks for this inspiring thread @TotallyInformation and @pgr as the original starter. :slight_smile:

I thought it would be a nice practice for my Go skills to see if I can make a similar program in Go to easily update Hugo from the command line. I’ve take a bit a different road, and used the GitHub API to get a JSON file with all the latest releases (instead of parsing the webpage).

I also didn’t want to overwrite the original hugo.exe file, in case there was some new feature that broke the website and I wanted to roll-back. So instead I rename the ‘old’ Hugo file.

Here’s how it looks like:

C:\Bestanden\Go practice\Networking>go run update-hugo.go
2018/06/20 17:42:02 Downloading Hugo v0.42.1, released on 13 Jun 2018 10:21
2018/06/20 17:42:06 Updated Hugo. Earlier version is stored as hugo_previous_2018-06-20_1742.exe

This is the code:

package main

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

// hugoRelease models the relevant JSON data returned by the GitHub API
type hugoRelease struct {
	Version     string    `json:"tag_name"`
	ReleaseDate time.Time `json:"published_at"`
	Assets      []struct {
		URL string `json:"browser_download_url"`
	}
}

func main() {
	// Set this variable to your platform.
	// See the download URLs on https://github.com/gohugoio/hugo/releases/latest for how Hugo names your operating system's download URL
	platformName := "Windows-64bit"

	// Set this path to whichever folder that is on your PATH and that you want to store the new Hugo version in
	targetFolder := `C:\Hugo\`

	// Set the Go's app working directory to that folder (saves us having to provide the path in full each time)
	if err := os.Chdir(targetFolder); err != nil {
		log.Fatalf("Failed to set program's working directory to %s", targetFolder)
	}

	// STEP 1. Get release information for the latest Hugo release through the GitHub API
	request, err := http.Get("https://api.github.com/repos/gohugoio/hugo/releases/latest")
	if err != nil {
		log.Fatalf("Failed to fetch the GitHub API response. Error: %s", err)
	}

	body, err := ioutil.ReadAll(request.Body)
	if err != nil {
		log.Fatalf("Failed to read the returned GitHub API response. Error: %s", err)
	}

	// Unmarshal the downloaded JSON data onto our struct
	release := hugoRelease{}
	if err = json.Unmarshal(body, &release); err != nil {
		log.Fatalf("Failed to marshal JSON onto our struct object. Error: %s", err)
	}
	log.Printf("Downloading Hugo %s, released on %s", release.Version,
		release.ReleaseDate.Format("2 Jan 2006 15:04"))

	// To get the right download URL we loop over all URLs, looking for the one that contains our platform
	downloadURL := ""
	for _, v := range release.Assets {
		if strings.Contains(v.URL, platformName) {
			downloadURL = v.URL
			break
		}
	}

	// STEP 2. Now that we have the download URL, download the file from GitHub
	resp, err := http.Get(downloadURL)
	if err != nil {
		log.Fatal("GitHub cannot be reached")
	} else if resp.StatusCode >= 400 {
		log.Fatalf("Failed to get %q: HTTP error %d", downloadURL, resp.StatusCode)
	}
	defer resp.Body.Close()

	// STEP 3. Rename current Hugo executable so we can rollback to our current Hugo version easily
	previousFile := "hugo_previous_" + time.Now().Format("2006-01-02_1504") + ".exe"

	err = os.Rename("hugo.exe", previousFile)
	if err != nil {
		log.Fatalf("Failed to rename existing Hugo executable. Error: %s", err)
	}

	// STEP 4. Unzip downloaded file and extract Hugo executable
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Failed to read downloaded GitHub file")
	}

	zipReader, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
	if err != nil {
		log.Fatalf("Failed to read zip file")
	}

	// Go through zip file, and look for hugo.exe. We don't need to uncompress LICENSE nor README.md
	for _, f := range zipReader.File {
		if f.Name != "hugo.exe" {
			continue
		}

		fileReader, err := f.Open()
		if err != nil {
			log.Fatalln("Failed to open the compressed hugo.exe file")
		}
		defer fileReader.Close()

		targetFile, err := os.Create("hugo.exe")
		if err != nil {
			log.Fatalln("Failed to open the target hugo.exe file")
		}
		defer targetFile.Close()

		// Copy hugo.exe from the zip into the target file
		if _, err := io.Copy(targetFile, fileReader); err != nil {
			log.Fatalln("Failed to write hugo.exe from the zip into its target file")
		}

		break // Terminate loop; we've found our hugo.exe file and extracted it
	}

	log.Printf("Updated Hugo. Earlier version is stored as %s", previousFile)
}

I’ll write up a blog post with more information. When that’s live, I’ll post the link here. :slight_smile:

2 Likes

Eh eh that’s very nice, but nothing beats the beauty and craziness of bash one-liners :stuck_out_tongue:

1 Like

I’ve recently taken over the Hugo Chocolatey package and released the hugo-extended package. See the releases for all of the automated releases.

2 Likes