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

Changing icon in windows #91

Closed
brunobg opened this issue May 10, 2017 · 31 comments
Closed

Changing icon in windows #91

brunobg opened this issue May 10, 2017 · 31 comments

Comments

@brunobg
Copy link

brunobg commented May 10, 2017

I tried changing the icon with Resource hacker, but the resulting .exe does not run. Actually, it runs, but freezes immediately. Here's what I tried:

"C:/local/libs/Resource Hacker/resourcehacker.exe" -addoverwrite myapp-noicon.exe, myapp.exe, myicon.ico, ICONGROUP,MAINICON,0

Is there any sort of protection on pkg that would explain that behaviour?

@abhijitsdeshmukh
Copy link

abhijitsdeshmukh commented May 19, 2017

We would also like a fix for this issue

@igorklopov
Copy link
Contributor

igorklopov commented May 20, 2017

A temporary solution can be to apply resources not to final executables, but to base binaries located at .pkg-cache directory of your user profile. Please try.

@brunobg
Copy link
Author

brunobg commented May 23, 2017

What is the difference between the final executables and the base binaries?

@abhijitsdeshmukh
Copy link

abhijitsdeshmukh commented May 24, 2017

The temporary solution worked for us, looking forward to actual solution later.

@igorklopov igorklopov changed the title Freezes after changing icon in windows Changing icon in windows May 24, 2017
@igorklopov
Copy link
Contributor

igorklopov commented May 29, 2017

Fixed in master branch.

@igorklopov
Copy link
Contributor

igorklopov commented Jun 4, 2017

Fixed in pkg@4.

@ashking
Copy link

ashking commented Feb 3, 2018

Somehow running the exe with resource hacker on windows still corrupt the packaged file. But works well by updating the fetched-v8.9.0-win-x64 within the .pkg-cache directory.

@Jule-
Copy link

Jule- commented Feb 7, 2018

@igorklopov could you point the commit that should have fixed this issue in pkg@4 in order that someone could investigate?
Thanks!

EDIT: I use rcedit package on the executable and when I try to start it after icon change, I get the following error:

Pkg: Error reading from file.

@Jule-
Copy link

Jule- commented Feb 7, 2018

Like it seems that changing resources on .pkg-cache binaries works, maybe an easy improvement should be to let users pass rcedit options and, before pkg generation:

  1. copy the binary
  2. use rcedit on it
  3. generate executable with the temporary modified binary
  4. remove temporary binary

Since it seems that packer class is fetching offsets in binary files, maybe this is the only way to change stuff on the executable.
Can @igorklopov or someone with a good knowledge on pkg code bring some light on this?

@acailly
Copy link

acailly commented Feb 13, 2018

I made a custom build script to customize my executable. Maybe it will be useful for someone else 😉

const fs = require("fs");
const resourceHacker = require("node-resourcehacker");
const execSync = require("child_process").execSync;

const customIconPath = "icon.ico";
const resourceHackerPath = "resource_hacker.zip"; //This file must exist, if not download it at http://www.angusj.com/resourcehacker/resource_hacker.zip
const originalPkgPrecompiledBinaries =
  "./pkg-cache/v2.5/fetched-v6.11.5-win-x64.original";
const customizedPkgPrecompiledBinaries =
  "./pkg-cache/v2.5/fetched-v6.11.5-win-x64";

process.env["SOURCE_RESOURCE_HACKER"] = resourceHackerPath;

console.log("Download pkg precompiled libraries");
downloadOriginalPkgPrecompiledBinaries();
console.log("Customize pkg precompiled libraries");
customizePkgPrecompiledBinaries();
console.log("Build customized executables");
buildCustomizedExecutables();
console.log("Done");

function downloadOriginalPkgPrecompiledBinaries() {
  if (!fs.existsSync(originalPkgPrecompiledBinaries)) {
    executePkg("temp.exe");
    //Add .original extension
    fs.renameSync(
      customizedPkgPrecompiledBinaries,
      originalPkgPrecompiledBinaries
    );
    //Remove temp.exe
    fs.unlinkSync("temp.exe");
  }
}

function customizePkgPrecompiledBinaries() {
  resourceHacker(
    {
      operation: "addoverwrite",
      input: "./pkg-cache/v2.5/fetched-v6.11.5-win-x64",
      output: "./pkg-cache/v2.5/fetched-v6.11.5-win-x64",
      resource: customIconPath,
      resourceType: "ICONGROUP",
      resourceName: "1"
    },
    err => {
      if (err) {
        return console.error(err);
      }
    }
  );
}

function buildCustomizedExecutables() {
  executePkg("GeneUP-Thermal_Performance_Tool.exe");
}

function executePkg(exeName) {
  execSync(
    "yarn run cross-env PKG_CACHE_PATH=./pkg-cache pkg index.js --target win --output " +
      exeName
  );
}

@Jule-
Copy link

Jule- commented Feb 13, 2018

@acailly thanks for sharing!

@kspearrin
Copy link

kspearrin commented May 24, 2018

@igorklopov This is still broken in v4 , only way for it to work is by using resource hacker on the executable in pkg cache prior to building the exe.

@s-h-a-d-o-w
Copy link

s-h-a-d-o-w commented Jun 2, 2018

@kspearrin

I was able to replace the icon of the packaged exe using the Resource Hacker UI just fine.
Command line is a different story, however...

@acailly

Does this still work with the currently available versions of Resource Hacker?

Because all I get is:
Error: Command failed: D:\development\github\spotify-ad-blocker\node_modules\node-resourcehacker\ResourceHacker.exe -addoverwrite spotify-ad-blocker.exe, out.exe, spotify_ad_blocker_icon_hC4_icon.ico, ICONGROUP, 1,

The following works but corrupts the exe (maybe it's the name for the ICONGROUP. But the ResourceHacker CLI requires that to be specified...):
ResourceHacker.exe -open spotify-ad-blocker.exe -action addoverwrite -save out.exe -res spotify_ad_blocker_icon_hC4_icon.ico -mask ICONGROUP,Dummy,1,

@pionl
Copy link

pionl commented Oct 11, 2018

Hi all, I was checking how the pkg builds the binary and it seems that node.js builds the node.exe and then the pkg "transfers" the content into the executable. Thats why we have base metadata on the node.exe

Here a source code where you can find icon definition (+ metadata): https://github.com/nodejs/node/blob/8b4af64f50c5e41ce0155716f294c24ccdecad03/src/res/node.rc

The pkg would have to overwrite the node.rc file and build the node.exe on every build.

@acailly
Copy link

acailly commented Oct 11, 2018

@s-h-a-d-o-w I just downloaded the last version of ressource hacker and tried again, it worked

I don't have much time to investigate more on this since I don't work anymore on this project, sorry

@brunobg
Copy link
Author

brunobg commented Aug 29, 2019

Just updating with what currently works. First create an icon like explained on #151 (comment), with only 16x16, 32x32, 48x48, 64x64 and 128x128.

You can update the icon from command line with:

ResourceHacker.exe -open app-no-icon.exe -save app-icon.exe -action addoverwrite -res icon.ico -mask ICONGROUP,1 

This works with ResourceHacker 5.1.7 and pkg 4.4.0. RH works on Wine 4.0, but it requires an X display even when it supposedly runs on command line only. You can workaround with a Xvfb.

@lorki97
Copy link

lorki97 commented Sep 2, 2019

Just updating with what currently works. First create an icon like explained on #151 (comment), with only 16x16, 32x32, 48x48, 64x64 and 128x128.

You can update the icon from command line with:

ResourceHacker.exe -open app-no-icon.exe -save app-icon.exe -action addoverwrite -res icon.ico -mask ICONGROUP,1 

This works with ResourceHacker 5.1.7 and pkg 4.4.0. RH works on Wine 4.0, but it requires an X display even when it supposedly runs on command line only. You can workaround with a Xvfb.

I had to revert to pkg@4.3.4 to get icon change working again. pkg@4.4.0 results in Pkg: Error reading from file. after changing the icon with ResourceHacker 5.1.7.

@instafluff
Copy link

instafluff commented Oct 28, 2019

Same. I had to revert to pkg@4.3.7 to get it to work again while pkg@4.4.0 results in the same error as above.

@r1b
Copy link

r1b commented Jan 10, 2020

As @pionl mentioned above, the right way to create a branded executable is to build node with a custom resource file. You might get lucky and not break the pkg bootstrapping by editing the PE image after the fact but it will never work in general or across platforms. You can compile an rc file to a res on any system with GNU binutils.

@klugerama
Copy link

klugerama commented Jan 28, 2020

I'm using pkg@4.4.2 and this appears to still be an issue. Our solution is to download the binary, modify it first, then run pkg.exec:

(with inspiration from @acailly)

const resourceHackerPath = path.join(__dirname, "ResourceHacker.exe"); //This file must exist, if not download it at http://www.angusj.com/resourcehacker/resource_hacker.zip
const pkgCachePath = "./pkg-cache";
process.env["SOURCE_RESOURCE_HACKER"] = resourceHackerPath;
process.env["PKG_CACHE_PATH"] = pkgCachePath;

const execSync = require("child_process").execSync;

const fs = require("fs-extra");
const pkg = require("pkg");
const pkgfetch = require("pkg-fetch");

const customIconPath = "iconset.ico";
const specificNodeVersion = "12.13.1"; // this must be the full release version, and be supported by 
pkg https://github.com/zeit/pkg-fetch/blob/master/patches/patches.json
const originalPkgPrecompiledBinaries =`${pkgCachePath}/v2.6/fetched-v${specificNodeVersion}-win-x64.original`;
const customizedPkgPrecompiledBinaries =`${pkgCachePath}/v2.6/fetched-v${specificNodeVersion}-win-x64`;

async function downloadOriginalPkgPrecompiledBinaries() {
    if (!fs.existsSync(originalPkgPrecompiledBinaries)) {
        await pkgfetch.need({nodeRange:`node${specificNodeVersion}`,platform:"win",arch:"x64"});
    }
}

async function customizePkgPrecompiledBinariesVersionInfo(s) {
    execSync(`${resourceHackerPath} -open ./resources/WinSW/${s}/version-info.rc -save ./resources/WinSW/${s}/version-info.res -action compile`);
    execSync(`${resourceHackerPath} -open ./pkg-cache/v2.6/fetched-v${specificNodeVersion}-win-x64.original -save ./pkg-cache/v2.6/fetched-v${specificNodeVersion}-win-x64 -resource ./resources/WinSW/${s}/version-info.res -action addoverwrite`);
}
async function customizePkgPrecompiledBinariesIcon() {
    execSync(`${resourceHackerPath} -open ./pkg-cache/v2.6/fetched-v${specificNodeVersion}-win-x64.original -save ./pkg-cache/v2.6/fetched-v${specificNodeVersion}-win-x64 -resource ${customIconPath} -action addoverwrite -mask ICONGROUP,1,`);
}
async function customizePkgPrecompiledBinaries(s) {
    await customizePkgPrecompiledBinariesVersionInfo(s);
    await fs.rename(
        customizedPkgPrecompiledBinaries,
        originalPkgPrecompiledBinaries
        );
    await customizePkgPrecompiledBinariesIcon();
}
  
async function package() {
    console.log("Download pkg precompiled libraries");
    await downloadOriginalPkgPrecompiledBinaries();
    for (let s of ["Agent", "Server"]) {
        await _pack(s); // runs pkg.exec to actually create the packaged .exe files
    }
}

@nihatyldz42
Copy link

nihatyldz42 commented Jul 16, 2020

This is a big shortcoming.

@artemcarabash
Copy link

artemcarabash commented Jul 17, 2020

as an option generate exe and also customize version-info.rc file using batch:
https://stackoverflow.com/a/58716971/4887252
just as a workaround, maybe it will be useful to someone

@ExordiumX
Copy link

ExordiumX commented Jun 17, 2021

The temporary solution worked for us, looking forward to actual solution later.

Binary hash does NOT match. Re-fetching...

Doesn't work anymore.

@mehadh
Copy link

mehadh commented Aug 7, 2021

The temporary solution worked for us, looking forward to actual solution later.

Binary hash does NOT match. Re-fetching...

Doesn't work anymore.

seconding this, anyone have a solution?

as an option generate exe and also customize version-info.rc file using batch:
https://stackoverflow.com/a/58716971/4887252
just as a workaround, maybe it will be useful to someone

this still works well for changing version info, but how can we change icon?

@fatman-
Copy link

fatman- commented Aug 14, 2021

We've built a solution, similar to the ones as described by @acailly and @klugerama; for changing the icon, and the file metadata (version, description, etc...) of the final executable by first updating the precompiled node binaries.

I'll try to describe the workflow -- which works perfectly for us -- as best as I can.

We're using pkg v4.5.1 as of this writing.


Let's take a quick peek at the codebase's directory structure, so you can better comprehend the code that follows.

Directory structure

node-project/
├── node_modules
├── ...
├── dist/
├── └── ... (THIS IS WHERE THE FINAL EXECUTABLE WOULD LAND)
├── ...
├── pkg/
├── ├── .pkg-cache/
├── ├── └── ... (THIS IS WHERE THE PRECOMPILED NODE BINARY WOULD LAND)
├── ├── resources/
├── │   ├── ResourceHacker/ResourceHacker.exe
├── │   ├── icon.ico
├── ├── config/
├── │   ├── pkg.server.json
├── │   └── version-info.server.rc
├── ├── index.js
├── └── server.js
├── ...
├── server/
├── ├── ...
├── └── index.js (THIS IS THE MAIN ENTRY POINT, FOR PKG)
├── ...
└──  package.json

Notice that we have a custom pkg-cache folder; this is where our node binaries would land at; without polluting the default .pkg-cache folder. The node binary used to created our final executable for the codebase can be updated (file metadata, and file icon) using ResourceHacker.exe, as many times as we would want to. Both these facts render the need for maintaining an "original" node binary unnecessary.

pkg/index.js exports a function called package that can be used on any number of entry/index points; to create any number of executables; all with their custom file metadata, and icons.

For example, for creating our "server" executable, following is how pkg/server.js looks.

pkg/server.js

const path = require('path');
const package = require('./index.js');

package({
	indexPath: path.join(__dirname, '../server/index.js'),
	pkgConfigPath: path.join(__dirname, './config/pkg.server.json'),
	versionInfoPath: path.join(__dirname, './config/version-info.server'), // The missing extension is intended...
	iconPath: path.join(__dirname, './resources/infinity-m.ico'),
	exeBuildPath: path.join(__dirname, '../dist/appcited.exe'),
});

To create the server executable, we have positioned a simple npm script called pkg:server.

package.json

{
	...
	"scripts": {
		...
		"start": "npm run build && node ./server/index.js",
		"pkg:server": "npm run build && node ./pkg/server.js",
		...
	},
	...
}

The function package (exported by pkg/index.js) enables us to have different configurations in the same codebase. For instance, we can maintain pkg/secondServer.js (similar to pkg/server.js) to create an executable for our "secondServer"; with its own corresponding entry/index point, and config files -- ../secondServer/index.js, ./config/pkg.secondServer.json, ./config/version-info.secondServer.rc.

Now let's look at pkg/index.js, and the function -- package -- that it exports.

pkg/index.js

const fs = require('fs');
const path = require('path');
const execSync = require('child_process').execSync;

const del = require('del');

const resourceHackerPath = path.join(__dirname, './resources/ResourceHacker/ResourceHacker.exe');
process.env['PKG_CACHE_PATH'] = path.join(__dirname, './resources/pkg-cache');

const pkg = require('pkg');
const pkgFetch = require('pkg-fetch');

function runResourceHacker(argumentValueMap) {
	return execSync(
		`${resourceHackerPath} ${Object.entries(argumentValueMap)
			.map(([key, value]) => `-${key} ${value}`)
			.join(' ')}`
	);
}

function customiseNodeBinaryVersionInfo(nodeBinaryPath, customVersionInfoPath) {
	runResourceHacker({
		action: 'compile',
		open: `${customVersionInfoPath}.rc`,
		save: `${customVersionInfoPath}.res`,
	});
	runResourceHacker({
		resource: `${customVersionInfoPath}.res`,
		action: 'addoverwrite',
		open: nodeBinaryPath,
		save: nodeBinaryPath,
	});
}
function customiseNodeBinaryIcon(nodeBinaryPath, customIconPath) {
	runResourceHacker({
		resource: customIconPath,
		mask: 'ICONGROUP,1,',
		action: 'addoverwrite',
		open: nodeBinaryPath,
		save: nodeBinaryPath,
	});
}

function customiseNodeBinary(nodeBinaryPath, customVersionInfoPath, customIconPath) {
	if (customVersionInfoPath) {
		customiseNodeBinaryVersionInfo(nodeBinaryPath, customVersionInfoPath);
	}
	if (customIconPath) {
		customiseNodeBinaryIcon(nodeBinaryPath, customIconPath);
	}
}

async function package(
	paths,
	_target = { nodeRange: 'node14.4.0', platform: 'win', arch: 'x86' },
	options = { fetchFreshNodeBinary: false }
) {
	const target = {
		..._target,
		toString() {
			return `${this.nodeRange}-${this.platform}-${this.arch}`;
		},
		getBinaryFilename() {
			return `${this.nodeRange.replace('node', 'fetched-v')}-${this.platform}-${this.arch}`;
		},
		getBinaryPath() {
			return `${process.env['PKG_CACHE_PATH']}/v2.6/${this.getBinaryFilename()}`;
		},
	};

	const { indexPath, pkgConfigPath, versionInfoPath, iconPath, exeBuildPath } = paths;

	const nodeBinaryPath = target.getBinaryPath();

	// Clean slate...
	del([exeBuildPath, ...(options.fetchFreshNodeBinary ? [nodeBinaryPath] : [])], { force: true });

	if (!fs.existsSync(nodeBinaryPath)) {
		await pkgFetch.need(_target);
	}

	customiseNodeBinary(nodeBinaryPath, versionInfoPath, iconPath);
	await pkg.exec([indexPath, '--target', target.toString(), '--config', pkgConfigPath, '--output', exeBuildPath]);
}

module.exports = package;

Finally, for the sake of reference, here's how the version-info-server.rc file looks.

pkg/config/version-info-server.rc

1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEOS 0x4
FILETYPE 0x2
{
	BLOCK "StringFileInfo"
	{
		BLOCK "040904b0"
		{
			VALUE "FileDescription", "Application Communication Interface for Telemed Equipment and Devices"
			VALUE "OriginalFilename", "appcited.exe"
			VALUE "CompanyName", "MGRM, Inc."
			VALUE "FileVersion", "1,0,0,0"
			VALUE "LegalCopyright", "Copyright © MGRM"
			VALUE "ProductName", "AppCITED"
			VALUE "ProductVersion", "1,0,0,0"
		}
	}

	BLOCK "VarFileInfo"
	{
		VALUE "Translation", 0x0409 0x04B0
	}
}

Don't forget to .gitignore the following build-time files.

.gitignore

**/node_modules
...
pkg/resources/pkg-cache
pkg/resources/ResourceHacker/ResourceHacker.ini
pkg/config/version-info*.res

@StiliyanKushev
Copy link

StiliyanKushev commented Jan 10, 2022

Hi there! I just want to simply point out that NONE of the above work anymore because of the commit 629cc71d268509ceb242440b89ffe2279a14a89c which adds a check for the file hashes of the prebuilt binaries.
That means that anytime you modify the prebuilt binary and run pkg, it's going to print out "Binary hash does NOT match. Re-fetching..." and do exactly that. May I suggest a workaround using a command-line argument or an environment variable to disable that check?

[NOTE] For anyone wondering what versions he/she should downgrade to, any pkg-fetch version lower than 3.0.3 and any pkg version lower than or equal to 5.0.0 will be fine.

@Tylerjet
Copy link

Tylerjet commented Feb 2, 2022

Found this over in the issues of patch package for those who might come to find a workaround from the update.

vercel/pkg-fetch#188 (comment)

@gorankarlic
Copy link

gorankarlic commented Feb 11, 2022

Summary of workaround from vercel/pkg-fetch#188:

  1. Use fetched-v... binary, change icon with resource hacker, and save the modified file as built-v... (in same directory)
  2. Use --build option and pkg will skip the hash check and use the modified built-v... binary

@aximut
Copy link

aximut commented Apr 9, 2022

A simpler, more universal version with fewer hard-coded params, updated to the recent changes:

const fs = require("fs");
const execSync = require("child_process").execSync;
const rcedit = require('rcedit');

const pkgver = "v3.2";
const nodever = "v16.13.2";

const customIconPath = "icon.ico";
const originalPkgPrecompiledBinaries =
    `./pkg-cache/${pkgver}/fetched-${nodever}-win-x64`;
const customizedPkgPrecompiledBinaries =
    `./pkg-cache/${pkgver}/built-${nodever}-win-x64`;

console.log("Download pkg precompiled libraries");
downloadOriginalPkgPrecompiledBinaries();
console.log("Customize pkg precompiled libraries");
customizePkgPrecompiledBinaries();
console.log("Build customized executables");
buildCustomizedExecutables();
console.log("Done");

function downloadOriginalPkgPrecompiledBinaries() {
    if (!fs.existsSync(customizedPkgPrecompiledBinaries)) {
        executePkg("temp.exe");
        // Add .original extension
        fs.renameSync(
            originalPkgPrecompiledBinaries,
            customizedPkgPrecompiledBinaries
        );
        // Remove temp.exe
        fs.unlinkSync("temp.exe");
    }
}

function customizePkgPrecompiledBinaries() {
    rcedit(customizedPkgPrecompiledBinaries, {
        icon: "assets/icon.ico"
    });
}

function buildCustomizedExecutables() {
    executePkg();
}

function executePkg(exeName) {
    execSync(
        `yarn run cross-env PKG_CACHE_PATH=./pkg-cache pkg . --compress Brotli --target win ${exeName && `--output ${exeName}` || ''}`
    );
}

@krenni1
Copy link

krenni1 commented Apr 27, 2022

From a legal point of view - is it OK to overwrite just any file property of a bundled Node.js application (e.g. version, description, copyright, original filename)?

@topsarus
Copy link

topsarus commented May 8, 2022

Summary of workaround from vercel/pkg-fetch#188:

  1. Use fetched-v... binary, change icon with resource hacker, and save the modified file as built-v... (in same directory)
  2. Use --build option and pkg will skip the hash check and use the modified built-v... binary

That worked perfectly to me, thanks!

@jesec jesec mentioned this issue Jun 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests