Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No auto updater by default #82

Merged
merged 4 commits into from
May 25, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions __tests__/creator-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,9 @@ regexTestIncludes('versioned app folder', /<Directory\s*Id=".*"\s*Name="app-1\.0
regexTestIncludes('stubbed exe', /<File\s*Name="acme\.exe"\s*Id=".*"\s*Source="C:\\Stub\.exe"/);

test('.wxs file has as many components as we have files', () => {
// Files + 2 Shortcuts + 3 special files + 7 registry + 1 purge components
// Files + 2 Shortcuts + 2 special files + 7 registry + 1 purge components
const count = wxsContent.split('</Component>').length - 1;
expect(count).toEqual(numberOfFiles + 13);
expect(count).toEqual(numberOfFiles + 12);
});

test('MSICreator create() creates Wix file with UI properties', async () => {
Expand Down Expand Up @@ -458,7 +458,7 @@ testIncludesNot('RegistryRunKey component', '<Component Id="RegistryRunKey"');
testIncludesNot('RegistryRunKey component-ref', '<ComponentRef Id="RegistryRunKey" />');
regexTestIncludesNot('AutoLaunch feature', /<Feature Id="AutoLaunch" Title="Launch On Login" Level="2" .*>/);
regexTestIncludesNot('AutoUpdater feature', /<Feature Id="AutoUpdater" Title="Auto Updater" Level="3" .*>/);
regexTestIncludes('Squirrel executable component-ref', /<ComponentRef Id="_msq.exe_.*" \/>/ );
regexTestIncludesNot('Squirrel executable component-ref', /<ComponentRef Id="_msq.exe_.*" \/>/ );
testIncludesNot('Permission component-ref', `<ComponentRef Id="SetFolderPermissions" />`);
testIncludesNot('RegistryRunKey component-ref', '<ComponentRef Id="SetUninstallDisplayVersionPermissions" />');

Expand Down Expand Up @@ -511,9 +511,9 @@ describe('auto-launch', () => {
});

test('.wxs file has as many components as we have files', () => {
// Files + 2 Shortcuts + 3 special files + 8 registry + 1 purge components
// Files + 2 Shortcuts + 2 special files + 8 registry + 1 purge components
const count = wxsContent.split('</Component>').length - 1;
expect(count).toEqual(numberOfFiles + 14);
expect(count).toEqual(numberOfFiles + 13);
});

test('.wxs file contains as many component refs as components', () => {
Expand Down
131 changes: 92 additions & 39 deletions e2e/src/e2e-auto-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import path from 'path';
import { getWindowsCompliantVersion } from '../../lib/utils/version-util';
import { expectSameFolderContent } from './common';
import { getProcessPath, kill, launch, runs } from './utils/app-process';
import { autoUpdate, checkInstall, getInstallPaths, install, installFeature, uninstall, uninstallViaPowershell } from './utils/installer';
import { autoUpdate, checkInstall, getInstallPaths, install, uninstall, uninstallViaPowershell } from './utils/installer';
import { createMsiPackage, defaultMsiOptions, HARNESS_APP_DIR, OUT_DIR } from './utils/msi-packager';
import { hasAccessRights } from './utils/ntfs';
import { getRegistryKeyValue } from './utils/registry';
import { getRegistryKeyValue, registryKeyExists } from './utils/registry';
import { cleanSquirrelOutDir, createSquirrelPackage, defaultSquirrelOptions, OUT_SQRL_DIR } from './utils/squirrel-packager';
import { serveSquirrel, stopServingSquirrel } from './utils/squirrel-server';

Expand Down Expand Up @@ -74,56 +74,103 @@ describe('MSI auto-updating', () => {
});

const installConfigs = [
{ userGroup: undefined, effectiveUserGroup: 'Users' },
{ userGroup: 'Guests', effectiveUserGroup: 'Guests' },
{ userGroup: undefined,
effectiveUserGroup: 'Users',
autoUpdateInstalled: true,
updatesEnabled: true,
targetVersion: squirrelOptions130.version },
{ userGroup: undefined,
effectiveUserGroup: 'Users',
autoUpdateInstalled: true,
updatesEnabled: false,
targetVersion: msiOptions.version },
{ userGroup: undefined,
effectiveUserGroup: 'Users',
autoUpdateInstalled: false,
updatesEnabled: true,
targetVersion: msiOptions.version },
{ userGroup: 'Guests',
effectiveUserGroup: 'Guests',
autoUpdateInstalled: true,
updatesEnabled: true,
targetVersion: squirrelOptions130.version },
{ userGroup: 'Guests',
effectiveUserGroup: 'Guests',
autoUpdateInstalled: true,
updatesEnabled: false,
targetVersion: msiOptions.version },
{ userGroup: 'Guests',
effectiveUserGroup: 'Guests',
autoUpdateInstalled: false,
updatesEnabled: false,
targetVersion: msiOptions.version },
];

installConfigs.forEach((config) => {
describe((`userGroup:${config.effectiveUserGroup}`), () => {
describe((`autoUpdateInstalled: ${config.autoUpdateInstalled}, updateEnabled: ${config.updatesEnabled}, userGroup:${config.effectiveUserGroup}`), () => {
const shouldUpdate = config.autoUpdateInstalled && config.updatesEnabled;

it(`installs (userGroup: ${config.effectiveUserGroup})`, async () => {
await install(msiPath, 2, config.userGroup);
const installLevel = config.autoUpdateInstalled ? 3 : 2;
await install(msiPath, installLevel, config.userGroup, 'perMachine', config.updatesEnabled);
const version = getWindowsCompliantVersion(msiOptions.version);
expect(await checkInstall(`${msiOptions.name} (Machine)`, msiOptions.version)).ok();
expect(await checkInstall(`${msiOptions.name} (Machine - MSI)`, version)).ok();
});

it(`doesn't auto-updates when auto-update disabled (userGroup: ${config.effectiveUserGroup})`, async () => {
const server = serveSquirrel(OUT_SQRL_DIR);
await autoUpdate(msiPaths123beta.updateExe, server);
stopServingSquirrel();
expect(await checkInstall(`${msiOptions.name} (Machine)`, msiOptions.version)).ok();
});

it(`installs auto-updater (userGroup: ${config.effectiveUserGroup})`, async () => {
await installFeature(msiPath, 'AutoUpdater', config.userGroup);
const version = getWindowsCompliantVersion(msiOptions.version);
expect(await checkInstall(`${msiOptions.name} (Machine)`, msiOptions.version)).ok();
expect(await checkInstall(`${msiOptions.name} (Machine - MSI)`, version)).ok();
const regValue = await getRegistryKeyValue(msiPaths123beta.registryAutoUpdateKey, 'AutoUpdate');
expect(regValue).to.be('1');
});
it(`has ${config.autoUpdateInstalled ? 'correct' : 'no'} auto-update registry key (userGroup: ${config.effectiveUserGroup})`,
async () => {
const regKey = `${msiPaths123beta.registryAutoUpdateKey}`;
if (config.autoUpdateInstalled && config.updatesEnabled) {
const regValue = await getRegistryKeyValue(regKey, 'AutoUpdate');
expect(regValue).to.be('1');
} else if (config.autoUpdateInstalled && !config.updatesEnabled) {
const regValue = await getRegistryKeyValue(regKey, 'AutoUpdate');
expect(regValue).to.be('0');
} else {
const regValue = await registryKeyExists(regKey);
expect(regValue).not.ok();
}
});

it(`auto-updates (userGroup: ${config.effectiveUserGroup})`, async () => {
const server = serveSquirrel(OUT_SQRL_DIR);
await autoUpdate(msiPaths123beta.updateExe, server);
stopServingSquirrel();
expect(await checkInstall(`${msiOptions.name} (Machine)`, squirrelOptions130Config.version)).ok();
it(`${shouldUpdate ? '' : 'does not '}auto-update (userGroup: ${config.effectiveUserGroup})`,
async () => {
if (shouldUpdate) {
const server = serveSquirrel(OUT_SQRL_DIR);
await autoUpdate(msiPaths123beta.updateExe, server);
stopServingSquirrel();
expect(await checkInstall(`${msiOptions.name} (Machine)`, squirrelOptions130Config.version)).ok();
} else if (config.autoUpdateInstalled) {
const server = serveSquirrel(OUT_SQRL_DIR);
await autoUpdate(msiPaths123beta.updateExe, server);
stopServingSquirrel();
expect(await checkInstall(`${msiOptions.name} (Machine)`, msiOptions.version)).ok();
} else {
expect(fs.pathExistsSync(msiPaths123beta.updateExe)).not.ok();
expect(await checkInstall(`${msiOptions.name} (Machine)`, msiOptions.version)).ok();
}
});

it(`has all files in program files (userGroup: ${config.effectiveUserGroup})`, () => {
const paths = shouldUpdate ? squirrelPaths130 : msiPaths123beta;
expect(fs.pathExistsSync(msiPaths123beta.stubExe)).ok();
expect(fs.pathExistsSync(squirrelPaths130.appFolder)).ok();
expectSameFolderContent(HARNESS_APP_DIR, squirrelPaths130.appFolder);
expect(fs.pathExistsSync(paths.appFolder)).ok();
expectSameFolderContent(HARNESS_APP_DIR, paths.appFolder);
});

it(`has access rights (userGroup: ${config.effectiveUserGroup})`, async () => {
const x = await hasAccessRights(squirrelPaths130.appRootFolder, config.effectiveUserGroup);
expect(x).ok();
it(`has ${config.autoUpdateInstalled ? '' : 'no '}access rights (userGroup: ${config.effectiveUserGroup})`
, async () => {
const x = await hasAccessRights(squirrelPaths130.appRootFolder, config.effectiveUserGroup);
if (config.autoUpdateInstalled) {
expect(x).ok();
} else {
expect(x).not.ok();
}
});

const regValues = [
{name: 'DisplayName', value: `${msiOptions.name} (Machine)`},
{name: 'DisplayVersion', value: squirrelOptions130Config.version},
{name: 'DisplayVersion', value: config.targetVersion },
{name: 'InstallPath', value: `${msiPaths123beta.appRootFolder}\\`},
{name: 'Publisher', value: msiOptions.manufacturer},
];
Expand All @@ -136,11 +183,17 @@ describe('MSI auto-updating', () => {
});
});

it(`has called msq self-update (userGroup: ${config.effectiveUserGroup})`, () => {
const selfUpdateLog = path.join(squirrelPaths130.appFolder, 'MSQ-UpdateSelf.log');
expect(fs.pathExistsSync(selfUpdateLog)).ok();
const logContent = fs.readFileSync(selfUpdateLog, 'utf-8');
expect(logContent.includes('--updateSelf')).ok();
it(`has ${shouldUpdate ? '' : 'not '}called msq self-update (userGroup: ${config.effectiveUserGroup})`
, () => {
if (shouldUpdate) {
const selfUpdateLog = path.join(squirrelPaths130.appFolder, 'MSQ-UpdateSelf.log');
expect(fs.pathExistsSync(selfUpdateLog)).ok();
const logContent = fs.readFileSync(selfUpdateLog, 'utf-8');
expect(logContent.includes('--updateSelf')).ok();
} else {
const selfUpdateLog = path.join(squirrelPaths130.appFolder, 'MSQ-UpdateSelf.log');
expect(fs.pathExistsSync(selfUpdateLog)).not.ok();
}
});

it(`has shortcuts (userGroup: ${config.effectiveUserGroup})`, () => {
Expand All @@ -156,9 +209,10 @@ describe('MSI auto-updating', () => {

entryPoints.forEach(async (entryPoint) => {
it(`runs the correct binary via ${entryPoint.name}`, async () => {
const paths = shouldUpdate ? squirrelPaths130 : msiPaths123beta;
await launch(entryPoint.path);
expect(await runs(msiOptions.exe)).ok();
expect(await getProcessPath(msiOptions.exe)).to.be(squirrelPaths130.appExe);
expect(await getProcessPath(msiOptions.exe)).to.be(paths.appExe);
await kill(msiOptions.exe);
});
});
Expand All @@ -167,7 +221,6 @@ describe('MSI auto-updating', () => {
await uninstall(msiPath);
expect(await checkInstall(`${msiOptions.name} (Machine)`)).not.ok();
expect(await checkInstall(`${msiOptions.name} (Machine - MSI)`)).not.ok();
console.log(msiPaths123beta.appRootFolder);
expect(fs.pathExistsSync(msiPaths123beta.appRootFolder)).not.ok();
expect(fs.pathExistsSync(msiPaths123beta.startMenuShortcut)).not.ok();
expect(fs.pathExistsSync(msiPaths123beta.desktopShortcut)).not.ok();
Expand Down
5 changes: 4 additions & 1 deletion e2e/src/utils/installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface InstallPaths {
registryAutoUpdateKey: string;
}

export const install = async (msi: string, installLevel: 1 | 2 | 3 = 2, autoUpdaterUserGroup?: string, installMode?: 'perUser' | 'perMachine') => {
export const install = async (msi: string, installLevel: 1 | 2 | 3 = 2, autoUpdaterUserGroup?: string, installMode?: 'perUser' | 'perMachine', updatesEnabled?: boolean) => {
const args = ['/i', msi];

if (installLevel !== 2) {
Expand All @@ -42,6 +42,9 @@ export const install = async (msi: string, installLevel: 1 | 2 | 3 = 2, autoUpda
if (installMode === 'perUser') {
args.push(`MSIINSTALLPERUSER=1`);
}
if (updatesEnabled === undefined || updatesEnabled !== true) {
args.push(`AUTOUPDATEENABLED=0`);
}
args.push('/qb');
return spawnPromise('msiexec.exe', args);
};
Expand Down
20 changes: 17 additions & 3 deletions e2e/src/utils/registry.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
import Shell from 'node-powershell';

export const getRegistryKeyValue = async (registryPath: string, registryKey: string) => {
export const getRegistryKeyValue = async (registryKey: string, registryValue: string) => {
const ps = new Shell({
executionPolicy: 'Bypass',
noProfile: true
});
let result = '';
try {
ps.addCommand(`Get-ItemPropertyValue '${registryPath}' -Name ${registryKey}`);
ps.addCommand(`Get-ItemPropertyValue '${registryKey}' -Name ${registryValue}`);
result = await ps.invoke();
} finally {
ps.dispose();
}

return result.replace(/(\r\n|\n|\r)/gm, '');
};

export const registryKeyExists = async (registryKey: string) => {
const ps = new Shell({
executionPolicy: 'Bypass',
noProfile: true
});
let result = '';
try {
ps.addCommand(`Test-Path '${registryKey}'`);
result = await ps.invoke();
} finally {
ps.dispose();
}
return result.replace(/(\r\n|\n|\r)/gm, '') === 'True';
};
16 changes: 9 additions & 7 deletions src/creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,12 +610,14 @@ export class MSICreator {
// injects an information file that helps the installed app to verify info about the installation
specialFiles.push({ name: `.installInfo.json`, path: installInfoFile });

// inject the Squirrel updater into the root directory
specialFiles.push({
name: `Update.exe`,
path: path.join(__dirname, '../vendor/msq.exe'),
featureAffinity: 'main'
});
if (this.autoUpdate) {
// inject the Squirrel updater into the root directory
specialFiles.push({
name: `Update.exe`,
path: path.join(__dirname, '../vendor/msq.exe'),
featureAffinity: 'autoUpdate'
});
}

return specialFiles;
}
Expand Down Expand Up @@ -723,7 +725,7 @@ export class MSICreator {
name: 'AutoUpdate',
key: productRegKey,
type: 'integer',
value: '1',
value: '[AUTOUPDATEENABLED]',
bitdisaster marked this conversation as resolved.
Show resolved Hide resolved
featureAffinity: 'autoUpdate',
forceDeleteOnUninstall: 'yes'
});
Expand Down
4 changes: 4 additions & 0 deletions static/wix.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
the install folder in cas the auto-updater is installed. User that run the App
must be part of that user group to be able to auto-update. -->
<Property Id="UPDATERUSERGROUP" Value="Users" />
<!-- A property to define whether the auto-updater is enabled when the
feature gets installed. This way the update can be installed but also be disabled
by overwriting the default value. -->
<Property Id="AUTOUPDATEENABLED" Value="1" />
<!-- Necessary registry search to find the install path which is used by the
PurgeOnUninstall action. Since this package can be installed perUser or perMachine,
we have to look in both places. First successful search wins. -->
Expand Down