Skip to content

Electron-builder's installer "safe directory" issue #6885

Description

@PerfectionVR

We’ve chosen to use electron-builder to create the installer because it’s easy to integrate. However, there is a small, mostly-inconsequential buglet concerning how it chooses its install directory.

By default, it’ll use the productName as its leaf install directory. So you get something like %PROGRAMFILES%/${productName}.
This is fine, unless you want to install somewhere else, like %SOMEPLACE%/foo. If that directory doesn’t exist yet, everything works fine. However, on the second install, it’ll install into %SOMEPLACE%/foo/${productName}. This is because electron-builder has an empty-directory check, which will append productName to the requested directory if the latter isn’t empty.

It shouldn’t just do an ‘empty’ check, but also see if this dir is the current install dir.

The reason I stumbled on this is that I’d prefer to have the directory be named “example-project”. This can be done by using productName="example-project", but then that also shows up in the text of the installer. I’d prefer that to be “Example Project”, but then the directories get weird.

For w use “example-project”, but we would like to know how this can be resolved.

Details:

node -v # v16.15.0
yarn -v # 1.22.15

Windows

package.json

{
  "name": "example-project",
  "description": "Example Project",
  "version": "0.0.1",
  "license": "MIT",
  "scripts": {
    "build:start": "node ./scripts/run-script",
    "build:clean": "rimraf .dist",
    "build:prepare": "node ./build.prepare.js",
    "build:main": "webpack --config webpack.config.main.js --mode=production",
    "build:renderer": "webpack --config webpack.config.renderer.js --mode=development",
    "build:main:dev": "webpack --watch --mode=development --config webpack.config.main.js",
    "build:renderer:dev": "webpack serve --port 3000 --mode=development --config webpack.config.renderer.js",
    "build:compile": "yarn electron-builder",
    "build": "yarn build:clean && npm-run-all --parallel build:prepare build:main build:renderer && yarn build:compile",
    "dev": "yarn build:clean && npm-run-all --parallel build:main:dev build:renderer:dev",
    "dev:start": "nodemon --watch ./.dist/www/index.js --exec electron ./.dist/www/index.js",
  },
  "dependencies": {
    "axios": "^0.27.2",
    "lodash": "^4.17.21",
    "mobx": "^6.5.0",
    "mobx-react": "^7.4.0",
    "mobx-utils": "^6.0.5",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
  },
  "devDependencies": {
    "electron": "^18.2.4",
    "electron-builder": "^23.0.3",
  }
}

electron-build.yml

appId: 'com.example.app'
artifactName: 'ExampleSetup.${ext}'
productName: 'example-project'
copyright: '${author} All rights reserved.'
directories:
  output: './.dist/app'
  app: './.dist/www'
extraResources:
  - ./icons/**/*
icon: './icons/icon.ico'
nsis:
  allowToChangeInstallationDirectory: true
  include: 'scripts/installer.nsh'
  oneClick: false
  perMachine: true
  runAfterFinish: false
  warningsAsErrors: false # off b/c RmTrailingSlash gives a warning
  # TODO? name the uninstaller "uninstall.exe", but can't find the option for it.

scripts/installer.nsh

!define ClientRegKey "SOFTWARE\Example\Client"
!define ProductRegKey "SOFTWARE\Example\Cloud GUI"

; SP[0] in/out	path
Function RmTrailingSlash
	ClearErrors
	Exch $R1 ; <path>
	Push $R2
	
	StrCpy $R2 "$R1" "" -1 ; R2 gets the last char
	StrCmp $R2 "\" 0 NoSlash ; check if last char is '\'
		StrCpy $R2 "$R1" -1 ; last char was '\', remove it
		Goto Done
	NoSlash:
		StrCpy $R2 $R1
	Done:
	
	Exch
	Pop $R1
	Exch $R2
FunctionEnd

!macro customHeader
	;Default installation folder. May be overwritten by .onInit
	InstallDir "$PROGRAMFILES\Example\example-project"

	; InstallDirRegKey is overwritten by main electron-builder script
	;InstallDirRegKey HKCU "${ProductRegKey}" ""
!macroend

!macro customInit
	; If ProductRegKey doesn't exist but ClientRegKey does, use that as install dir.
	ReadRegStr $0 HKCU "${ProductRegKey}" ""
	${If} "$0" == ""
		ReadRegStr $0 HKCU "${ClientRegKey}" ""
		${If} "$0" != ""
			Push $0
			Call RmTrailingSlash
			Pop $0
			StrCpy $INSTDIR "$0\example-project"
		${EndIf}
	${EndIf}
!macroend

!macro customInstall
	; Write installdir to Example registry. NOTE! This is not the key that the installer uses!
	WriteRegStr HKCU "${ProductRegKey}" "" "$INSTDIR"
!macroend

!macro customUninstall
	DeleteRegKey HKCU "${ProductRegKey}"
!macroend

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions