From 2c3d8c2ec3322e813b56e17f58953704f9a11046 Mon Sep 17 00:00:00 2001 From: develar Date: Wed, 17 Aug 2016 20:02:51 +0200 Subject: [PATCH] feat(nsis): per-machine installer automatically removes old version #621 --- src/appInfo.ts | 6 +- src/winPackager.ts | 2 +- templates/nsis/boringInstaller.nsh | 12 ++-- templates/nsis/install.nsh | 66 +++++++++++++++++ templates/nsis/installer.nsi | 72 ++----------------- templates/nsis/multiUser.nsh | 22 ++---- templates/nsis/multiUserUi.nsh | 16 ++--- templates/nsis/oneClick.nsh | 14 +--- templates/nsis/uninstaller.nsh | 23 +++--- .../app-executable-deps/app/index.html | 2 +- test/fixtures/test-app-one/index.html | 10 +++ test/fixtures/test-app-one/index.js | 28 +++++--- test/fixtures/test-app/app/index.js | 47 ++++-------- 13 files changed, 150 insertions(+), 170 deletions(-) create mode 100644 templates/nsis/install.nsh diff --git a/src/appInfo.ts b/src/appInfo.ts index 75b0da7903a..92ca7239b51 100644 --- a/src/appInfo.ts +++ b/src/appInfo.ts @@ -14,6 +14,7 @@ export class AppInfo { readonly buildNumber: string readonly buildVersion: string + readonly productName: string readonly productFilename: string constructor(public metadata: AppMetadata, private devMetadata: DevMetadata, buildVersion?: string | null) { @@ -32,6 +33,7 @@ export class AppInfo { this.buildVersion = buildVersion! } + this.productName = getProductName(this.metadata, this.devMetadata) this.productFilename = sanitizeFileName(this.productName) } @@ -68,10 +70,6 @@ export class AppInfo { return metadata.category || old } - get productName(): string { - return getProductName(this.metadata, this.devMetadata) - } - get copyright(): string { const metadata = this.devMetadata.build const old = (metadata)["app-copyright"] diff --git a/src/winPackager.ts b/src/winPackager.ts index febe17bf0f1..a3146addcdd 100644 --- a/src/winPackager.ts +++ b/src/winPackager.ts @@ -154,7 +154,7 @@ export class WinPackager extends PlatformPackager { "--set-version-string", "CompanyName", appInfo.companyName, "--set-version-string", "FileDescription", appInfo.description, "--set-version-string", "ProductName", appInfo.productName, - "--set-version-string", "InternalName", appInfo.productName, + "--set-version-string", "InternalName", path.basename(appInfo.productFilename, ".exe"), "--set-version-string", "LegalCopyright", appInfo.copyright, "--set-version-string", "OriginalFilename", "", "--set-file-version", appInfo.buildVersion, diff --git a/templates/nsis/boringInstaller.nsh b/templates/nsis/boringInstaller.nsh index 8ba73347839..a7faf555275 100644 --- a/templates/nsis/boringInstaller.nsh +++ b/templates/nsis/boringInstaller.nsh @@ -22,7 +22,7 @@ !insertmacro MUI_UNPAGE_INSTFILES !endif -!macro initMultiUser UNINSTALLER_FUNCPREFIX +!macro initMultiUser !insertmacro UAC_PageElevation_OnInit ${If} ${UAC_IsInnerInstance} @@ -60,17 +60,17 @@ ${endif} ${if} $hasPerUserInstallation == "1" - ${andif} $hasPerMachineInstallation == "0" - Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + ${andif} $hasPerMachineInstallation == "0" + !insertmacro setInstallModePerUser ${elseif} $hasPerUserInstallation == "0" ${andif} $hasPerMachineInstallation == "1" - Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !insertmacro setInstallModePerAllUsers ${else} # if there is no installation, or there is both per-user and per-machine !ifdef INSTALL_MODE_PER_ALL_USERS - Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !insertmacro setInstallModePerAllUsers !else - Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + !insertmacro setInstallModePerUser !endif ${endif} !macroend diff --git a/templates/nsis/install.nsh b/templates/nsis/install.nsh new file mode 100644 index 00000000000..6e11f041a16 --- /dev/null +++ b/templates/nsis/install.nsh @@ -0,0 +1,66 @@ +${IfNot} ${Silent} + SetDetailsPrint none + + !ifdef ONE_CLICK + !ifdef HEADER_ICO + SpiderBanner::Show /MODERN /ICON "$PLUGINSDIR\installerHeaderico.ico" + !else + SpiderBanner::Show /MODERN + !endif + !endif +${endif} + +!insertmacro CHECK_APP_RUNNING "install" + +${if} $installMode == "all" + ReadRegStr $R0 HKEY_LOCAL_MACHINE "${UNINSTALL_REGISTRY_KEY}" UninstallString + ${if} $R0 != "" + ExecWait "$R0 /S" + ${endif} +${endif} + +RMDir /r $INSTDIR +SetOutPath $INSTDIR + +!ifdef APP_64 + ${If} ${RunningX64} + Nsis7z::Extract "$PLUGINSDIR\app-64.7z" + ${Else} + Nsis7z::Extract "$PLUGINSDIR\app-32.7z" + ${endif} +!else + Nsis7z::Extract "$PLUGINSDIR\app-32.7z" +!endif + +File "/oname=${UNINSTALL_FILENAME}" "${UNINSTALLER_OUT_FILE}" + +!insertmacro registryAddInstallInfo + +StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" +StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" + +# create shortcuts in the start menu and on the desktop +# shortcut for uninstall is bad cause user can choose this by mistake during search, so, we don't add it +CreateShortCut "$startMenuLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" +CreateShortCut "$desktopLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" + +WinShell::SetLnkAUMI "$startMenuLink" "${APP_ID}" +WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}" + +!ifmacrodef registerFileAssociations + !insertmacro registerFileAssociations +!endif + +!ifmacrodef customInstall + !insertmacro customInstall +!endif + +${IfNot} ${Silent} + !ifdef ONE_CLICK + # otherwise app window will be in backround + HideWindow + !ifdef RUN_AFTER_FINISH + Call StartApp + !endif + !endif +${EndIf} \ No newline at end of file diff --git a/templates/nsis/installer.nsi b/templates/nsis/installer.nsi index d2ae571ef7e..6511c0beded 100644 --- a/templates/nsis/installer.nsi +++ b/templates/nsis/installer.nsi @@ -28,7 +28,7 @@ Function .onInit Quit !else !insertmacro check64BitAndSetRegView - !insertmacro initMultiUser "" + !insertmacro initMultiUser !ifdef ONE_CLICK !insertmacro ALLOW_ONLY_ONE_INSTALLER_INSTACE @@ -59,70 +59,12 @@ Function .onInit !endif FunctionEnd -!ifndef BUILD_UNINSTALLER - Section "install" - ${IfNot} ${Silent} - SetDetailsPrint none - - !ifdef ONE_CLICK - !ifdef HEADER_ICO - SpiderBanner::Show /MODERN /ICON "$PLUGINSDIR\installerHeaderico.ico" - !else - SpiderBanner::Show /MODERN - !endif - !endif - ${EndIf} - - !insertmacro CHECK_APP_RUNNING "install" - - RMDir /r $INSTDIR - SetOutPath $INSTDIR - - !ifdef APP_64 - ${If} ${RunningX64} - Nsis7z::Extract "$PLUGINSDIR\app-64.7z" - ${Else} - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" - ${EndIf} - !else - Nsis7z::Extract "$PLUGINSDIR\app-32.7z" - !endif - - File "/oname=${UNINSTALL_FILENAME}" "${UNINSTALLER_OUT_FILE}" - - !insertmacro registryAddInstallInfo - - StrCpy $startMenuLink "$SMPROGRAMS\${PRODUCT_FILENAME}.lnk" - StrCpy $desktopLink "$DESKTOP\${PRODUCT_FILENAME}.lnk" - - # create shortcuts in the start menu and on the desktop - # shortcut for uninstall is bad cause user can choose this by mistake during search, so, we don't add it - CreateShortCut "$startMenuLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" - CreateShortCut "$desktopLink" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0 "" "" "${APP_DESCRIPTION}" - - WinShell::SetLnkAUMI "$startMenuLink" "${APP_ID}" - WinShell::SetLnkAUMI "$desktopLink" "${APP_ID}" - - !ifmacrodef registerFileAssociations - !insertmacro registerFileAssociations - !endif - - !ifmacrodef customInstall - !insertmacro customInstall - !endif +Section "install" + !ifndef BUILD_UNINSTALLER + !include "install.nsh" + !endif +SectionEnd - ${IfNot} ${Silent} - !ifdef ONE_CLICK - # otherwise app window will be in backround - HideWindow - !ifdef RUN_AFTER_FINISH - Call StartApp - !endif - !endif - ${EndIf} - SectionEnd -!else - Section - SectionEnd +!ifdef BUILD_UNINSTALLER !include "uninstaller.nsh" !endif \ No newline at end of file diff --git a/templates/nsis/multiUser.nsh b/templates/nsis/multiUser.nsh index 6d0c6559ee8..463563465c6 100644 --- a/templates/nsis/multiUser.nsh +++ b/templates/nsis/multiUser.nsh @@ -7,7 +7,7 @@ !define UNINSTALL_REGISTRY_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_GUID}" !define UNINSTALL_DISPLAY_NAME "${PRODUCT_NAME} ${VERSION}" -# current Install Mode ("AllUsers" or "CurrentUser") +# current Install Mode ("all" or "CurrentUser") Var installMode !ifndef INSTALL_MODE_PER_ALL_USERS @@ -28,7 +28,7 @@ Var installMode System::Call '*$2(&w${NSIS_MAX_STRLEN} .r1)' StrCpy $0 $1 System::Call 'Ole32::CoTaskMemFree(ir2)' - ${EndIf} + ${endif} StrCpy $INSTDIR "$0\${PRODUCT_FILENAME}" !endif @@ -38,21 +38,13 @@ Var installMode StrCpy $INSTDIR $perUserInstallationFolder ${endif} !macroend - - !ifndef BUILD_UNINSTALLER - Function installMode.CurrentUser - !insertmacro setInstallModePerUser - FunctionEnd - !endif !endif !ifdef INSTALL_MODE_PER_ALL_USERS_REQUIRED Var perMachineInstallationFolder !macro setInstallModePerAllUsers - # Install mode initialization - per-machine - StrCpy $installMode AllUsers - + StrCpy $installMode all SetShellVarContext all StrCpy $INSTDIR "$PROGRAMFILES\${PRODUCT_FILENAME}" @@ -63,12 +55,6 @@ Var installMode StrCpy $INSTDIR $perMachineInstallationFolder ${endif} !macroend - - !ifndef BUILD_UNINSTALLER - Function installMode.AllUsers - !insertmacro setInstallModePerAllUsers - FunctionEnd - !endif !endif # SHCTX is the hive HKLM if SetShellVarContext all, or HKCU if SetShellVarContext user @@ -77,7 +63,7 @@ Var installMode WriteRegStr SHCTX "${INSTALL_REGISTRY_KEY}" InstallLocation "$INSTDIR" # Write the uninstall keys for Windows - ${if} $installMode == "AllUsers" + ${if} $installMode == "all" WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" DisplayName "${UNINSTALL_DISPLAY_NAME}" WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" UninstallString '"$INSTDIR\${UNINSTALL_FILENAME}" /allusers' ${else} diff --git a/templates/nsis/multiUserUi.nsh b/templates/nsis/multiUserUi.nsh index 98c702509b7..aed0ccdf502 100755 --- a/templates/nsis/multiUserUi.nsh +++ b/templates/nsis/multiUserUi.nsh @@ -33,20 +33,20 @@ Var RadioButtonLabel1 ${If} ${UAC_IsInnerInstance} ${AndIf} ${UAC_IsAdmin} # inner Process (and Admin) - skip selection, inner process is always used for elevation (machine-wide) - Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !insertmacro setInstallModePerAllUsers Abort ${EndIf} ${GetParameters} $R0 ${GetOptions} $R0 "/allusers" $R1 ${IfNot} ${Errors} - Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !insertmacro setInstallModePerAllUsers Abort ${EndIf} ${GetOptions} $R0 "/currentuser" $R1 ${IfNot} ${Errors} - Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + !insertmacro setInstallModePerUser Abort ${EndIf} @@ -56,11 +56,11 @@ Var RadioButtonLabel1 !ifdef BUILD_UNINSTALLER ${if} $hasPerUserInstallation == "1" ${andif} $hasPerMachineInstallation == "0" - Call un.installMode.CurrentUser + !insertmacro setInstallModePerUser Abort ${elseif} $hasPerUserInstallation == "0" ${andif} $hasPerMachineInstallation == "1" - Call un.installMode.AllUsers + !insertmacro setInstallModePerAllUsers Abort ${endif} @@ -113,7 +113,7 @@ Var RadioButtonLabel1 ${NSD_CreateLabel} 0u 110u 280u 50u "" Pop $RadioButtonLabel1 - ${if} $installMode == "AllUsers" ; setting defaults + ${if} $installMode == "all" SendMessage $MultiUser.InstallModePage.AllUsers ${BM_SETCHECK} ${BST_CHECKED} 0 ; set as default SendMessage $MultiUser.InstallModePage.AllUsers ${BM_CLICK} 0 0 ; trigger click event ${else} @@ -130,7 +130,7 @@ Var RadioButtonLabel1 ${if} $MultiUser.InstallModePage.ReturnValue = ${BST_CHECKED} ${if} ${UAC_IsAdmin} - Call ${UNINSTALLER_FUNCPREFIX}installMode.AllUsers + !insertmacro setInstallModePerAllUsers ${else} !ifdef MULTIUSER_INSTALLMODE_ALLOW_ELEVATION GetDlgItem $9 $HWNDParent 1 @@ -158,7 +158,7 @@ Var RadioButtonLabel1 !endif ${endif} ${else} - Call ${UNINSTALLER_FUNCPREFIX}installMode.CurrentUser + !insertmacro setInstallModePerUser ${endif} !insertmacro MUI_PAGE_FUNCTION_CUSTOM LEAVE diff --git a/templates/nsis/oneClick.nsh b/templates/nsis/oneClick.nsh index 47c47a525b5..fed866a7a92 100644 --- a/templates/nsis/oneClick.nsh +++ b/templates/nsis/oneClick.nsh @@ -25,18 +25,10 @@ AutoCloseWindow true RequestExecutionLevel user !endif -!macro initMultiUser UNINSTALLER_FUNCPREFIX +!macro initMultiUser !ifdef INSTALL_MODE_PER_ALL_USERS - !ifdef BUILD_UNINSTALLER - Call un.installMode.AllUsers - !else - Call installMode.AllUsers - !endif + !insertmacro setInstallModePerAllUsers !else - !ifdef BUILD_UNINSTALLER - Call un.installMode.CurrentUser - !else - Call installMode.CurrentUser - !endif + !insertmacro setInstallModePerUser !endif !macroend \ No newline at end of file diff --git a/templates/nsis/uninstaller.nsh b/templates/nsis/uninstaller.nsh index 7710bb305cf..2fcba4000b8 100644 --- a/templates/nsis/uninstaller.nsh +++ b/templates/nsis/uninstaller.nsh @@ -1,15 +1,3 @@ -!ifndef INSTALL_MODE_PER_ALL_USERS - Function un.installMode.CurrentUser - !insertmacro setInstallModePerUser - FunctionEnd -!endif - -!ifdef INSTALL_MODE_PER_ALL_USERS_REQUIRED - Function un.installMode.AllUsers - !insertmacro setInstallModePerAllUsers - FunctionEnd -!endif - Function un.onInit !insertmacro check64BitAndSetRegView @@ -22,9 +10,9 @@ Function un.onInit !insertmacro CHECK_APP_RUNNING "uninstall" SetSilent silent !endif - ${EndIf} + ${endIf} - !insertmacro initMultiUser un. + !insertmacro initMultiUser !ifmacrodef customUnInit !insertmacro customUnInit @@ -59,7 +47,14 @@ Section "un.install" ${GetParameters} $R0 ${GetOptions} $R0 "/KEEP_APP_DATA" $R1 ${If} ${Errors} + # electron always uses per user app data + ${if} $installMode == "all" + SetShellVarContext current + ${endif} RMDir /r "$APPDATA\${PRODUCT_FILENAME}" + ${if} $installMode == "all" + SetShellVarContext all + ${endif} ${EndIf} DeleteRegKey SHCTX "${UNINSTALL_REGISTRY_KEY}" diff --git a/test/fixtures/app-executable-deps/app/index.html b/test/fixtures/app-executable-deps/app/index.html index da8a27a9be2..e2cbd70a9c3 100644 --- a/test/fixtures/app-executable-deps/app/index.html +++ b/test/fixtures/app-executable-deps/app/index.html @@ -7,7 +7,7 @@

Press the button to show a notification

- + diff --git a/test/fixtures/test-app-one/index.html b/test/fixtures/test-app-one/index.html index 8d710f08e6d..a50eff39f33 100644 --- a/test/fixtures/test-app-one/index.html +++ b/test/fixtures/test-app-one/index.html @@ -3,11 +3,21 @@ Hello World! +

Hello World!

We are using node , Chrome , and Electron . + + and Electron . + + diff --git a/test/fixtures/test-app-one/index.js b/test/fixtures/test-app-one/index.js index 5b76d2c73ee..54b22666dbc 100644 --- a/test/fixtures/test-app-one/index.js +++ b/test/fixtures/test-app-one/index.js @@ -1,6 +1,9 @@ 'use strict' -const app = require('electron').app +const electron = require("electron") +const app = electron.app +const fs = require("fs") +const path = require("path") // this should be placed at top of main.js to handle setup events quickly if (handleSquirrelEvent()) { @@ -14,7 +17,6 @@ function handleSquirrelEvent() { } const ChildProcess = require('child_process'); - const path = require('path'); const appFolder = path.resolve(process.execPath, '..'); const rootAtomFolder = path.resolve(appFolder, '..'); @@ -70,7 +72,6 @@ function handleSquirrelEvent() { } } -const electron = require('electron'); // Module to control application life. // Module to create native browser window. const BrowserWindow = electron.BrowserWindow; @@ -89,7 +90,8 @@ function createWindow () { // Open the DevTools. mainWindow.webContents.openDevTools(); - mainWindow.webContents.executeJavaScript(`console.log("appData: ${app.getPath("appData")}")`) + mainWindow.webContents.executeJavaScript(`console.log("appData: ${app.getPath("appData").replace(/\\/g, "\\\\")}")`) + mainWindow.webContents.executeJavaScript(`console.log("userData: ${app.getPath("userData").replace(/\\/g, "\\\\")}")`) // Emitted when the window is closed. mainWindow.on('closed', function() { @@ -113,10 +115,18 @@ app.on('window-all-closed', function () { } }); -app.on('activate', function () { - // On MacOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. +app.on("activate", function () { if (mainWindow === null) { - createWindow(); + createWindow() } -}); +}) + +electron.ipcMain.on("saveAppData", () => { + try { + // electron doesn't escape / in the product name + fs.writeFileSync(path.join(app.getPath("appData"), "Test App ßW", "testFile"), "test") + } + catch (e) { + mainWindow.webContents.executeJavaScript(`console.log(\`userData: ${e}\`)`) + } +}) \ No newline at end of file diff --git a/test/fixtures/test-app/app/index.js b/test/fixtures/test-app/app/index.js index 2c7080ba45a..e8ddab327ef 100644 --- a/test/fixtures/test-app/app/index.js +++ b/test/fixtures/test-app/app/index.js @@ -1,51 +1,32 @@ -'use strict'; +"use strict" -const electron = require('electron'); -// Module to control application life. -const app = electron.app; -// Module to create native browser window. -const BrowserWindow = electron.BrowserWindow; +const electron = require('electron') +const app = electron.app +const BrowserWindow = electron.BrowserWindow -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow; +let mainWindow function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({width: 800, height: 600}); + mainWindow = new BrowserWindow({width: 800, height: 600}) + mainWindow.loadURL('file://' + __dirname + '/index.html') - // and load the index.html of the app. - mainWindow.loadURL('file://' + __dirname + '/index.html'); + mainWindow.webContents.openDevTools() - // Open the DevTools. - mainWindow.webContents.openDevTools(); - - // Emitted when the window is closed. mainWindow.on('closed', function() { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null; + mainWindow = null }); } -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -app.on('ready', createWindow); +app.on('ready', createWindow) -// Quit when all windows are closed. app.on('window-all-closed', function () { - // On MacOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q if (process.platform !== 'darwin') { - app.quit(); + app.quit() } -}); +}) app.on('activate', function () { - // On MacOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. if (mainWindow === null) { - createWindow(); + createWindow() } -}); +}) \ No newline at end of file