1 /* 2 * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package com.oracle.tools.packager.windows; 27 28 import com.oracle.tools.packager.*; 29 import com.sun.javafx.tools.packager.bundlers.BundleParams; 30 31 import java.io.*; 32 import java.text.MessageFormat; 33 import java.util.*; 34 import java.util.regex.Matcher; 35 import java.util.regex.Pattern; 36 37 import static com.oracle.tools.packager.StandardBundlerParam.SECONDARY_LAUNCHERS; 38 import static com.oracle.tools.packager.StandardBundlerParam.SERVICE_HINT; 39 import static com.oracle.tools.packager.StandardBundlerParam.VERBOSE; 40 import static com.oracle.tools.packager.windows.WindowsBundlerParam.*; 41 import jdk.packager.internal.legacy.windows.WindowsDefender; 42 43 public class WinExeBundler extends AbstractBundler { 44 45 private static final ResourceBundle I18N = 46 ResourceBundle.getBundle(WinExeBundler.class.getName()); 47 48 public static final BundlerParamInfo<WinAppBundler> APP_BUNDLER = new WindowsBundlerParam<>( 49 I18N.getString("param.app-bundler.name"), 50 I18N.getString("param.app-bundler.description"), 51 "win.app.bundler", 52 WinAppBundler.class, 53 params -> new WinAppBundler(), 54 null); 55 56 public static final BundlerParamInfo<WinServiceBundler> SERVICE_BUNDLER = new WindowsBundlerParam<>( 57 I18N.getString("param.service-bundler.name"), 58 I18N.getString("param.service-bundler.description"), 59 "win.service.bundler", 60 WinServiceBundler.class, 61 params -> new WinServiceBundler(), 62 null); 63 64 public static final BundlerParamInfo<File> CONFIG_ROOT = new WindowsBundlerParam<>( 65 I18N.getString("param.config-root.name"), 66 I18N.getString("param.config-root.description"), 67 "configRoot", 68 File.class, params -> { 69 File imagesRoot = new File(BUILD_ROOT.fetchFrom(params), "windows"); 70 imagesRoot.mkdirs(); 71 return imagesRoot; 72 }, 73 (s, p) -> null); 74 75 //default for .exe is user level installation 76 // only do system wide if explicitly requested 77 public static final StandardBundlerParam<Boolean> EXE_SYSTEM_WIDE = 78 new StandardBundlerParam<>( 79 I18N.getString("param.system-wide.name"), 80 I18N.getString("param.system-wide.description"), 81 "win.exe." + BundleParams.PARAM_SYSTEM_WIDE, 82 Boolean.class, 83 params -> params.containsKey(SYSTEM_WIDE.getID()) 84 ? SYSTEM_WIDE.fetchFrom(params) 85 : false, // EXEs default to user local install 86 (s, p) -> (s == null || "null".equalsIgnoreCase(s))? null : Boolean.valueOf(s) // valueOf(null) is false, and we actually do want null 87 ); 88 89 public static final BundlerParamInfo<File> EXE_IMAGE_DIR = new WindowsBundlerParam<>( 90 I18N.getString("param.image-dir.name"), 91 I18N.getString("param.image-dir.description"), 92 "win.exe.imageDir", 93 File.class, 94 params -> { 95 File imagesRoot = IMAGES_ROOT.fetchFrom(params); 96 if (!imagesRoot.exists()) imagesRoot.mkdirs(); 97 return new File(imagesRoot, "win-exe.image"); 98 }, 99 (s, p) -> null); 100 101 private final static String DEFAULT_EXE_PROJECT_TEMPLATE = "template.iss"; 102 private static final String TOOL_INNO_SETUP_COMPILER = "iscc.exe"; 103 104 public static final BundlerParamInfo<String> TOOL_INNO_SETUP_COMPILER_EXECUTABLE = new WindowsBundlerParam<>( 105 I18N.getString("param.iscc-path.name"), 106 I18N.getString("param.iscc-path.description"), 107 "win.exe.iscc.exe", 108 String.class, 109 params -> { 110 for (String dirString : (System.getenv("PATH") + ";C:\\Program Files (x86)\\Inno Setup 5;C:\\Program Files\\Inno Setup 5").split(";")) { 111 File f = new File(dirString.replace("\"", ""), TOOL_INNO_SETUP_COMPILER); 112 if (f.isFile()) { 113 return f.toString(); 114 } 115 } 116 return null; 117 }, 118 null); 119 120 public WinExeBundler() { 121 super(); 122 baseResourceLoader = WinResources.class; 123 } 124 125 @Override 126 public String getName() { 127 return I18N.getString("bundler.name"); 128 } 129 130 @Override 131 public String getDescription() { 132 return I18N.getString("bundler.description"); 133 } 134 135 @Override 136 public String getID() { 137 return "exe"; 138 } 139 140 @Override 141 public String getBundleType() { 142 return "INSTALLER"; 143 } 144 145 @Override 146 public Collection<BundlerParamInfo<?>> getBundleParameters() { 147 Collection<BundlerParamInfo<?>> results = new LinkedHashSet<>(); 148 results.addAll(WinAppBundler.getAppBundleParameters()); 149 results.addAll(getExeBundleParameters()); 150 return results; 151 } 152 153 public static Collection<BundlerParamInfo<?>> getExeBundleParameters() { 154 return Arrays.asList( 155 DESCRIPTION, 156 COPYRIGHT, 157 LICENSE_FILE, 158 MENU_GROUP, 159 MENU_HINT, 160 // RUN_AT_STARTUP, 161 SHORTCUT_HINT, 162 // SERVICE_HINT, 163 // START_ON_INSTALL, 164 // STOP_ON_UNINSTALL, 165 SYSTEM_WIDE, 166 TITLE, 167 VENDOR, 168 INSTALLDIR_CHOOSER 169 ); 170 } 171 172 @Override 173 public File execute(Map<String, ? super Object> params, File outputParentDir) { 174 return bundle(params, outputParentDir); 175 } 176 177 static class VersionExtractor extends PrintStream { 178 double version = 0f; 179 180 public VersionExtractor() { 181 super(new ByteArrayOutputStream()); 182 } 183 184 double getVersion() { 185 if (version == 0f) { 186 String content = new String(((ByteArrayOutputStream) out).toByteArray()); 187 Pattern pattern = Pattern.compile("Inno Setup (\\d+.?\\d*)"); 188 Matcher matcher = pattern.matcher(content); 189 if (matcher.find()) { 190 String v = matcher.group(1); 191 version = new Double(v); 192 } 193 } 194 return version; 195 } 196 } 197 198 private static double findToolVersion(String toolName) { 199 try { 200 if (toolName == null || "".equals(toolName)) return 0f; 201 202 ProcessBuilder pb = new ProcessBuilder( 203 toolName, 204 "/?"); 205 VersionExtractor ve = new VersionExtractor(); 206 IOUtils.exec(pb, Log.isDebug(), true, ve); //not interested in the output 207 double version = ve.getVersion(); 208 Log.verbose(MessageFormat.format(I18N.getString("message.tool-version"), toolName, version)); 209 return version; 210 } catch (Exception e) { 211 if (Log.isDebug()) { 212 e.printStackTrace(); 213 } 214 return 0f; 215 } 216 } 217 218 @Override 219 public boolean validate(Map<String, ? super Object> p) throws UnsupportedPlatformException, ConfigException { 220 try { 221 if (p == null) throw new ConfigException(I18N.getString("error.parameters-null"), I18N.getString("error.parameters-null.advice")); 222 223 //run basic validation to ensure requirements are met 224 //we are not interested in return code, only possible exception 225 APP_BUNDLER.fetchFrom(p).validate(p); 226 227 // make sure some key values don't have newlines 228 for (BundlerParamInfo<String> pi : Arrays.asList( 229 APP_NAME, 230 COPYRIGHT, 231 DESCRIPTION, 232 MENU_GROUP, 233 TITLE, 234 VENDOR, 235 VERSION) 236 ) { 237 String v = pi.fetchFrom(p); 238 if (v.contains("\n") | v.contains("\r")) { 239 throw new ConfigException("Parmeter '" + pi.getID() + "' cannot contain a newline.", 240 "Change the value of '" + pi.getID() + " so that it does not contain any newlines"); 241 } 242 } 243 244 //exe bundlers trim the copyright to 100 characters, tell them this will happen 245 if (COPYRIGHT.fetchFrom(p).length() > 100) { 246 throw new ConfigException( 247 I18N.getString("error.copyright-is-too-long"), 248 I18N.getString("error.copyright-is-too-long.advice")); 249 } 250 251 // validate license file, if used, exists in the proper place 252 if (p.containsKey(LICENSE_FILE.getID())) { 253 List<RelativeFileSet> appResourcesList = APP_RESOURCES_LIST.fetchFrom(p); 254 for (String license : LICENSE_FILE.fetchFrom(p)) { 255 boolean found = false; 256 for (RelativeFileSet appResources : appResourcesList) { 257 found = found || appResources.contains(license); 258 } 259 if (!found) { 260 throw new ConfigException( 261 I18N.getString("error.license-missing"), 262 MessageFormat.format(I18N.getString("error.license-missing.advice"), 263 license)); 264 } 265 } 266 } 267 268 if (SERVICE_HINT.fetchFrom(p)) { 269 SERVICE_BUNDLER.fetchFrom(p).validate(p); 270 } 271 272 double innoVersion = findToolVersion(TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p)); 273 274 //Inno Setup 5+ is required 275 double minVersion = 5.0f; 276 277 if (innoVersion < minVersion) { 278 Log.info(MessageFormat.format(I18N.getString("message.tool-wrong-version"), TOOL_INNO_SETUP_COMPILER, innoVersion, minVersion)); 279 throw new ConfigException( 280 I18N.getString("error.iscc-not-found"), 281 I18N.getString("error.iscc-not-found.advice")); 282 } 283 284 return true; 285 } catch (RuntimeException re) { 286 if (re.getCause() instanceof ConfigException) { 287 throw (ConfigException) re.getCause(); 288 } else { 289 throw new ConfigException(re); 290 } 291 } 292 } 293 294 private boolean prepareProto(Map<String, ? super Object> params) throws IOException { 295 File imageDir = EXE_IMAGE_DIR.fetchFrom(params); 296 File appOutputDir = APP_BUNDLER.fetchFrom(params).doBundle(params, imageDir, true); 297 if (appOutputDir == null) { 298 return false; 299 } 300 301 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 302 if (licenseFiles != null) { 303 //need to copy license file to the root of win.app.image 304 outerLoop: 305 for (RelativeFileSet rfs : APP_RESOURCES_LIST.fetchFrom(params)) { 306 for (String s : licenseFiles) { 307 if (rfs.contains(s)) { 308 File lfile = new File(rfs.getBaseDirectory(), s); 309 IOUtils.copyFile(lfile, new File(imageDir, lfile.getName())); 310 break outerLoop; 311 } 312 } 313 } 314 } 315 316 for (Map<String, ? super Object> fileAssociation : FILE_ASSOCIATIONS.fetchFrom(params)) { 317 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 318 if (icon != null && icon.exists()) { 319 IOUtils.copyFile(icon, new File(appOutputDir, icon.getName())); 320 } 321 } 322 323 if (SERVICE_HINT.fetchFrom(params)) { 324 // copies the service launcher to the app root folder 325 appOutputDir = SERVICE_BUNDLER.fetchFrom(params).doBundle(params, appOutputDir, true); 326 if (appOutputDir == null) { 327 return false; 328 } 329 } 330 return true; 331 } 332 333 public File bundle(Map<String, ? super Object> p, File outputDirectory) { 334 if (!outputDirectory.isDirectory() && !outputDirectory.mkdirs()) { 335 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-create-output-dir"), outputDirectory.getAbsolutePath())); 336 } 337 338 if (!outputDirectory.canWrite()) { 339 throw new RuntimeException(MessageFormat.format(I18N.getString("error.cannot-write-to-output-dir"), outputDirectory.getAbsolutePath())); 340 } 341 342 if (WindowsDefender.isThereAPotentialWindowsDefenderIssue()) { 343 Log.info(MessageFormat.format(I18N.getString("message.potential.windows.defender.issue"), WindowsDefender.getUserTempDirectory())); 344 } 345 346 // validate we have valid tools before continuing 347 String iscc = TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(p); 348 if (iscc == null || !new File(iscc).isFile()) { 349 Log.info(I18N.getString("error.iscc-not-found")); 350 Log.info(MessageFormat.format(I18N.getString("message.iscc-file-string"), iscc)); 351 return null; 352 } 353 354 File imageDir = EXE_IMAGE_DIR.fetchFrom(p); 355 try { 356 imageDir.mkdirs(); 357 358 boolean menuShortcut = MENU_HINT.fetchFrom(p); 359 boolean desktopShortcut = SHORTCUT_HINT.fetchFrom(p); 360 if (!menuShortcut && !desktopShortcut) { 361 //both can not be false - user will not find the app 362 Log.verbose(I18N.getString("message.one-shortcut-required")); 363 p.put(MENU_HINT.getID(), true); 364 } 365 366 if (prepareProto(p) && prepareProjectConfig(p)) { 367 File configScript = getConfig_Script(p); 368 if (configScript.exists()) { 369 Log.info(MessageFormat.format(I18N.getString("message.running-wsh-script"), configScript.getAbsolutePath())); 370 IOUtils.run("wscript", configScript, VERBOSE.fetchFrom(p)); 371 } 372 return buildEXE(p, outputDirectory); 373 } 374 return null; 375 } catch (IOException ex) { 376 ex.printStackTrace(); 377 return null; 378 } finally { 379 try { 380 if (VERBOSE.fetchFrom(p)) { 381 saveConfigFiles(p); 382 } 383 if (imageDir != null && !Log.isDebug()) { 384 IOUtils.deleteRecursive(imageDir); 385 } else if (imageDir != null) { 386 Log.info(MessageFormat.format(I18N.getString("message.debug-working-directory"), imageDir.getAbsolutePath())); 387 } 388 } catch (FileNotFoundException ex) { 389 //noinspection ReturnInsideFinallyBlock 390 return null; 391 } 392 } 393 } 394 395 //name of post-image script 396 private File getConfig_Script(Map<String, ? super Object> params) { 397 return new File(EXE_IMAGE_DIR.fetchFrom(params), APP_NAME.fetchFrom(params) + "-post-image.wsf"); 398 } 399 400 protected void saveConfigFiles(Map<String, ? super Object> params) { 401 try { 402 File configRoot = CONFIG_ROOT.fetchFrom(params); 403 if (getConfig_ExeProjectFile(params).exists()) { 404 IOUtils.copyFile(getConfig_ExeProjectFile(params), 405 new File(configRoot, getConfig_ExeProjectFile(params).getName())); 406 } 407 if (getConfig_Script(params).exists()) { 408 IOUtils.copyFile(getConfig_Script(params), 409 new File(configRoot, getConfig_Script(params).getName())); 410 } 411 if (getConfig_SmallInnoSetupIcon(params).exists()) { 412 IOUtils.copyFile(getConfig_SmallInnoSetupIcon(params), 413 new File(configRoot, getConfig_SmallInnoSetupIcon(params).getName())); 414 } 415 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 416 } catch (IOException ioe) { 417 ioe.printStackTrace(); 418 } 419 } 420 421 private String getAppIdentifier(Map<String, ? super Object> params) { 422 String nm = IDENTIFIER.fetchFrom(params); 423 424 //limitation of innosetup 425 if (nm.length() > 126) 426 nm = nm.substring(0, 126); 427 428 return nm; 429 } 430 431 432 private String getLicenseFile(Map<String, ? super Object> params) { 433 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 434 if (licenseFiles == null || licenseFiles.isEmpty()) { 435 return ""; 436 } else { 437 return licenseFiles.get(0); 438 } 439 } 440 441 void validateValueAndPut(Map<String, String> data, String key, BundlerParamInfo<String> param, Map<String, ? super Object> params) throws IOException { 442 String value = param.fetchFrom(params); 443 if (value.contains("\r") || value.contains("\n")) { 444 throw new IOException("Configuration Parameter " + param.getID() + " cannot contain multiple lines of text"); 445 } 446 data.put(key, innosetupEscape(value)); 447 } 448 449 private String innosetupEscape(String value) { 450 if (value.contains("\"") || !value.trim().equals(value)) { 451 value = "\"" + value.replace("\"", "\"\"") + "\""; 452 } 453 return value; 454 } 455 456 boolean prepareMainProjectFile(Map<String, ? super Object> params) throws IOException { 457 Map<String, String> data = new HashMap<>(); 458 data.put("PRODUCT_APP_IDENTIFIER", innosetupEscape(getAppIdentifier(params))); 459 460 validateValueAndPut(data, "APPLICATION_NAME", APP_NAME, params); 461 462 validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params); 463 validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params); 464 validateValueAndPut(data, "INSTALLER_FILE_NAME", INSTALLER_FILE_NAME, params); 465 466 data.put("APPLICATION_LAUNCHER_FILENAME", innosetupEscape(WinAppBundler.getLauncherName(params))); 467 468 data.put("APPLICATION_DESKTOP_SHORTCUT", SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 469 data.put("APPLICATION_MENU_SHORTCUT", MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 470 validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params); 471 validateValueAndPut(data, "APPLICATION_COMMENTS", TITLE, params); // TODO this seems strange, at least in name 472 validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params); 473 474 data.put("APPLICATION_LICENSE_FILE", innosetupEscape(getLicenseFile(params))); 475 data.put("DISABLE_DIR_PAGE", INSTALLDIR_CHOOSER.fetchFrom(params) ? "No" : "Yes"); 476 477 Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params); 478 479 if (isSystemWide) { 480 data.put("APPLICATION_INSTALL_ROOT", "{pf}"); 481 data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); 482 } else { 483 data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); 484 data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); 485 } 486 487 if (BIT_ARCH_64.fetchFrom(params)) { 488 data.put("ARCHITECTURE_BIT_MODE", "x64"); 489 } else { 490 data.put("ARCHITECTURE_BIT_MODE", ""); 491 } 492 493 if (SERVICE_HINT.fetchFrom(params)) { 494 data.put("RUN_FILENAME", innosetupEscape(WinServiceBundler.getAppSvcName(params))); 495 } else { 496 validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params); 497 } 498 validateValueAndPut(data, "APPLICATION_DESCRIPTION", DESCRIPTION, params); 499 data.put("APPLICATION_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 500 data.put("APPLICATION_NOT_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnFalse" : "returnTrue"); 501 data.put("APPLICATION_APP_CDS_INSTALL", 502 (UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params) 503 && ("install".equals(APP_CDS_CACHE_MODE.fetchFrom(params)) 504 || "auto+install".equals(APP_CDS_CACHE_MODE.fetchFrom(params)))) 505 ? "returnTrue" 506 : "returnFalse"); 507 data.put("START_ON_INSTALL", START_ON_INSTALL.fetchFrom(params) ? "-startOnInstall" : ""); 508 data.put("STOP_ON_UNINSTALL", STOP_ON_UNINSTALL.fetchFrom(params) ? "-stopOnUninstall" : ""); 509 data.put("RUN_AT_STARTUP", RUN_AT_STARTUP.fetchFrom(params) ? "-runAtStartup" : ""); 510 511 StringBuilder secondaryLaunchersCfg = new StringBuilder(); 512 for (Map<String, ? super Object> launcher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 513 String application_name = APP_NAME.fetchFrom(launcher); 514 if (MENU_HINT.fetchFrom(launcher)) { 515 //Name: "{group}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 516 secondaryLaunchersCfg.append("Name: \"{group}\\"); 517 secondaryLaunchersCfg.append(application_name); 518 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 519 secondaryLaunchersCfg.append(application_name); 520 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 521 secondaryLaunchersCfg.append(application_name); 522 secondaryLaunchersCfg.append(".ico\"\r\n"); 523 } 524 if (SHORTCUT_HINT.fetchFrom(launcher)) { 525 //Name: "{commondesktop}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 526 secondaryLaunchersCfg.append("Name: \"{commondesktop}\\"); 527 secondaryLaunchersCfg.append(application_name); 528 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 529 secondaryLaunchersCfg.append(application_name); 530 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 531 secondaryLaunchersCfg.append(application_name); 532 secondaryLaunchersCfg.append(".ico\"\r\n"); 533 } 534 } 535 data.put("SECONDARY_LAUNCHERS", secondaryLaunchersCfg.toString()); 536 537 StringBuilder registryEntries = new StringBuilder(); 538 String regName = APP_REGISTRY_NAME.fetchFrom(params); 539 List<Map<String, ? super Object>> fetchFrom = FILE_ASSOCIATIONS.fetchFrom(params); 540 for (int i = 0; i < fetchFrom.size(); i++) { 541 Map<String, ? super Object> fileAssociation = fetchFrom.get(i); 542 String description = FA_DESCRIPTION.fetchFrom(fileAssociation); 543 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 544 545 List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); 546 String entryName = regName + "File"; 547 if (i > 0) { 548 entryName += "." + i; 549 } 550 551 if (extensions == null) { 552 Log.info(I18N.getString("message.creating-association-with-null-extension")); 553 } else { 554 for (String ext : extensions) { 555 if (isSystemWide) { 556 // "Root: HKCR; Subkey: \".myp\"; ValueType: string; ValueName: \"\"; ValueData: \"MyProgramFile\"; Flags: uninsdeletevalue" 557 registryEntries.append("Root: HKCR; Subkey: \".") 558 .append(ext) 559 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 560 .append(entryName) 561 .append("\"; Flags: uninsdeletevalue\r\n"); 562 } else { 563 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\.") 564 .append(ext) 565 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 566 .append(entryName) 567 .append("\"; Flags: uninsdeletevalue\r\n"); 568 } 569 } 570 } 571 572 if (extensions != null && !extensions.isEmpty()) { 573 String ext = extensions.get(0); 574 List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation); 575 for (String mime : mimeTypes) { 576 if (isSystemWide) { 577 // "Root: HKCR; Subkey: HKCR\\Mime\\Database\\Content Type\\application/chaos; ValueType: string; ValueName: Extension; ValueData: .chaos; Flags: uninsdeletevalue" 578 registryEntries.append("Root: HKCR; Subkey: \"Mime\\Database\\Content Type\\") 579 .append(mime) 580 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 581 .append(ext) 582 .append("\"; Flags: uninsdeletevalue\r\n"); 583 } else { 584 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\Mime\\Database\\Content Type\\") 585 .append(mime) 586 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 587 .append(ext) 588 .append("\"; Flags: uninsdeletevalue\r\n"); 589 } 590 } 591 } 592 593 if (isSystemWide) { 594 //"Root: HKCR; Subkey: \"MyProgramFile\"; ValueType: string; ValueName: \"\"; ValueData: \"My Program File\"; Flags: uninsdeletekey" 595 registryEntries.append("Root: HKCR; Subkey: \"") 596 .append(entryName) 597 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 598 .append(description) 599 .append("\"; Flags: uninsdeletekey\r\n"); 600 } else { 601 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 602 .append(entryName) 603 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 604 .append(description) 605 .append("\"; Flags: uninsdeletekey\r\n"); 606 607 } 608 609 if (icon != null && icon.exists()) { 610 if (isSystemWide) { 611 // "Root: HKCR; Subkey: \"MyProgramFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\MYPROG.EXE,0\"\n" + 612 registryEntries.append("Root: HKCR; Subkey: \"") 613 .append(entryName) 614 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 615 .append(icon.getName()) 616 .append("\"\r\n"); 617 } else { 618 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 619 .append(entryName) 620 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 621 .append(icon.getName()) 622 .append("\"\r\n"); 623 } 624 } 625 626 if (isSystemWide) { 627 //"Root: HKCR; Subkey: \"MyProgramFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" 628 registryEntries.append("Root: HKCR; Subkey: \"") 629 .append(entryName) 630 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 631 .append(APP_NAME.fetchFrom(params)) 632 .append("\"\" \"\"%1\"\"\"\r\n"); 633 } else { 634 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 635 .append(entryName) 636 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 637 .append(APP_NAME.fetchFrom(params)) 638 .append("\"\" \"\"%1\"\"\"\r\n"); 639 } 640 } 641 if (registryEntries.length() > 0) { 642 data.put("FILE_ASSOCIATIONS", "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + registryEntries.toString()); 643 } else { 644 data.put("FILE_ASSOCIATIONS", ""); 645 } 646 647 Writer w = new BufferedWriter(new FileWriter(getConfig_ExeProjectFile(params))); 648 String content = preprocessTextResource( 649 WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ExeProjectFile(params).getName(), 650 I18N.getString("resource.inno-setup-project-file"), DEFAULT_EXE_PROJECT_TEMPLATE, data, 651 VERBOSE.fetchFrom(params), 652 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 653 w.write(content); 654 w.close(); 655 return true; 656 } 657 658 private final static String DEFAULT_INNO_SETUP_ICON = "icon_inno_setup.bmp"; 659 660 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 661 prepareMainProjectFile(params); 662 663 //prepare installer icon 664 File iconTarget = getConfig_SmallInnoSetupIcon(params); 665 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + iconTarget.getName(), 666 I18N.getString("resource.setup-icon"), 667 DEFAULT_INNO_SETUP_ICON, 668 iconTarget, 669 VERBOSE.fetchFrom(params), 670 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 671 672 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_Script(params).getName(), 673 I18N.getString("resource.post-install-script"), 674 (String) null, 675 getConfig_Script(params), 676 VERBOSE.fetchFrom(params), 677 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 678 return true; 679 } 680 681 private File getConfig_SmallInnoSetupIcon(Map<String, ? super Object> params) { 682 return new File(EXE_IMAGE_DIR.fetchFrom(params), 683 APP_NAME.fetchFrom(params) + "-setup-icon.bmp"); 684 } 685 686 private File getConfig_ExeProjectFile(Map<String, ? super Object> params) { 687 return new File(EXE_IMAGE_DIR.fetchFrom(params), 688 APP_NAME.fetchFrom(params) + ".iss"); 689 } 690 691 692 private File buildEXE(Map<String, ? super Object> params, File outdir) throws IOException { 693 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outdir.getAbsolutePath())); 694 695 outdir.mkdirs(); 696 697 //run candle 698 ProcessBuilder pb = new ProcessBuilder( 699 TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params), 700 "/o"+outdir.getAbsolutePath(), 701 getConfig_ExeProjectFile(params).getAbsolutePath()); 702 pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params)); 703 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 704 705 Log.info(MessageFormat.format(I18N.getString("message.output-location"), outdir.getAbsolutePath())); 706 707 // presume the result is the ".exe" file with the newest modified time 708 // not the best solution, but it is the most reliable 709 File result = null; 710 long lastModified = 0; 711 File[] list = outdir.listFiles(); 712 if (list != null) { 713 for (File f : list) { 714 if (f.getName().endsWith(".exe") && f.lastModified() > lastModified) { 715 result = f; 716 lastModified = f.lastModified(); 717 } 718 } 719 } 720 721 return result; 722 } 723 }