Skip to content

Commit

Permalink
write toast activator CLSID in shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
bitdisaster committed Mar 24, 2021
1 parent becf1dc commit 501ff71
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 5 deletions.
42 changes: 42 additions & 0 deletions __tests__/creator-spec.ts
Expand Up @@ -557,3 +557,45 @@ describe('perUser install by default', () => {

testIncludes('correct defautlt perUser setting', '<Property Id="MSIINSTALLPERUSER" Secure="yes" Value="1" />');
});

describe('shortcut properties', () => {
test('MSICreator includes default shortcut properties', async () => {
const msiCreator = new MSICreator({
...defaultOptions,
});
const { wxsFile } = await msiCreator.create();
wxsContent = await fs.readFile(wxsFile, 'utf-8');
expect(wxsFile).toBeTruthy();
});
testIncludes('a default appUserModelId', '<ShortcutProperty Key="System.AppUserModel.ID" Value="com.squirrel.Acme.acme"');
testIncludesNot('a ToastActivatorCLSID', '<ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID"');

test('MSICreator includes custom shortcut properties ', async () => {
const msiCreator = new MSICreator({
...defaultOptions,
appUserModelId: 'com.squirrel.myapp.myapp',
toastActivatorClsid: 'd3519df4-76a7-412f-bd73-9d7746d1d757'
});
const { wxsFile } = await msiCreator.create();
wxsContent = await fs.readFile(wxsFile, 'utf-8');
expect(wxsFile).toBeTruthy();
});

testIncludes('a default appUserModelId', '<ShortcutProperty Key="System.AppUserModel.ID" Value="com.squirrel.myapp.myapp"/>');
testIncludes('a ToastActivatorCLSID', '<ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID" Value="{d3519df4-76a7-412f-bd73-9d7746d1d757}"/>');

test('MSICreator includes toast activator with brackets', async () => {
const msiCreator = new MSICreator({
...defaultOptions,
appUserModelId: 'com.squirrel.myapp.myapp',
toastActivatorClsid: '{d3519df4-76a7-412f-bd73-9d7746d1d757}'
});
const { wxsFile } = await msiCreator.create();
wxsContent = await fs.readFile(wxsFile, 'utf-8');
expect(wxsFile).toBeTruthy();
});

testIncludes('the ToastActivatorCLSID', '<ShortcutProperty Key="System.AppUserModel.ToastActivatorCLSID" Value="{d3519df4-76a7-412f-bd73-9d7746d1d757}"/>');
});


2 changes: 2 additions & 0 deletions e2e/src/e2e-auto-launch.ts
Expand Up @@ -8,6 +8,7 @@ import { getProcessPath, kill, launch, runs } from './utils/app-process';
import { checkInstall, getInstallPaths, install, uninstall, uninstallViaPowershell } from './utils/installer';
import { createMsiPackage, defaultMsiOptions, HARNESS_APP_DIR, OUT_DIR } from './utils/msi-packager';
import { getRegistryKeyValue } from './utils/registry';
import { sleep } from './utils/util';

const msiPath = path.join(OUT_DIR, 'HelloWix.msi');
const autoLaunchMsiOptions = {
Expand Down Expand Up @@ -95,6 +96,7 @@ describe('MSI auto-launch', () => {
it(`runs the correct binary via ${entryPoint.name}`, async () => {
await launch(entryPoint.path);
expect(await runs(msiOptions.exe)).ok();
await sleep(1000);
expect(await getProcessPath(msiOptions.exe)).to.be(msiPaths123beta.appExe);
await kill(msiOptions.exe);
});
Expand Down
2 changes: 2 additions & 0 deletions e2e/src/e2e-auto-updater.ts
Expand Up @@ -11,6 +11,7 @@ import { hasAccessRights } from './utils/ntfs';
import { getRegistryKeyValue, registryKeyExists } from './utils/registry';
import { cleanSquirrelOutDir, createSquirrelPackage, defaultSquirrelOptions, OUT_SQRL_DIR } from './utils/squirrel-packager';
import { serveSquirrel, stopServingSquirrel } from './utils/squirrel-server';
import { sleep } from './utils/util';

interface TestConfig {
arch: 'x86' | 'x64';
Expand Down Expand Up @@ -212,6 +213,7 @@ describe('MSI auto-updating', () => {
const paths = shouldUpdate ? squirrelPaths130 : msiPaths123beta;
await launch(entryPoint.path);
expect(await runs(msiOptions.exe)).ok();
await sleep(1000);
expect(await getProcessPath(msiOptions.exe)).to.be(paths.appExe);
await kill(msiOptions.exe);
});
Expand Down
14 changes: 13 additions & 1 deletion e2e/src/e2e-msi.ts
Expand Up @@ -6,8 +6,9 @@ import { getWindowsCompliantVersion } from '../../lib/utils/version-util';
import { expectSameFolderContent } from './common';
import { getProcessPath, kill, launch, runs } from './utils/app-process';
import { checkInstall, getInstallPaths, install, uninstall, uninstallViaPowershell } from './utils/installer';
import { readAppUserModelId } from './utils/lnk-inspector';
import { readAppUserModelId, readToastActivatorCLSID } from './utils/lnk-inspector';
import { createMsiPackage, defaultMsiOptions, HARNESS_APP_DIR, OUT_DIR } from './utils/msi-packager';
import { sleep } from './utils/util';

const msiPath = path.join(OUT_DIR, 'HelloWix.msi');

Expand All @@ -16,6 +17,7 @@ interface TestConfig {
shortcutFolderName?: string;
shortcutName?: string;
appUserModelId?: string;
toastActivatorClsid?: string;
}

describe('Electron WIX MSI', () => {
Expand All @@ -31,6 +33,7 @@ describe('Electron WIX MSI', () => {
const tests: TestConfig[] = [
{arch: 'x86'},
{arch: 'x86', shortcutFolderName: 'SuperWix', shortcutName: 'SuperHello', appUserModelId: 'com.wix.super.hello'},
{arch: 'x86', appUserModelId: 'com.wix.super.hello', toastActivatorClsid: '7dfb947c-7f80-4472-b777-8e83b8dac298'},
{arch: 'x64'},
];

Expand All @@ -51,6 +54,7 @@ describe('Electron WIX MSI', () => {
configString += test.shortcutName || '';
}
configString += test.appUserModelId ? `|aumid:${test.appUserModelId}` : '';
configString += test.toastActivatorClsid ? `|toastCLSID:${test.toastActivatorClsid}` : '';

return `(${configString})`;
};
Expand Down Expand Up @@ -92,6 +96,13 @@ describe('Electron WIX MSI', () => {
expect(aumid).to.be(paths.appUserModelId);
});

it('has correct ToastActivatorCLSID', async () => {
const clsid = await readToastActivatorCLSID(paths.startMenuShortcut);
console.log(clsid);
console.log(paths.toastActivatorClsid);
expect(clsid).to.be(paths.toastActivatorClsid);
});

const entryPoints = [
{ name: 'stubExe', path: paths.stubExe },
{ name: 'start menu shortcut', path: paths.startMenuShortcut },
Expand All @@ -102,6 +113,7 @@ describe('Electron WIX MSI', () => {
it(`runs the correct binary via ${entryPoint.name}`, async () => {
await launch(entryPoint.path);
expect(await runs(msiOptions.exe)).ok();
await sleep(1000);
expect(await getProcessPath(msiOptions.exe)).to.be(paths.appExe);
await kill(msiOptions.exe);
});
Expand Down
2 changes: 2 additions & 0 deletions e2e/src/e2e-per-user.ts
Expand Up @@ -8,6 +8,7 @@ import { getProcessPath, kill, launch, runs } from './utils/app-process';
import { checkInstall, getInstallPaths, install, uninstall, uninstallViaPowershell } from './utils/installer';
import { createMsiPackage, defaultMsiOptions, HARNESS_APP_DIR, OUT_DIR } from './utils/msi-packager';
import { getRegistryKeyValue } from './utils/registry';
import { sleep } from './utils/util';

interface TestConfig {
arch: 'x86' | 'x64';
Expand Down Expand Up @@ -105,6 +106,7 @@ describe.only('MSI perUser install', () => {
it(`runs the correct binary via ${entryPoint.name}`, async () => {
await launch(entryPoint.path);
expect(await runs(msiOptions.exe)).ok();
await sleep(1000);
expect(await getProcessPath(msiOptions.exe)).to.be(msiPaths123beta.appExe);
await kill(msiOptions.exe);
});
Expand Down
Binary file modified e2e/src/utils/bin/Lnkspector.exe
Binary file not shown.
4 changes: 4 additions & 0 deletions e2e/src/utils/installer.ts
Expand Up @@ -22,6 +22,7 @@ export interface InstallPaths {
startMenuShortcut: string;
desktopShortcut: string;
appUserModelId: string;
toastActivatorClsid: string;
registryRunKey: string;
registryUninstallKey: string;
registryAutoUpdateKey: string;
Expand Down Expand Up @@ -110,13 +111,15 @@ export const checkInstall = async (name: string, version?: string) => {

export const getInstallPaths = (options: MSICreatorOptions | SquirrelOptions,
installMode: 'perUser' | 'perMachine' = 'perMachine'): InstallPaths => {
const emptyGuid = '00000000-0000-0000-0000-000000000000';
const arch = options.arch;
let programFiles = arch === 'x86' ? process.env['ProgramFiles(x86)']! : process.env.ProgramFiles!;
programFiles = installMode === 'perMachine' ? programFiles : `${process.env['LOCALAPPDATA']}\\Programs`;
const appRootFolder = path.join(programFiles, options.name!);
const shortName = isMSICreatorOptions(options) ? options.shortName || options.name : options.name;
const genericAumid = `com.squirrel.${shortName}.${options.exe!.replace(/\.exe$/, '')}`;
const appUserModelId = isMSICreatorOptions(options) ? options.appUserModelId || genericAumid : genericAumid;
const toastActivatorClsid = isMSICreatorOptions(options) ? options.toastActivatorClsid || emptyGuid : emptyGuid;
const registryRoot = installMode === 'perMachine' ? 'HKLM' : 'HKCU';
const registryWow = arch === 'x86' && installMode === 'perMachine' ? 'WOW6432Node\\' : '';
const registryRunKey = `${registryRoot}:\\SOFTWARE\\${registryWow}Microsoft\\Windows\\CurrentVersion\\Run`;
Expand All @@ -135,6 +138,7 @@ export const getInstallPaths = (options: MSICreatorOptions | SquirrelOptions,
startMenuShortcut: isMSICreatorOptions(options) ? path.join(startMenuRoot, `${options.shortcutFolderName || options.manufacturer}/${options.shortcutName || options.name}.lnk`) : '',
desktopShortcut: isMSICreatorOptions(options) ? path.join(home, `Desktop/${options.shortcutName || options.name}.lnk`) : '',
appUserModelId,
toastActivatorClsid,
registryRunKey,
registryUninstallKey,
registryAutoUpdateKey
Expand Down
17 changes: 14 additions & 3 deletions e2e/src/utils/lnk-inspector.ts
@@ -1,8 +1,19 @@
import path from 'path';
import { spawnPromise } from 'spawn-rx';

export const readAppUserModelId = async (lnkPath: string) => {
const readShortcut = async (lnkPath: string) => {
const lnkspector = path.join(__dirname, '../../src/utils/bin/Lnkspector.exe');
const aumid = await spawnPromise(lnkspector, ['-aumid', lnkPath]);
return aumid.replace(/(\r\n|\n|\r)/gm, '');
const json = await spawnPromise(lnkspector, [lnkPath]);
return JSON.parse(json);
};

export const readAppUserModelId = async (lnkPath: string) => {
const shortcut = await readShortcut(lnkPath);
return shortcut.AppUserModelId;
};

export const readToastActivatorCLSID = async (lnkPath: string) => {
const shortcut = await readShortcut(lnkPath);
return shortcut.ToastActivatorCLSID;
// return toastActivatorClsid !== '00000000-0000-0000-000-000000000000' ? toastActivatorClsid : undefined;
};
1 change: 1 addition & 0 deletions e2e/src/utils/util.ts
@@ -0,0 +1 @@
export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
1 change: 1 addition & 0 deletions harness/harness.js
Expand Up @@ -20,6 +20,7 @@ async function harness() {
appIconPath: path.join(APP_DIR, '../HelloWix.ico'),
outputDirectory: OUT_DIR,
description: 'A hello wix package',
toastActivatorClsid: '808ba5f6-12a4-4175-8cfe-9c10a6b1bab6',
ui: {
chooseDirectory: true
},
Expand Down
30 changes: 30 additions & 0 deletions src/creator.ts
Expand Up @@ -34,6 +34,7 @@ const debug = require('debug')('electron-wix-msi');
export interface MSICreatorOptions {
appDirectory: string;
appUserModelId?: string;
toastActivatorClsid?: string;
description: string;
exe: string;
appIconPath?: string;
Expand Down Expand Up @@ -99,13 +100,15 @@ export class MSICreator {
public updaterTemplate = getTemplate('updater-feature', true);
public updaterPermissions = getTemplate('updater-permissions');
public autoLaunchTemplate = getTemplate('auto-launch-feature', true);
public shortcutPropertyTemplate = getTemplate('shortcut-property', true);

// State, overwritable beteween steps
public wxsFile: string = '';

// Configuration
public appDirectory: string;
public appUserModelId: string;
public toastActivatorClsid?: string;
public description: string;
public exe: string;
public iconPath?: string;
Expand Down Expand Up @@ -174,6 +177,7 @@ export class MSICreator {

this.appUserModelId = options.appUserModelId
|| `com.squirrel.${this.shortName}.${this.exe}`.toLowerCase();
this.toastActivatorClsid = options.toastActivatorClsid;

this.ui = options.ui !== undefined ? options.ui : false;
this.autoUpdate = false;
Expand Down Expand Up @@ -263,6 +267,13 @@ export class MSICreator {
const { chooseDirectory } = this.ui;
enableChooseDirectory = chooseDirectory || false;
}
const shortcutProperties = [ {key: 'System.AppUserModel.ID', value: this.appUserModelId } ];
if (this.toastActivatorClsid) {
shortcutProperties.push({
key: 'System.AppUserModel.ToastActivatorCLSID',
value: this.toastActivatorClsid.match(/^{.*}$/) ?
this.toastActivatorClsid : `{${this.toastActivatorClsid }}` });
}

const scaffoldReplacements = {
'<!-- {{ComponentRefs}} -->': componentRefs.map(({ xml }) => xml).join('\n'),
Expand All @@ -273,6 +284,8 @@ export class MSICreator {
'<!-- {{AutoLaunchFeature}} -->': this.autoLaunch ? this.autoLaunchTemplate : '{{remove newline}}',
'<!-- {{UpdaterComponentRefs}} -->': updaterComponentRefs.map(({ xml }) => xml).join('\n'),
'<!-- {{AutoLaunchComponentRefs}} -->': autoLaunchComponentRefs.map(({ xml }) => xml).join('\n'),
'<!-- {{ShortcutProperties}} -->': shortcutProperties.map(({key, value}) =>
this.getShortcutProperty(key, value)).join('\n'),
};

const replacements = {
Expand Down Expand Up @@ -594,6 +607,23 @@ export class MSICreator {
return { guid, componentId: registry.id, xml, featureAffinity: registry.featureAffinity || 'main' };
}

/**
* Creates Wix shortcut property.
*
* @param {key}
* @param {value}
* @returns {xml}
*/
private getShortcutProperty(key: string, value: string): string {

const xml = replaceInString(this.shortcutPropertyTemplate, {
'{{ShortcutPropertyKey}}': key,
'{{ShortcutPropertyValue}}': value,
});

return xml;
}

/**
* Creates a usable component id to use with Wix "id" fields
*
Expand Down
1 change: 1 addition & 0 deletions static/shortcut-property.xml
@@ -0,0 +1 @@
<ShortcutProperty Key="{{ShortcutPropertyKey}}" Value="{{ShortcutPropertyValue}}"/>
2 changes: 1 addition & 1 deletion static/wix.xml
Expand Up @@ -108,7 +108,7 @@
Description="{{ApplicationDescription}}"
Target="[APPLICATIONROOTDIRECTORY]{{ApplicationBinary}}.exe"
WorkingDirectory="APPLICATIONROOTDIRECTORY">
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{AppUserModelId}}" />
<!-- {{ShortcutProperties}} -->
</Shortcut>
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
<RegistryValue Root="HKCU"
Expand Down

0 comments on commit 501ff71

Please sign in to comment.