Change-Id: Ia5603fe8917a9b567603328996e2dfbb56f38b73
diff --git a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationDialog.java b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationDialog.java
new file mode 100644
index 0000000..09adca5
--- /dev/null
+++ b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationDialog.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2016 Eike Stepper (Berlin, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Eike Stepper - initial API and implementation
+ */
+package org.eclipse.oomph.setup.internal.installer;
+
+import org.eclipse.oomph.setup.ui.AbstractConfirmDialog;
+import org.eclipse.oomph.util.PropertiesUtil;
+
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * @author Eike Stepper
+ */
+public class FileAssociationDialog extends AbstractConfirmDialog
+{
+  private boolean register = true;
+
+  public FileAssociationDialog()
+  {
+    super("Register File Association", 560, 270, "Check on each startup");
+    setShellStyle(getShellStyle() & ~SWT.MIN & ~SWT.MAX);
+  }
+
+  public boolean isRegister()
+  {
+    return register;
+  }
+
+  @Override
+  protected String getShellText()
+  {
+    return PropertiesUtil.getProductName();
+  }
+
+  @Override
+  protected String getDefaultMessage()
+  {
+    return "Do you want to associate this installer with '.setup' files?";
+  }
+
+  @Override
+  protected boolean getRememberButtonDefaultValue()
+  {
+    return FileAssociationUtil.isCheckRegistration();
+  }
+
+  @Override
+  protected void createUI(Composite parent)
+  {
+    initializeDialogUnits(parent);
+
+    Composite composite = new Composite(parent, SWT.NONE);
+    composite.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+    composite.setLayout(new GridLayout(1, false));
+
+    Button yesButton = new Button(composite, SWT.RADIO);
+    yesButton.setText("Yes, associate this installer with '.setup' files.");
+    yesButton.setLayoutData(new GridData(GridData.BEGINNING));
+    yesButton.setSelection(register);
+    Dialog.applyDialogFont(yesButton);
+    yesButton.addSelectionListener(new SelectionAdapter()
+    {
+      @Override
+      public void widgetSelected(SelectionEvent e)
+      {
+        register = true;
+      }
+    });
+
+    Button noButton = new Button(composite, SWT.RADIO);
+    noButton.setText("No, skip association.");
+    noButton.setLayoutData(new GridData(GridData.BEGINNING));
+    yesButton.setSelection(!register);
+    Dialog.applyDialogFont(noButton);
+    noButton.addSelectionListener(new SelectionAdapter()
+    {
+      @Override
+      public void widgetSelected(SelectionEvent e)
+      {
+        register = false;
+      }
+    });
+  }
+
+  @Override
+  protected void doCreateButtons(Composite parent)
+  {
+    createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, false);
+  }
+}
diff --git a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationUtil.java b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationUtil.java
new file mode 100644
index 0000000..f9e137c
--- /dev/null
+++ b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/FileAssociationUtil.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2016 Eike Stepper (Berlin, Germany) and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    Stepper - initial API and implementation
+ */
+package org.eclipse.oomph.setup.internal.installer;
+
+import org.eclipse.oomph.util.IOUtil;
+import org.eclipse.oomph.util.OS;
+import org.eclipse.oomph.util.OomphPlugin.Preference;
+import org.eclipse.oomph.util.PropertiesUtil;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eike Stepper
+ */
+public class FileAssociationUtil
+{
+  public static final FileAssociationUtil INSTANCE = create();
+
+  private static final String LAUNCHER = OS.getCurrentLauncher(false);
+
+  private static final Preference PREF_CHECK_REGISTATION = SetupInstallerPlugin.INSTANCE.getConfigurationPreference("checkRegistration");
+
+  private FileAssociationUtil()
+  {
+  }
+
+  public final boolean canBeRegistered()
+  {
+    return LAUNCHER != null && !LAUNCHER.startsWith(PropertiesUtil.getTmpDir()) && !isRegistered();
+  }
+
+  public boolean isRegistered()
+  {
+    // Subclasses may override.
+    return false;
+  }
+
+  public final void register()
+  {
+    register(LAUNCHER);
+  }
+
+  public void register(String launcher)
+  {
+    // Subclasses may override.
+  }
+
+  public static boolean isCheckRegistration()
+  {
+    return PREF_CHECK_REGISTATION.get(true);
+  }
+
+  public static void setCheckRegistration(boolean checkRegistration)
+  {
+    PREF_CHECK_REGISTATION.set(checkRegistration);
+  }
+
+  private static FileAssociationUtil create()
+  {
+    if (OS.INSTANCE.isWin())
+    {
+      return new Win();
+    }
+
+    if (OS.INSTANCE.isMac())
+    {
+      return new Mac();
+    }
+
+    if (OS.INSTANCE.isLinux())
+    {
+      return new Linux();
+    }
+
+    return new FileAssociationUtil();
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Win extends FileAssociationUtil
+  {
+    private static final String EXTENSION = ".setup";
+
+    private static final String TYPE = "Oomph.setup.1";
+
+    // private static final String HKEY_CLASSES = "HKEY_CLASSES_ROOT";
+
+    private static final String HKEY_CLASSES = "HKEY_CURRENT_USER\\Software\\Classes";
+
+    private static final String REG_SZ = "REG_SZ";
+
+    private static final int SUCCESS = 0;
+
+    public Win()
+    {
+    }
+
+    @Override
+    public boolean isRegistered()
+    {
+      String type = queryRegistryDefaultValue(HKEY_CLASSES + "\\" + EXTENSION);
+      if (!TYPE.equals(type))
+      {
+        return false;
+      }
+
+      int xxx;
+      // TODO Do we want to check for the "Content Type" value?
+
+      String openCommand = queryRegistryDefaultValue(HKEY_CLASSES + "\\" + TYPE + "\\shell\\open\\command");
+      if (!("\"" + LAUNCHER + "\" \"%1\"").equals(openCommand))
+      {
+        return false;
+      }
+
+      return true;
+    }
+
+    @Override
+    public void register(String launcher)
+    {
+      File regFile = null;
+
+      try
+      {
+        launcher = launcher.replace("\\", "\\\\");
+
+        List<String> lines = new ArrayList<String>();
+        lines.add("Windows Registry Editor Version 5.00");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + EXTENSION + "]");
+        lines.add("@=\"" + TYPE + "\"");
+        lines.add("\"Content Type\"=\"application/x-oomph-setup+xml\"");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + TYPE + "]");
+        lines.add("@=\"Oomph Setup\"");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + TYPE + "\\DefaultIcon]");
+        lines.add("@=\"\\\"" + launcher + "\\\"\"");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + TYPE + "\\shell\\edit\\command]");
+        // Does not work: lines.add("@=REG_EXPAND_SZ:\"\\\"%SystemRoot%\\\\System32\\\\notepad.exe\\\" \\\"%1\\\"\"");
+        lines.add("@=hex(2):22,00,25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,\\");
+        lines.add(" 74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,6e,\\");
+        lines.add(" 00,6f,00,74,00,65,00,70,00,61,00,64,00,2e,00,65,00,78,00,65,00,22,00,20,00,\\");
+        lines.add(" 22,00,25,00,31,00,22,00,00,00");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + TYPE + "\\shell\\open]");
+        lines.add("\"MUIVerb\"=\"Install...\"");
+        lines.add("");
+        lines.add("[" + HKEY_CLASSES + "\\" + TYPE + "\\shell\\open\\command]");
+        lines.add("@=\"\\\"" + launcher + "\\\" \\\"%1\\\"\"");
+        lines.add("");
+
+        regFile = File.createTempFile("oomph-", ".reg");
+        IOUtil.writeLines(regFile, "ASCII", lines);
+
+        exec("reg.exe", "import", regFile.getAbsolutePath());
+        exec("ie4uinit.exe", "-ClearIconCache");
+      }
+      catch (Throwable ex)
+      {
+        SetupInstallerPlugin.INSTANCE.log(ex);
+      }
+      finally
+      {
+        if (regFile != null)
+        {
+          IOUtil.deleteBestEffort(regFile, true);
+        }
+      }
+    }
+
+    private String queryRegistryDefaultValue(String key)
+    {
+      try
+      {
+        List<String> lines = exec("reg.exe", "query", key, "/ve");
+        if (lines != null && lines.size() >= 3)
+        {
+          String line = lines.get(2);
+          int pos = line.indexOf(REG_SZ);
+          if (pos != -1)
+          {
+            return line.substring(pos + REG_SZ.length()).trim();
+          }
+        }
+      }
+      catch (Throwable ex)
+      {
+        //$FALL-THROUGH$
+      }
+
+      return null;
+    }
+
+    private List<String> exec(String... command)
+    {
+      Process process = null;
+
+      try
+      {
+        process = new ProcessBuilder(command).start();
+        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+        List<String> lines = new ArrayList<String>();
+        String line;
+
+        while ((line = bufferedReader.readLine()) != null)
+        {
+          lines.add(line);
+        }
+
+        if (process.waitFor() != SUCCESS)
+        {
+          return null;
+        }
+
+        return lines;
+      }
+      catch (Throwable ex)
+      {
+        return null;
+      }
+      finally
+      {
+        if (process != null)
+        {
+          IOUtil.closeSilent(process.getInputStream());
+          IOUtil.closeSilent(process.getOutputStream());
+          IOUtil.closeSilent(process.getErrorStream());
+        }
+      }
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Mac extends FileAssociationUtil
+  {
+    public Mac()
+    {
+    }
+  }
+
+  /**
+   * @author Eike Stepper
+   */
+  private static final class Linux extends FileAssociationUtil
+  {
+    public Linux()
+    {
+    }
+
+    @Override
+    public void register(String launcher)
+    {
+      // desktop-file-validate ~/.local/share/applications/eclipse-inst.desktop
+
+      // ~/.local/share/mime/packages/application-x-foobar.xml
+
+      // <?xml version="1.0" encoding="UTF-8"?>
+      // <mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
+      // <mime-type type="application/x-foobar">
+      // <comment>foo file</comment>
+      // <icon name="application-x-foobar"/>
+      // <glob-deleteall/>
+      // <glob pattern="*.foo"/>
+      // </mime-type>
+      // </mime-info>
+
+      // update-mime-database ~/.local/share/mime
+
+      // gsettings get com.canonical.Unity.Launcher favorites
+
+      // gsettings set com.canonical.Unity.Launcher favorites "['application://org.gnome.Nautilus.desktop', 'application://firefox.desktop',
+      // 'application://ubuntu-software-center.desktop', 'application://unity-control-center.desktop', 'application://gnome-terminal.desktop',
+      // 'unity://running-apps', 'application://eclipse-inst.desktop', 'application://gedit.desktop', 'unity://expo-icon', 'unity://devices']"
+
+      // gtk-launch eclipse-inst
+    }
+  }
+}
diff --git a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/InstallerApplication.java b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/InstallerApplication.java
index f2b821c..b93e40a 100644
--- a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/InstallerApplication.java
+++ b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/InstallerApplication.java
@@ -16,6 +16,7 @@
 import org.eclipse.oomph.p2.core.P2Util;
 import org.eclipse.oomph.p2.core.ProfileTransaction.Resolution;
 import org.eclipse.oomph.setup.internal.core.SetupContext;
+import org.eclipse.oomph.setup.internal.core.util.SetupCoreUtil;
 import org.eclipse.oomph.setup.ui.wizards.SetupWizard.SelectionMemento;
 import org.eclipse.oomph.ui.ErrorDialog;
 import org.eclipse.oomph.ui.UIUtil;
@@ -27,6 +28,7 @@
 
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
 
 import org.eclipse.core.runtime.IProgressMonitor;
@@ -51,12 +53,15 @@
 import org.osgi.framework.Bundle;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -131,7 +136,7 @@
         List<Image> images = new ArrayList<Image>();
         for (String windowImageValue : StringUtil.explode(windowImages, ","))
         {
-          URI windowImageURI = URI.createURI(windowImageValue);
+          URI windowImageURI = getConfigurationResourceURI(windowImageValue);
           if (windowImageURI.isRelative())
           {
             URL url = brandingBundle.getEntry(windowImageValue);
@@ -153,6 +158,24 @@
       }
     }
 
+    // Check if the file association is registered, prompt if not, and register if requested.
+    if (FileAssociationUtil.isCheckRegistration())
+    {
+      if (FileAssociationUtil.INSTANCE.canBeRegistered())
+      {
+        FileAssociationDialog dialog = new FileAssociationDialog();
+        if (dialog.open() == FileAssociationDialog.OK)
+        {
+          FileAssociationUtil.setCheckRegistration(dialog.isRemember());
+
+          if (dialog.isRegister())
+          {
+            FileAssociationUtil.INSTANCE.register();
+          }
+        }
+      }
+    }
+
     boolean restarted = false;
     File restarting = new File(SetupContext.CONFIGURATION_STATE_LOCATION_URI.appendSegment("restarting").toFileString());
     SelectionMemento selectionMemento = null;
@@ -202,7 +225,11 @@
 
     mode = Mode.valueOf(modeName.toUpperCase());
 
-    Collection<? extends Resource> configurationResources = Collections.emptySet();
+    @SuppressWarnings("rawtypes")
+    Map arguments = context.getArguments();
+    String[] applicationArgs = arguments == null ? null : (String[])arguments.get("application.args");
+    Collection<? extends Resource> configurationResources = getConfigurationResources(applicationArgs);
+
     for (;;)
     {
       if (selectionMemento == null)
@@ -270,7 +297,7 @@
           try
           {
             // EXIT_RESTART often makes the new process come up behind other windows, so try a fresh native process first.
-            Runtime.getRuntime().exec(launcher);
+            launch(launcher);
             return EXIT_OK;
           }
           catch (Throwable ex)
@@ -292,6 +319,69 @@
     PREF_MODE.set(mode.name());
   }
 
+  private Set<Resource> getConfigurationResources(String[] applicationArgs)
+  {
+    Set<Resource> resources = new HashSet<Resource>();
+    ResourceSet resourceSet = null;
+
+    for (String arg : applicationArgs)
+    {
+      URI uri;
+
+      try
+      {
+        uri = getConfigurationResourceURI(arg);
+      }
+      catch (Throwable ex)
+      {
+        SetupInstallerPlugin.INSTANCE.log(ex);
+        continue;
+      }
+
+      if (resourceSet == null)
+      {
+        resourceSet = SetupCoreUtil.createResourceSet();
+      }
+
+      Resource resource;
+
+      try
+      {
+        resource = resourceSet.getResource(uri, true);
+      }
+      catch (Throwable ex)
+      {
+        SetupInstallerPlugin.INSTANCE.log(ex);
+        continue;
+      }
+
+      if (resource != null)
+      {
+        resources.add(resource);
+      }
+    }
+
+    return resources;
+  }
+
+  private URI getConfigurationResourceURI(String arg)
+  {
+    try
+    {
+      File file = new File(arg);
+      if (file.isFile() && file.canRead())
+      {
+        return URI.createFileURI(arg);
+      }
+    }
+    catch (Throwable ex)
+    {
+      //$FALL-THROUGH$
+    }
+
+    return URI.createURI(arg);
+  }
+
   private void handleCocoaMenu(final Display display, final InstallerUI[] installerDialog)
   {
     if (!SKIP_COCOA_MENU && Platform.WS_COCOA.equals(Platform.getWS()))
@@ -454,6 +544,16 @@
     // Do nothing.
   }
 
+  static void launch(String launcher) throws IOException
+  {
+    String[] args = Platform.getCommandLineArgs();
+    String[] cmdarray = new String[1 + args.length];
+    cmdarray[0] = launcher;
+    System.arraycopy(args, 0, cmdarray, 1, args.length);
+
+    Runtime.getRuntime().exec(cmdarray);
+  }
+
   /**
    * @author Eike Stepper
    */
diff --git a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerDialog.java b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerDialog.java
index 86ee2eb..8a3d7a3 100644
--- a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerDialog.java
+++ b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerDialog.java
@@ -51,6 +51,8 @@
 
   private Button desktopButton;
 
+  private Button registerButton;
+
   public KeepInstallerDialog(Shell parentShell, boolean startPermanentInstaller)
   {
     super(parentShell, PropertiesUtil.getProductName(), 560, 270, SetupInstallerPlugin.INSTANCE, false);
@@ -173,6 +175,13 @@
       desktopButton.setSelection(true);
     }
 
+    new Label(parent, SWT.NONE);
+    registerButton = new Button(parent, SWT.CHECK);
+    registerButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1));
+    registerButton.setText("Register file association");
+    registerButton.setToolTipText("Associate this installer with '.setup' files");
+    registerButton.setSelection(true);
+
     setDefaultLocation(locationText);
   }
 
@@ -184,6 +193,7 @@
     {
       final boolean startMenu = startMenuButton == null ? false : startMenuButton.getSelection();
       final boolean desktop = desktopButton == null ? false : desktopButton.getSelection();
+      final boolean register = registerButton.getSelection();
 
       ProgressMonitorDialog progressMonitorDialog = new ProgressMonitorDialog((Shell)getShell().getParent());
 
@@ -194,7 +204,13 @@
           public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException
           {
             monitor.beginTask("Copying installer to " + location, IProgressMonitor.UNKNOWN);
-            KeepInstallerUtil.keepInstaller(location, startPermanentInstaller, launcher, startMenu, desktop, false);
+            String permanentLauncher = KeepInstallerUtil.keepInstaller(location, startPermanentInstaller, launcher, startMenu, desktop, false);
+
+            if (register)
+            {
+              FileAssociationUtil.INSTANCE.register(permanentLauncher);
+            }
+
             monitor.done();
           }
         });
@@ -226,6 +242,7 @@
       {
         File home = new File(PropertiesUtil.getUserHome());
         String folderName = PropertiesUtil.getProductName().toLowerCase().replace(' ', '-');
+
         for (int i = 1; i < Integer.MAX_VALUE; i++)
         {
           File folder = new File(home, folderName + (i > 1 ? i : ""));
diff --git a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerUtil.java b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerUtil.java
index 989c68b..80ede5b 100644
--- a/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerUtil.java
+++ b/plugins/org.eclipse.oomph.setup.installer/src/org/eclipse/oomph/setup/internal/installer/KeepInstallerUtil.java
@@ -43,35 +43,28 @@
   {
     try
     {
-      String powerShell = KeepInstallerUtil.getPowerShell();
+      String powerShell = getPowerShell();
       if (powerShell != null)
       {
         if (groupName != null)
         {
-          Runtime.getRuntime().exec(
-              new String[] {
-                  powerShell,
-                  "-command",
-                  "& { " + "$folderPath = Join-Path ([Environment]::GetFolderPath('" + specialFolder + "')) '" + groupName + "';"
-                      + //
-                      "[system.io.directory]::CreateDirectory($folderPath); "
-                      + //
+          Runtime.getRuntime()
+              .exec(new String[] { powerShell, "-command",
+                  "& { " + "$folderPath = Join-Path ([Environment]::GetFolderPath('" + specialFolder + "')) '" + groupName + "';" + //
+                      "[system.io.directory]::CreateDirectory($folderPath); " + //
                       "$linkPath = Join-Path $folderPath '" + shortcutName + ".lnk'; $targetPath = '" + target
                       + "'; $link = (New-Object -ComObject WScript.Shell).CreateShortcut( $linkpath ); $link.TargetPath = $targetPath; $link.Save()}" });
 
         }
         else
         {
-          Runtime.getRuntime().exec(
-              new String[] {
-                  powerShell,
-                  "-command",
+          Runtime.getRuntime()
+              .exec(new String[] { powerShell, "-command",
                   "& {$linkPath = Join-Path ([Environment]::GetFolderPath('" + specialFolder + "')) '" + shortcutName + ".lnk'; $targetPath = '" + target
                       + "'; $link = (New-Object -ComObject WScript.Shell).CreateShortcut( $linkpath ); $link.TargetPath = $targetPath; $link.Save()}" });
 
         }
       }
-      // [system.io.directory]::CreateDirectory("C:\test")
     }
     catch (IOException ex)
     {
@@ -83,12 +76,11 @@
   {
     try
     {
-      String powerShell = KeepInstallerUtil.getPowerShell();
+      String powerShell = getPowerShell();
       if (powerShell != null)
       {
-        Runtime.getRuntime().exec(
-            new String[] { powerShell, "-command",
-                "& { (new-object -c shell.application).namespace('" + location + "').parsename('" + launcherName + "').invokeverb('taskbarpin') }" });
+        Runtime.getRuntime().exec(new String[] { powerShell, "-command",
+            "& { (new-object -c shell.application).namespace('" + location + "').parsename('" + launcherName + "').invokeverb('taskbarpin') }" });
       }
     }
     catch (IOException ex)
@@ -160,7 +152,7 @@
     return powerShell;
   }
 
-  public static void keepInstaller(String targetLocation, boolean startPermanentInstaller, String launcher, boolean startMenu, boolean desktop,
+  public static String keepInstaller(String targetLocation, boolean startPermanentInstaller, String launcher, boolean startMenu, boolean desktop,
       boolean quickLaunch)
   {
     File source = new File(launcher).getParentFile();
@@ -174,7 +166,7 @@
     {
       try
       {
-        Runtime.getRuntime().exec(permanentLauncher);
+        InstallerApplication.launch(permanentLauncher);
       }
       catch (Exception ex)
       {
@@ -203,6 +195,7 @@
     }
 
     setKeepInstaller(true);
+    return permanentLauncher;
   }
 
   public static boolean isInstallerKept()
diff --git a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/AbstractConfirmDialog.java b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/AbstractConfirmDialog.java
index d00ecfd..1b18320 100644
--- a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/AbstractConfirmDialog.java
+++ b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/AbstractConfirmDialog.java
@@ -26,7 +26,7 @@
 {
   private final String rememberButtonText;
 
-  private boolean remember;
+  private boolean remember = getRememberButtonDefaultValue();
 
   public AbstractConfirmDialog(Shell parentShell, String title, int width, int height, String rememberButtonText)
   {
@@ -48,6 +48,7 @@
   protected void createButtonsForButtonBar(Composite parent)
   {
     final Button rememberButton = createCheckbox(parent, rememberButtonText);
+    rememberButton.setSelection(remember);
     rememberButton.addSelectionListener(new SelectionAdapter()
     {
       @Override
@@ -76,4 +77,9 @@
   {
     return null;
   }
+
+  protected boolean getRememberButtonDefaultValue()
+  {
+    return false;
+  }
 }
diff --git a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/ProjectPage.java b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/ProjectPage.java
index 8a2071a..01b6f14 100644
--- a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/ProjectPage.java
+++ b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/ProjectPage.java
@@ -1163,26 +1163,6 @@
     }
   }
 
-  public static void hookTransferControl(Shell shell, Control execludedControl, SetupTransferSupport transferSupport,
-      ConfigurationListener configurationListener)
-  {
-    shell.addShellListener(configurationListener);
-
-    // Listen for drops on the overall composite.
-    for (Control control : shell.getChildren())
-    {
-      if (control instanceof Composite)
-      {
-        transferSupport.addControl(control);
-        break;
-      }
-    }
-
-    // But exclude the page itself.
-    transferSupport.excludeControl(execludedControl);
-    configurationListener.checkConfigurationAvailability();
-  }
-
   @Override
   public void leavePage(boolean forward)
   {
@@ -1206,13 +1186,6 @@
     }
   }
 
-  public static void unhookTransferControl(Shell shell, Control execludedControl, SetupTransferSupport transferSupport,
-      ConfigurationListener configurationListener)
-  {
-    shell.removeShellListener(configurationListener);
-    transferSupport.removeControls();
-  }
-
   @Override
   public void sendStats(boolean success)
   {
@@ -1490,6 +1463,33 @@
     adapterFactory.dispose();
   }
 
+  public static void hookTransferControl(Shell shell, Control execludedControl, SetupTransferSupport transferSupport,
+      ConfigurationListener configurationListener)
+  {
+    shell.addShellListener(configurationListener);
+  
+    // Listen for drops on the overall composite.
+    for (Control control : shell.getChildren())
+    {
+      if (control instanceof Composite)
+      {
+        transferSupport.addControl(control);
+        break;
+      }
+    }
+  
+    // But exclude the page itself.
+    transferSupport.excludeControl(execludedControl);
+    configurationListener.checkConfigurationAvailability();
+  }
+
+  public static void unhookTransferControl(Shell shell, Control execludedControl, SetupTransferSupport transferSupport,
+      ConfigurationListener configurationListener)
+  {
+    shell.removeShellListener(configurationListener);
+    transferSupport.removeControls();
+  }
+
   private static void createProjectLabel(Project project, StringBuilder builder)
   {
     Project parentProject = project.getParentProject();
diff --git a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/VariablePage.java b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/VariablePage.java
index 3b9e1bc..460a859 100644
--- a/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/VariablePage.java
+++ b/plugins/org.eclipse.oomph.setup.ui/src/org/eclipse/oomph/setup/ui/wizards/VariablePage.java
@@ -803,6 +803,7 @@
     @SuppressWarnings("unchecked")
     List<SetupTaskPerformer> performers = (List<SetupTaskPerformer>)contexts;
     allPromptedPerfomers.addAll(performers);
+
     for (SetupTaskPerformer performer : performers)
     {
       boolean resolvedAll = true;
diff --git a/releng/org.eclipse.oomph.releng/Build Linux 64bit.launch b/releng/org.eclipse.oomph.releng/Build Linux 64bit.launch
new file mode 100644
index 0000000..f4c21b4
--- /dev/null
+++ b/releng/org.eclipse.oomph.releng/Build Linux 64bit.launch
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<launchConfiguration type="org.eclipse.ui.externaltools.ProgramLaunchConfigurationType">
+<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
+<listEntry value="org.eclipse.ui.externaltools.launchGroup"/>
+</listAttribute>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_LOCATION" value="/usr/bin/mvn"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS" value="-Dbuild.id=Local&#13;&#10;-Dgit.commit=Unknown&#13;&#10;-DskipTests=true&#13;&#10;-Denv=linux64&#13;&#10;-DDtycho.debug.resolver=true&#13;&#10;-DX&#13;&#10;clean&#13;&#10;verify"/>
+<stringAttribute key="org.eclipse.ui.externaltools.ATTR_WORKING_DIRECTORY" value="${git.clone.oomph}"/>
+</launchConfiguration>