mirror of https://github.com/liamg/aminal.git
219 lines
6.0 KiB
Go
219 lines
6.0 KiB
Go
// +build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"flag"
|
|
"github.com/liamg/aminal/generated-src/installer/data"
|
|
"github.com/liamg/aminal/windows/winutil"
|
|
"golang.org/x/sys/windows/registry"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"text/template"
|
|
)
|
|
|
|
const Version = "VERSION"
|
|
const ProductId = `{35B0CF1E-FBB0-486F-A1DA-BE3A41DDC780}`
|
|
|
|
func main() {
|
|
doInstallPtr := flag.Bool("install", false, "Install Aminal")
|
|
doUpdatePtr := flag.Bool("update", false, "Update Aminal")
|
|
flag.Parse()
|
|
|
|
var installDir string
|
|
isUserInstall := strings.HasPrefix(os.Args[0], os.Getenv("LOCALAPPDATA"))
|
|
if *doInstallPtr {
|
|
installDir = getInstallDirWhenManagedByOmaha()
|
|
extractAssets(installDir)
|
|
createRegistryKeysForUninstaller(installDir, isUserInstall)
|
|
updateVersionInRegistry(isUserInstall)
|
|
createStartMenuShortcut(installDir, isUserInstall)
|
|
launchAminal(installDir)
|
|
} else if *doUpdatePtr {
|
|
installDir = getInstallDirWhenManagedByOmaha()
|
|
extractAssets(installDir)
|
|
updateVersionInRegistry(isUserInstall)
|
|
removeOldVersions(installDir)
|
|
} else {
|
|
// Offline installer.
|
|
// We don't know whether we're being executed with Admin privileges.
|
|
// It's also not easy to determine. So perform user install:
|
|
isUserInstall = true
|
|
installDir = getDefaultInstallDir()
|
|
extractAssets(installDir)
|
|
createRegistryKeysForUninstaller(installDir, isUserInstall)
|
|
createStartMenuShortcut(installDir, isUserInstall)
|
|
launchAminal(installDir)
|
|
}
|
|
}
|
|
|
|
func getInstallDirWhenManagedByOmaha() string {
|
|
executablePath, err := winutil.GetExecutablePath()
|
|
check(err)
|
|
result := executablePath
|
|
prevResult := ""
|
|
for filepath.Base(result) != "Aminal" {
|
|
prevResult = result
|
|
result = filepath.Dir(result)
|
|
if result == prevResult {
|
|
break
|
|
}
|
|
}
|
|
if result == prevResult {
|
|
msg := "Could not find parent directory 'Aminal' above " + executablePath
|
|
check(errors.New(msg))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func getDefaultInstallDir() string {
|
|
localAppData := os.Getenv("LOCALAPPDATA")
|
|
if localAppData == "" {
|
|
panic("Environment variable LOCALAPPDATA is not set.")
|
|
}
|
|
return filepath.Join(localAppData, "Aminal")
|
|
}
|
|
|
|
func extractAssets(installDir string) {
|
|
for _, relPath := range data.AssetNames() {
|
|
bytes, err := data.Asset(relPath)
|
|
check(err)
|
|
absPath := filepath.Join(installDir, relPath)
|
|
check(os.MkdirAll(filepath.Dir(absPath), 0755))
|
|
f, err := os.OpenFile(absPath, os.O_CREATE, 0755)
|
|
check(err)
|
|
defer f.Close()
|
|
w := bufio.NewWriter(f)
|
|
_, err = w.Write(bytes)
|
|
check(err)
|
|
w.Flush()
|
|
}
|
|
}
|
|
|
|
func createRegistryKeysForUninstaller(installDir string, isUserInstall bool) {
|
|
regRoot := getRegistryRoot(isUserInstall)
|
|
uninstKey := `Software\Microsoft\Windows\CurrentVersion\Uninstall\Aminal`
|
|
writeRegStr(regRoot, uninstKey, "", installDir)
|
|
writeRegStr(regRoot, uninstKey, "DisplayName", "Aminal")
|
|
writeRegStr(regRoot, uninstKey, "Publisher", "Liam Galvin")
|
|
uninstaller := filepath.Join(installDir, "uninstall.exe")
|
|
uninstString := `"` + uninstaller + `"`
|
|
if isUserInstall {
|
|
uninstString += " /CurrentUser"
|
|
} else {
|
|
uninstString += " /AllUsers"
|
|
}
|
|
writeRegStr(regRoot, uninstKey, "UninstallString", uninstString)
|
|
}
|
|
|
|
func updateVersionInRegistry(isUserInstall bool) {
|
|
regRoot := getRegistryRoot(isUserInstall)
|
|
updateKey := `Software\Aminal\Update\Clients\` + ProductId
|
|
writeRegStr(regRoot, updateKey, "pv", Version+".0")
|
|
writeRegStr(regRoot, updateKey, "name", "Aminal")
|
|
}
|
|
|
|
func getRegistryRoot(isUserInstall bool) registry.Key {
|
|
if isUserInstall {
|
|
return registry.CURRENT_USER
|
|
}
|
|
return registry.LOCAL_MACHINE
|
|
}
|
|
|
|
func writeRegStr(regRoot registry.Key, keyPath string, valueName string, value string) {
|
|
const mode = registry.WRITE | registry.WOW64_32KEY
|
|
key, _, err := registry.CreateKey(regRoot, keyPath, mode)
|
|
check(err)
|
|
defer key.Close()
|
|
check(key.SetStringValue(valueName, value))
|
|
}
|
|
|
|
func createStartMenuShortcut(installDir string, isUserInstall bool) {
|
|
startMenuDir := getStartMenuDir(isUserInstall)
|
|
linkPath := filepath.Join(startMenuDir, "Programs", "Aminal.lnk")
|
|
targetPath := filepath.Join(installDir, "Aminal.exe")
|
|
createShortcut(linkPath, targetPath)
|
|
}
|
|
|
|
func getStartMenuDir(isUserInstall bool) string {
|
|
if isUserInstall {
|
|
usr, err := user.Current()
|
|
check(err)
|
|
return usr.HomeDir + `\AppData\Roaming\Microsoft\Windows\Start Menu`
|
|
} else {
|
|
return os.Getenv("ProgramData") + `\Microsoft\Windows\Start Menu`
|
|
}
|
|
}
|
|
|
|
func createShortcut(linkPath, targetPath string) {
|
|
type Shortcut struct {
|
|
LinkPath string
|
|
TargetPath string
|
|
}
|
|
tmpl := template.New("createLnk.vbs")
|
|
tmpl, err := tmpl.Parse(`Set oWS = WScript.CreateObject("WScript.Shell")
|
|
sLinkFile = "{{.LinkPath}}"
|
|
Set oLink = oWS.CreateShortcut(sLinkFile)
|
|
oLink.TargetPath = "{{.TargetPath}}"
|
|
oLink.Save
|
|
WScript.Quit 0`)
|
|
check(err)
|
|
tmpDir, err := ioutil.TempDir("", "Aminal")
|
|
check(err)
|
|
createLnk := filepath.Join(tmpDir, "createLnk.vbs")
|
|
defer os.RemoveAll(tmpDir)
|
|
f, err := os.Create(createLnk)
|
|
check(err)
|
|
defer f.Close()
|
|
w := bufio.NewWriter(f)
|
|
shortcut := Shortcut{linkPath, targetPath}
|
|
check(tmpl.Execute(w, shortcut))
|
|
w.Flush()
|
|
f.Close()
|
|
cmd := exec.Command("cscript", f.Name())
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
|
check(cmd.Run())
|
|
}
|
|
|
|
func launchAminal(installDir string) {
|
|
cmd := exec.Command(getAminalExePath(installDir))
|
|
check(cmd.Start())
|
|
}
|
|
|
|
func getAminalExePath(installDir string) string {
|
|
return filepath.Join(installDir, "Aminal.exe")
|
|
}
|
|
|
|
func removeOldVersions(installDir string) {
|
|
versionsDir := filepath.Join(installDir, "Versions")
|
|
versions, err := ioutil.ReadDir(versionsDir)
|
|
check(err)
|
|
for _, version := range versions {
|
|
if version.Name() == Version {
|
|
continue
|
|
}
|
|
versionPath := filepath.Join(versionsDir, version.Name())
|
|
// Try deleting the main executable first. We do this to prevent a
|
|
// version that is still running from being deleted.
|
|
mainExecutable := filepath.Join(versionPath, "Aminal.exe")
|
|
err = os.Remove(mainExecutable)
|
|
if err == nil {
|
|
// Remove the rest:
|
|
check(os.RemoveAll(versionPath))
|
|
}
|
|
}
|
|
}
|
|
|
|
func check(e error) {
|
|
if e != nil {
|
|
panic(e)
|
|
}
|
|
}
|