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 (IOException ex) { 389 //noinspection ReturnInsideFinallyBlock 390 Log.debug(ex.getMessage()); 391 return null; 392 } 393 } 394 } 395 396 //name of post-image script 397 private File getConfig_Script(Map<String, ? super Object> params) { 398 return new File(EXE_IMAGE_DIR.fetchFrom(params), APP_NAME.fetchFrom(params) + "-post-image.wsf"); 399 } 400 401 protected void saveConfigFiles(Map<String, ? super Object> params) { 402 try { 403 File configRoot = CONFIG_ROOT.fetchFrom(params); 404 if (getConfig_ExeProjectFile(params).exists()) { 405 IOUtils.copyFile(getConfig_ExeProjectFile(params), 406 new File(configRoot, getConfig_ExeProjectFile(params).getName())); 407 } 408 if (getConfig_Script(params).exists()) { 409 IOUtils.copyFile(getConfig_Script(params), 410 new File(configRoot, getConfig_Script(params).getName())); 411 } 412 if (getConfig_SmallInnoSetupIcon(params).exists()) { 413 IOUtils.copyFile(getConfig_SmallInnoSetupIcon(params), 414 new File(configRoot, getConfig_SmallInnoSetupIcon(params).getName())); 415 } 416 Log.info(MessageFormat.format(I18N.getString("message.config-save-location"), configRoot.getAbsolutePath())); 417 } catch (IOException ioe) { 418 ioe.printStackTrace(); 419 } 420 } 421 422 private String getAppIdentifier(Map<String, ? super Object> params) { 423 String nm = IDENTIFIER.fetchFrom(params); 424 425 //limitation of innosetup 426 if (nm.length() > 126) 427 nm = nm.substring(0, 126); 428 429 return nm; 430 } 431 432 433 private String getLicenseFile(Map<String, ? super Object> params) { 434 List<String> licenseFiles = LICENSE_FILE.fetchFrom(params); 435 if (licenseFiles == null || licenseFiles.isEmpty()) { 436 return ""; 437 } else { 438 return licenseFiles.get(0); 439 } 440 } 441 442 void validateValueAndPut(Map<String, String> data, String key, BundlerParamInfo<String> param, Map<String, ? super Object> params) throws IOException { 443 String value = param.fetchFrom(params); 444 if (value.contains("\r") || value.contains("\n")) { 445 throw new IOException("Configuration Parameter " + param.getID() + " cannot contain multiple lines of text"); 446 } 447 data.put(key, innosetupEscape(value)); 448 } 449 450 private String innosetupEscape(String value) { 451 if (value.contains("\"") || !value.trim().equals(value)) { 452 value = "\"" + value.replace("\"", "\"\"") + "\""; 453 } 454 return value; 455 } 456 457 boolean prepareMainProjectFile(Map<String, ? super Object> params) throws IOException { 458 Map<String, String> data = new HashMap<>(); 459 data.put("PRODUCT_APP_IDENTIFIER", innosetupEscape(getAppIdentifier(params))); 460 461 validateValueAndPut(data, "APPLICATION_NAME", APP_NAME, params); 462 463 validateValueAndPut(data, "APPLICATION_VENDOR", VENDOR, params); 464 validateValueAndPut(data, "APPLICATION_VERSION", VERSION, params); 465 validateValueAndPut(data, "INSTALLER_FILE_NAME", INSTALLER_FILE_NAME, params); 466 467 data.put("APPLICATION_LAUNCHER_FILENAME", innosetupEscape(WinAppBundler.getLauncherName(params))); 468 469 data.put("APPLICATION_DESKTOP_SHORTCUT", SHORTCUT_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 470 data.put("APPLICATION_MENU_SHORTCUT", MENU_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 471 validateValueAndPut(data, "APPLICATION_GROUP", MENU_GROUP, params); 472 validateValueAndPut(data, "APPLICATION_COMMENTS", TITLE, params); // TODO this seems strange, at least in name 473 validateValueAndPut(data, "APPLICATION_COPYRIGHT", COPYRIGHT, params); 474 475 data.put("APPLICATION_LICENSE_FILE", innosetupEscape(getLicenseFile(params))); 476 data.put("DISABLE_DIR_PAGE", INSTALLDIR_CHOOSER.fetchFrom(params) ? "No" : "Yes"); 477 478 Boolean isSystemWide = EXE_SYSTEM_WIDE.fetchFrom(params); 479 480 if (isSystemWide) { 481 data.put("APPLICATION_INSTALL_ROOT", "{pf}"); 482 data.put("APPLICATION_INSTALL_PRIVILEGE", "admin"); 483 } else { 484 data.put("APPLICATION_INSTALL_ROOT", "{localappdata}"); 485 data.put("APPLICATION_INSTALL_PRIVILEGE", "lowest"); 486 } 487 488 if (BIT_ARCH_64.fetchFrom(params)) { 489 data.put("ARCHITECTURE_BIT_MODE", "x64"); 490 } else { 491 data.put("ARCHITECTURE_BIT_MODE", ""); 492 } 493 494 if (SERVICE_HINT.fetchFrom(params)) { 495 data.put("RUN_FILENAME", innosetupEscape(WinServiceBundler.getAppSvcName(params))); 496 } else { 497 validateValueAndPut(data, "RUN_FILENAME", APP_NAME, params); 498 } 499 validateValueAndPut(data, "APPLICATION_DESCRIPTION", DESCRIPTION, params); 500 data.put("APPLICATION_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnTrue" : "returnFalse"); 501 data.put("APPLICATION_NOT_SERVICE", SERVICE_HINT.fetchFrom(params) ? "returnFalse" : "returnTrue"); 502 data.put("APPLICATION_APP_CDS_INSTALL", 503 (UNLOCK_COMMERCIAL_FEATURES.fetchFrom(params) && ENABLE_APP_CDS.fetchFrom(params) 504 && ("install".equals(APP_CDS_CACHE_MODE.fetchFrom(params)) 505 || "auto+install".equals(APP_CDS_CACHE_MODE.fetchFrom(params)))) 506 ? "returnTrue" 507 : "returnFalse"); 508 data.put("START_ON_INSTALL", START_ON_INSTALL.fetchFrom(params) ? "-startOnInstall" : ""); 509 data.put("STOP_ON_UNINSTALL", STOP_ON_UNINSTALL.fetchFrom(params) ? "-stopOnUninstall" : ""); 510 data.put("RUN_AT_STARTUP", RUN_AT_STARTUP.fetchFrom(params) ? "-runAtStartup" : ""); 511 512 StringBuilder secondaryLaunchersCfg = new StringBuilder(); 513 for (Map<String, ? super Object> launcher : SECONDARY_LAUNCHERS.fetchFrom(params)) { 514 String application_name = APP_NAME.fetchFrom(launcher); 515 if (MENU_HINT.fetchFrom(launcher)) { 516 //Name: "{group}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 517 secondaryLaunchersCfg.append("Name: \"{group}\\"); 518 secondaryLaunchersCfg.append(application_name); 519 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 520 secondaryLaunchersCfg.append(application_name); 521 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 522 secondaryLaunchersCfg.append(application_name); 523 secondaryLaunchersCfg.append(".ico\"\r\n"); 524 } 525 if (SHORTCUT_HINT.fetchFrom(launcher)) { 526 //Name: "{commondesktop}\APPLICATION_NAME"; Filename: "{app}\APPLICATION_NAME.exe"; IconFilename: "{app}\APPLICATION_NAME.ico" 527 secondaryLaunchersCfg.append("Name: \"{commondesktop}\\"); 528 secondaryLaunchersCfg.append(application_name); 529 secondaryLaunchersCfg.append("\"; Filename: \"{app}\\"); 530 secondaryLaunchersCfg.append(application_name); 531 secondaryLaunchersCfg.append(".exe\"; IconFilename: \"{app}\\"); 532 secondaryLaunchersCfg.append(application_name); 533 secondaryLaunchersCfg.append(".ico\"\r\n"); 534 } 535 } 536 data.put("SECONDARY_LAUNCHERS", secondaryLaunchersCfg.toString()); 537 538 StringBuilder registryEntries = new StringBuilder(); 539 String regName = APP_REGISTRY_NAME.fetchFrom(params); 540 List<Map<String, ? super Object>> fetchFrom = FILE_ASSOCIATIONS.fetchFrom(params); 541 for (int i = 0; i < fetchFrom.size(); i++) { 542 Map<String, ? super Object> fileAssociation = fetchFrom.get(i); 543 String description = FA_DESCRIPTION.fetchFrom(fileAssociation); 544 File icon = FA_ICON.fetchFrom(fileAssociation); //TODO FA_ICON_ICO 545 546 List<String> extensions = FA_EXTENSIONS.fetchFrom(fileAssociation); 547 String entryName = regName + "File"; 548 if (i > 0) { 549 entryName += "." + i; 550 } 551 552 if (extensions == null) { 553 Log.info(I18N.getString("message.creating-association-with-null-extension")); 554 } else { 555 for (String ext : extensions) { 556 if (isSystemWide) { 557 // "Root: HKCR; Subkey: \".myp\"; ValueType: string; ValueName: \"\"; ValueData: \"MyProgramFile\"; Flags: uninsdeletevalue" 558 registryEntries.append("Root: HKCR; Subkey: \".") 559 .append(ext) 560 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 561 .append(entryName) 562 .append("\"; Flags: uninsdeletevalue\r\n"); 563 } else { 564 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\.") 565 .append(ext) 566 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 567 .append(entryName) 568 .append("\"; Flags: uninsdeletevalue\r\n"); 569 } 570 } 571 } 572 573 if (extensions != null && !extensions.isEmpty()) { 574 String ext = extensions.get(0); 575 List<String> mimeTypes = FA_CONTENT_TYPE.fetchFrom(fileAssociation); 576 for (String mime : mimeTypes) { 577 if (isSystemWide) { 578 // "Root: HKCR; Subkey: HKCR\\Mime\\Database\\Content Type\\application/chaos; ValueType: string; ValueName: Extension; ValueData: .chaos; Flags: uninsdeletevalue" 579 registryEntries.append("Root: HKCR; Subkey: \"Mime\\Database\\Content Type\\") 580 .append(mime) 581 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 582 .append(ext) 583 .append("\"; Flags: uninsdeletevalue\r\n"); 584 } else { 585 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\Mime\\Database\\Content Type\\") 586 .append(mime) 587 .append("\"; ValueType: string; ValueName: \"Extension\"; ValueData: \".") 588 .append(ext) 589 .append("\"; Flags: uninsdeletevalue\r\n"); 590 } 591 } 592 } 593 594 if (isSystemWide) { 595 //"Root: HKCR; Subkey: \"MyProgramFile\"; ValueType: string; ValueName: \"\"; ValueData: \"My Program File\"; Flags: uninsdeletekey" 596 registryEntries.append("Root: HKCR; Subkey: \"") 597 .append(entryName) 598 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 599 .append(description) 600 .append("\"; Flags: uninsdeletekey\r\n"); 601 } else { 602 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 603 .append(entryName) 604 .append("\"; ValueType: string; ValueName: \"\"; ValueData: \"") 605 .append(description) 606 .append("\"; Flags: uninsdeletekey\r\n"); 607 608 } 609 610 if (icon != null && icon.exists()) { 611 if (isSystemWide) { 612 // "Root: HKCR; Subkey: \"MyProgramFile\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\MYPROG.EXE,0\"\n" + 613 registryEntries.append("Root: HKCR; Subkey: \"") 614 .append(entryName) 615 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 616 .append(icon.getName()) 617 .append("\"\r\n"); 618 } else { 619 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 620 .append(entryName) 621 .append("\\DefaultIcon\"; ValueType: string; ValueName: \"\"; ValueData: \"{app}\\") 622 .append(icon.getName()) 623 .append("\"\r\n"); 624 } 625 } 626 627 if (isSystemWide) { 628 //"Root: HKCR; Subkey: \"MyProgramFile\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\MYPROG.EXE\"\" \"\"%1\"\"\"\n" 629 registryEntries.append("Root: HKCR; Subkey: \"") 630 .append(entryName) 631 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 632 .append(APP_NAME.fetchFrom(params)) 633 .append("\"\" \"\"%1\"\"\"\r\n"); 634 } else { 635 registryEntries.append("Root: HKCU; Subkey: \"Software\\Classes\\") 636 .append(entryName) 637 .append("\\shell\\open\\command\"; ValueType: string; ValueName: \"\"; ValueData: \"\"\"{app}\\") 638 .append(APP_NAME.fetchFrom(params)) 639 .append("\"\" \"\"%1\"\"\"\r\n"); 640 } 641 } 642 if (registryEntries.length() > 0) { 643 data.put("FILE_ASSOCIATIONS", "ChangesAssociations=yes\r\n\r\n[Registry]\r\n" + registryEntries.toString()); 644 } else { 645 data.put("FILE_ASSOCIATIONS", ""); 646 } 647 648 Writer w = new BufferedWriter(new FileWriter(getConfig_ExeProjectFile(params))); 649 String content = preprocessTextResource( 650 WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_ExeProjectFile(params).getName(), 651 I18N.getString("resource.inno-setup-project-file"), DEFAULT_EXE_PROJECT_TEMPLATE, data, 652 VERBOSE.fetchFrom(params), 653 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 654 w.write(content); 655 w.close(); 656 return true; 657 } 658 659 private final static String DEFAULT_INNO_SETUP_ICON = "icon_inno_setup.bmp"; 660 661 private boolean prepareProjectConfig(Map<String, ? super Object> params) throws IOException { 662 prepareMainProjectFile(params); 663 664 //prepare installer icon 665 File iconTarget = getConfig_SmallInnoSetupIcon(params); 666 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + iconTarget.getName(), 667 I18N.getString("resource.setup-icon"), 668 DEFAULT_INNO_SETUP_ICON, 669 iconTarget, 670 VERBOSE.fetchFrom(params), 671 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 672 673 fetchResource(WinAppBundler.WIN_BUNDLER_PREFIX + getConfig_Script(params).getName(), 674 I18N.getString("resource.post-install-script"), 675 (String) null, 676 getConfig_Script(params), 677 VERBOSE.fetchFrom(params), 678 DROP_IN_RESOURCES_ROOT.fetchFrom(params)); 679 return true; 680 } 681 682 private File getConfig_SmallInnoSetupIcon(Map<String, ? super Object> params) { 683 return new File(EXE_IMAGE_DIR.fetchFrom(params), 684 APP_NAME.fetchFrom(params) + "-setup-icon.bmp"); 685 } 686 687 private File getConfig_ExeProjectFile(Map<String, ? super Object> params) { 688 return new File(EXE_IMAGE_DIR.fetchFrom(params), 689 APP_NAME.fetchFrom(params) + ".iss"); 690 } 691 692 693 private File buildEXE(Map<String, ? super Object> params, File outdir) throws IOException { 694 Log.verbose(MessageFormat.format(I18N.getString("message.outputting-to-location"), outdir.getAbsolutePath())); 695 696 outdir.mkdirs(); 697 698 //run candle 699 ProcessBuilder pb = new ProcessBuilder( 700 TOOL_INNO_SETUP_COMPILER_EXECUTABLE.fetchFrom(params), 701 "/o"+outdir.getAbsolutePath(), 702 getConfig_ExeProjectFile(params).getAbsolutePath()); 703 pb = pb.directory(EXE_IMAGE_DIR.fetchFrom(params)); 704 IOUtils.exec(pb, VERBOSE.fetchFrom(params)); 705 706 Log.info(MessageFormat.format(I18N.getString("message.output-location"), outdir.getAbsolutePath())); 707 708 // presume the result is the ".exe" file with the newest modified time 709 // not the best solution, but it is the most reliable 710 File result = null; 711 long lastModified = 0; 712 File[] list = outdir.listFiles(); 713 if (list != null) { 714 for (File f : list) { 715 if (f.getName().endsWith(".exe") && f.lastModified() > lastModified) { 716 result = f; 717 lastModified = f.lastModified(); 718 } 719 } 720 } 721 722 return result; 723 } 724 }