Fix agent metadata (agentFileInfo), the ProductVersion and FileVersion.
Ylian Saint-Hilaire committed
Aug 4, 2022 at 01:47 UTC
b760bb61162a672eb52d8aa455086f300b3ecd8b
5 files changed
+95
-16
authenticode.js
+44
-5
@@ -787,6 +787,8 @@ function createAuthenticodeHandler(path) {
787
if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return null;
788
const strings = StringFileInfo.stringTable.strings;
789
for (var i in strings) { r[strings[i].key] = strings[i].value; }
790
+ r['~FileVersion'] = (info.fixedFileInfo.dwFileVersionMS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwFileVersionLS >> 16) + '.' + (info.fixedFileInfo.dwFileVersionLS & 0xFFFF);
791
+ r['~ProductVersion'] = (info.fixedFileInfo.dwProductVersionMS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionMS & 0xFFFF) + '.' + (info.fixedFileInfo.dwProductVersionLS >> 16) + '.' + (info.fixedFileInfo.dwProductVersionLS & 0xFFFF);
792
return r;
793
}
794
@@ -794,7 +796,7 @@ function createAuthenticodeHandler(path) {
796
obj.setVersionInfo = function (versions) {
797
// Convert the version information into a string array
798
const stringArray = [];
797
- for (var i in versions) { stringArray.push({ key: i, value: versions[i] }); }
799
+ for (var i in versions) { if (!i.startsWith('~')) { stringArray.push({ key: i, value: versions[i] }); } }
800
801
// Get the existing version data and switch the strings to the new strings
802
var r = {}, info = readVersionInfo(getVersionInfoData(), 0);
@@ -804,6 +806,20 @@ function createAuthenticodeHandler(path) {
806
if ((StringFileInfo == null) || (StringFileInfo.stringTable == null) || (StringFileInfo.stringTable.strings == null)) return;
807
StringFileInfo.stringTable.strings = stringArray;
808
809
+ // Set the file version
810
+ if (versions['~FileVersion'] != null) {
811
+ const FileVersionSplit = versions['~FileVersion'].split('.');
812
+ info.fixedFileInfo.dwFileVersionMS = (parseInt(FileVersionSplit[0]) << 16) + parseInt(FileVersionSplit[1]);
813
+ info.fixedFileInfo.dwFileVersionLS = (parseInt(FileVersionSplit[2]) << 16) + parseInt(FileVersionSplit[3]);
814
+ }
815
+
816
+ // Set the product version
817
+ if (versions['~ProductVersion'] != null) {
818
+ const ProductVersionSplit = versions['~ProductVersion'].split('.');
819
+ info.fixedFileInfo.dwProductVersionMS = (parseInt(ProductVersionSplit[0]) << 16) + parseInt(ProductVersionSplit[1]);
820
+ info.fixedFileInfo.dwProductVersionLS = (parseInt(ProductVersionSplit[2]) << 16) + parseInt(ProductVersionSplit[3]);
821
+ }
822
+
823
// Re-encode the version information into a buffer
824
var verInfoResBufArray = [];
825
writeVersionInfo(verInfoResBufArray, info);
@@ -1840,6 +1856,8 @@ function start() {
1856
console.log("");
1857
console.log("When doing sign/unsign, you can also change resource properties of the generated file.");
1858
console.log("");
1859
+ console.log(" --fileversionnumber n.n.n.n");
1860
+ console.log(" --productversionnumber n.n.n.n");
1861
console.log(" --filedescription [value]");
1862
console.log(" --fileversion [value]");
1863
console.log(" --internalname [value]");
@@ -1867,7 +1885,7 @@ function start() {
1885
if (exe == null) { console.log("Unable to parse executable file: " + args.exe); return; }
1886
}
1887
1870
- // Parse the resources and make any required changes
1888
+ // Parse the string resources and make any required changes
1889
var resChanges = false, versionStrings = null;
1890
if (exe != null) {
1891
versionStrings = exe.getVersionInfo();
@@ -1876,6 +1894,18 @@ function start() {
1894
const prop = versionProperties[i], propl = prop.toLowerCase();
1895
if (args[propl] && (args[propl] != versionStrings[prop])) { versionStrings[prop] = args[propl]; resChanges = true; }
1896
}
1897
+ if (args['fileversionnumber'] != null) {
1898
+ const fileVerSplit = args['fileversionnumber'].split('.');
1899
+ if (fileVerSplit.length != 4) { console.log("--fileversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; }
1900
+ for (var i in fileVerSplit) { var n = parseInt(fileVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--fileversionnumber numbers must be between 0 and 65535."); return; } }
1901
+ if (args['fileversionnumber'] != versionStrings['~FileVersion']) { versionStrings['~FileVersion'] = args['fileversionnumber']; resChanges = true; }
1902
+ }
1903
+ if (args['productversionnumber'] != null) {
1904
+ const productVerSplit = args['productversionnumber'].split('.');
1905
+ if (productVerSplit.length != 4) { console.log("--productversionnumber must be of format n.n.n.n, for example: 1.2.3.4"); return; }
1906
+ for (var i in productVerSplit) { var n = parseInt(productVerSplit[i]); if ((n < 0) || (n > 65535)) { console.log("--productversionnumber numbers must be between 0 and 65535."); return; } }
1907
+ if (args['productversionnumber'] != versionStrings['~ProductVersion']) { versionStrings['~ProductVersion'] = args['productversionnumber']; resChanges = true; }
1908
+ }
1909
if (resChanges == true) { exe.setVersionInfo(versionStrings); }
1910
}
1911
@@ -1884,8 +1914,12 @@ function start() {
1914
if (command == 'info') { // Get signature information about an executable
1915
if (exe == null) { console.log("Missing --exe [filename]"); return; }
1916
if (args.json) {
1887
- var r = {}, versionInfo = exe.getVersionInfo();
1888
- if (versionInfo != null) { r.versionInfo = versionInfo; }
1917
+ var r = {}, stringInfo = exe.getVersionInfo();
1918
+ if (stringInfo != null) {
1919
+ r.versionInfo = {};
1920
+ r.stringInfo = {};
1921
+ for (var i in stringInfo) { if (i.startsWith('~')) { r.versionInfo[i.substring(1)] = stringInfo[i]; } else { r.stringInfo[i] = stringInfo[i]; } }
1922
+ }
1923
if (exe.fileHashAlgo != null) {
1924
r.signture = {};
1925
if (exe.fileHashAlgo != null) { r.signture.hashMethod = exe.fileHashAlgo; }
@@ -1896,7 +1930,12 @@ function start() {
1930
console.log(JSON.stringify(r, null, 2));
1931
} else {
1932
var versionInfo = exe.getVersionInfo();
1899
- if (versionInfo != null) { console.log("Version Information:"); for (var i in versionInfo) { if (versionInfo[i] == null) { console.log(' ' + i + ': (Empty)'); } else { console.log(' ' + i + ': \"' + versionInfo[i] + '\"'); } } }
1933
+ if (versionInfo != null) {
1934
+ console.log("Version Information:");
1935
+ for (var i in versionInfo) { if (i.startsWith('~') == true) { console.log(' ' + i.substring(1) + ': ' + versionInfo[i] + ''); } }
1936
+ console.log("String Information:");
1937
+ for (var i in versionInfo) { if (i.startsWith('~') == false) { if (versionInfo[i] == null) { console.log(' ' + i + ': (Empty)'); } else { console.log(' ' + i + ': \"' + versionInfo[i] + '\"'); } } }
1938
+ }
1939
console.log("Checksum Information:");
1940
console.log(" Header CheckSum: 0x" + exe.header.peWindows.checkSum.toString(16));
1941
console.log(" Actual CheckSum: 0x" + exe.header.peWindows.checkSumActual.toString(16));
common.js
+13
@@ -339,3 +339,16 @@ function validateObjectForMongoRec(obj, maxStrLen) {
339
}
340
return true;
341
}
342
+
343
+// Parse a version string of the type n.n.n.n
344
+module.exports.parseVersion = function (verstr) {
345
+ if (typeof verstr != 'string') return null;
346
+ const r = [], verstrsplit = verstr.split('.');
347
+ if (verstrsplit.length != 4) return null;
348
+ for (var i in verstrsplit) {
349
+ var n = parseInt(verstrsplit[i]);
350
+ if (isNaN(n) || (n < 0) || (n > 65535)) return null;
351
+ r.push(n);
352
+ }
353
+ return r;
354
+}
meshcentral-config-schema.json
+2
-2
@@ -575,12 +575,12 @@
575
"description": "Use this section to set resource metadata of the Windows agents prior to signing. In Windows, you can right-click and select properties to view these values.",
576
"properties": {
577
"fileDescription": { "type": "string", "description": "Executable file description." },
578
- "fileVersion": { "type": "string", "description": "Executable file version, generally in the form of 1.2.3.4." },
578
+ "fileVersion": { "type": "string", "description": "Executable file version, in the form of 'n.n.n.n', for example: '1.2.3.4'." },
579
"internalName": { "type": "string", "description": "Executable internal name." },
580
"legalCopyright": { "type": "string", "description": "Executable legal copyright." },
581
"originalFilename": { "type": "string", "description": "Executable original file name." },
582
"productName": { "type": "string", "description": "Executable product name." },
583
- "productVersion": { "type": "string", "description": "Executable product version, generally in the form of 1.2.3.4." }
583
+ "productVersion": { "type": "string", "description": "Executable product version. Any string format will work, but a alphabetic character is required for this value to show correctly in the Windows property box. For example: 'v1.2.3.4' will work, but '1.2.3.4' will not." }
584
}
585
},
586
"assistantCustomization": {
meshcentral.js
+29
-2
@@ -1362,6 +1362,13 @@ function CreateMeshCentralServer(config, args) {
1362
}
1363
}
1364
}
1365
+
1366
+ // Check agentfileinfo
1367
+ if (typeof obj.config.domains[i].agentfileinfo == 'object') {
1368
+ if ((obj.config.domains[i].agentfileinfo.fileversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.fileversionnumber; }
1369
+ if ((obj.config.domains[i].agentfileinfo.productversionnumber != null) && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.productversionnumber) == null)) { delete obj.config.domains[i].agentfileinfo.productversionnumber; }
1370
+ if ((obj.config.domains[i].agentfileinfo.fileversionnumber == null) && (typeof obj.config.domains[i].agentfileinfo.fileversion == 'string') && (obj.common.parseVersion(obj.config.domains[i].agentfileinfo.fileversion) != null)) { obj.config.domains[i].agentfileinfo.fileversionnumber = obj.config.domains[i].agentfileinfo.fileversion; }
1371
+ }
1372
}
1373
1374
// Log passed arguments into Windows Service Log
@@ -2939,8 +2946,8 @@ function CreateMeshCentralServer(config, args) {
2946
// If the agent is signed correctly, look to see if the resources in the destination agent are correct
2947
var orgVersionStrings = originalAgent.getVersionInfo();
2948
if (destinationAgentOk == true) {
2942
- var versionStrings = destinationAgent.getVersionInfo();
2943
- var versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
2949
+ const versionStrings = destinationAgent.getVersionInfo();
2950
+ const versionProperties = ['FileDescription', 'FileVersion', 'InternalName', 'LegalCopyright', 'OriginalFilename', 'ProductName', 'ProductVersion'];
2951
for (var i in versionProperties) {
2952
const prop = versionProperties[i], propl = prop.toLowerCase();
2953
if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo[propl] == 'string')) {
@@ -2949,6 +2956,20 @@ function CreateMeshCentralServer(config, args) {
2956
if (orgVersionStrings[prop] != versionStrings[prop]) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
2957
}
2958
}
2959
+
2960
+ // Check file version number
2961
+ if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['fileversionnumber'] == 'string')) {
2962
+ if (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent.
2963
+ } else {
2964
+ if (orgVersionStrings['~FileVersion'] != versionStrings['~FileVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
2965
+ }
2966
+
2967
+ // Check product version number
2968
+ if ((domain.agentfileinfo != null) && (typeof domain.agentfileinfo == 'object') && (typeof domain.agentfileinfo['productversionnumber'] == 'string')) {
2969
+ if (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // If the resource we want is not the same as the destination executable, we need to re-sign the agent.
2970
+ } else {
2971
+ if (orgVersionStrings['~ProductVersion'] != versionStrings['~ProductVersion']) { destinationAgentOk = false; } // if the resource of the orginal agent not the same as the destination executable, we need to re-sign the agent.
2972
+ }
2973
}
2974
2975
// If everything looks ok, runs a hash of the original and destination agent skipping the CRC, resource and signature blocks. If different, sign the agent again.
@@ -2986,6 +3007,12 @@ function CreateMeshCentralServer(config, args) {
3007
const prop = versionProperties[i], propl = prop.toLowerCase();
3008
if (domain.agentfileinfo[propl] && (domain.agentfileinfo[propl] != versionStrings[prop])) { versionStrings[prop] = domain.agentfileinfo[propl]; resChanges = true; }
3009
}
3010
+ if (domain.agentfileinfo['fileversionnumber'] && (domain.agentfileinfo['fileversionnumber'] != versionStrings['~FileVersion'])) {
3011
+ versionStrings['~FileVersion'] = domain.agentfileinfo['fileversionnumber']; resChanges = true;
3012
+ }
3013
+ if (domain.agentfileinfo['productversionnumber'] && (domain.agentfileinfo['productversionnumber'] != versionStrings['~ProductVersion'])) {
3014
+ versionStrings['~ProductVersion'] = domain.agentfileinfo['productversionnumber']; resChanges = true;
3015
+ }
3016
if (resChanges == true) { originalAgent.setVersionInfo(versionStrings); }
3017
}
3018
sample-config-advanced.json
+7
-7
@@ -308,13 +308,13 @@
308
"fileName": "compagnyagent"
309
},
310
"_agentFileInfo": {
311
- "_filedescription": "sample_filedescription",
312
- "_fileversion": "0.1.2.3",
313
- "_internalname": "sample_internalname",
314
- "_legalcopyright": "sample_legalcopyright",
315
- "_originalfilename": "sample_originalfilename",
316
- "_productname": "sample_productname",
317
- "_productversion": "0.1.2.3"
311
+ "filedescription": "sample_filedescription",
312
+ "fileversion": "0.1.2.3",
313
+ "internalname": "sample_internalname",
314
+ "legalcopyright": "sample_legalcopyright",
315
+ "originalfilename": "sample_originalfilename",
316
+ "productname": "sample_productname",
317
+ "productversion": "v0.1.2.3"
318
},
319
"_assistantCustomization": {
320
"title": "Company® Product™",