Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/DeploymentService/PluginDescription.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/DeploymentService/PluginDescription.cs	(revision 2860)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/DeploymentService/PluginDescription.cs	(revision 2922)
@@ -45,5 +45,7 @@
 
     #region IPluginDescription Members
-
+    public string Description {
+      get { return string.Empty; }
+    }
 
     [Obsolete]
@@ -63,4 +65,8 @@
 
     #endregion
+
+    public override string ToString() {
+      return Name + " " + Version;
+    }
   }
 }
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManager.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManager.cs	(revision 2860)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManager.cs	(revision 2922)
@@ -28,4 +28,5 @@
 using System.ComponentModel;
 using System.Reflection;
+using ICSharpCode.SharpZipLib.Zip;
 
 namespace HeuristicLab.PluginInfrastructure.Advanced {
@@ -40,124 +41,66 @@
     internal event EventHandler<PluginInfrastructureEventArgs> PluginInstalled;
 
-
-    public IEnumerable<PluginDescription> Plugins {
-      get { return pluginManager.Plugins; }
-    }
-
     private string pluginDir;
-    private string updateLocationUrl;
-    private PluginManager pluginManager;
     public InstallationManager(string pluginDir) {
       this.pluginDir = pluginDir;
-      this.updateLocationUrl = "http://localhost:59253/UpdateLocation.svc";
-      this.pluginManager = new PluginManager(pluginDir);
-      this.pluginManager.DiscoverAndCheckPlugins();
-    }
-
-    public IEnumerable<string> Show(IEnumerable<string> pluginNames) {
-      foreach (PluginDescription desc in GetPluginDescriptions(pluginNames)) {
-        yield return GetInformation(desc);
-      }
-    }
-
-    internal string GetInformation(string pluginName) {
-      return GetInformation(GetPluginDescription(pluginName));
-    }
-
-    private string GetInformation(PluginDescription desc) {
-      StringBuilder builder = new StringBuilder();
-      builder.Append("Name: ").AppendLine(desc.Name);
-      builder.Append("Version: ").AppendLine(desc.Version.ToString());
-      builder.AppendLine("Description:").AppendLine(desc.Description);
-      if (!string.IsNullOrEmpty(desc.ContactName)) {
-        builder.Append("Contact: ").Append(desc.ContactName).Append(", ").AppendLine(desc.ContactEmail);
-      }
-      builder.AppendLine("This plugin is " + desc.PluginState.ToString().ToLowerInvariant() + ".");
-      builder.AppendLine("Files: ");
-      foreach (var file in desc.Files) {
-        builder.AppendLine(file.Type + " " + file.Name);
-      }
-      builder.AppendLine().AppendLine("Directly depends on:");
-      if (desc.Dependencies.Count() == 0) builder.AppendLine("None");
-      foreach (var dependency in desc.Dependencies) {
-        builder.AppendLine(dependency.Name + " " + dependency.Version);
-      }
-      builder.AppendLine().AppendFormat("Plugins directly dependent on {0}:", desc.Name).AppendLine();
-      var dependents = from x in pluginManager.Plugins
-                       where x.Dependencies.Contains(desc)
-                       select x;
-      if (dependents.Count() == 0) builder.AppendLine("None");
-      foreach (var dependent in dependents) {
-        builder.AppendLine(dependent.Name + " " + dependent.Version);
-      }
-      builder.AppendLine();
-      if (desc.PluginState == PluginState.Disabled) {
-        builder.AppendLine(DetermineProblem(desc));
-      }
-
-      return builder.ToString();
-    }
-
-    private static string DetermineProblem(PluginDescription desc) {
-      // either any file is missing
-      StringBuilder builder = new StringBuilder();
-      builder.AppendLine("Problem report:");
-      builder.AppendLine(desc.LoadingErrorInformation);
-      return builder.ToString();
-    }
-
-    private static IEnumerable<string> GetDeclaredDependencies(PluginDescription desc) {
-      var plugin = ApplicationManager.GetInstances<IPlugin>(desc).Single();
-      return plugin.GetType().GetCustomAttributes(typeof(PluginDependencyAttribute), false).Cast<PluginDependencyAttribute>().Select(x => x.Dependency);
-    }
-
-    private PluginDescription GetPluginDescription(string pluginName) {
-      var exactMatch = from pluginDesc in pluginManager.Plugins
-                       where string.Equals(pluginName, pluginDesc.Name, StringComparison.InvariantCultureIgnoreCase)
-                       select pluginDesc;
-      var inexactMatch = from pluginDesc in pluginManager.Plugins
-                         where MatchPluginNameInexact(pluginName, pluginDesc.Name)
-                         select pluginDesc;
-      return exactMatch.Count() > 0 ? exactMatch.Single() : inexactMatch.First();
-    }
-
-    private IEnumerable<PluginDescription> GetPluginDescriptions(IEnumerable<string> pluginNames) {
-      return from pluginName in pluginNames
-             select GetPluginDescription(pluginName);
-    }
-
-    private static bool MatchPluginNameInexact(string similarName, string actualName) {
-      return
-        // Core-3.2 == HeuristicLab.Core-3.2
-        actualName.Equals("HeuristicLab." + similarName, StringComparison.InvariantCultureIgnoreCase) ||
-        // HeuristicLab.Core == HeuristicLab.Core-3.2 (this should be save because we checked for exact matches first)
-        (Math.Abs(actualName.Length - similarName.Length) <= 4 && actualName.StartsWith(similarName, StringComparison.InvariantCultureIgnoreCase)) ||
-        // Core == HeuristicLab.Core-3.2
-        (Math.Abs(actualName.Length - similarName.Length) <= 17 && actualName.StartsWith("HeuristicLab." + similarName, StringComparison.InvariantCultureIgnoreCase));
-    }
-
-    public void Install(IEnumerable<string> pluginNames) {
-      throw new NotImplementedException();
-      //IEnumerable<PluginInformation> pluginsToInstall;
-      //using (UpdateLocationClient updateLocation = new UpdateLocationClient()) {
-      //  pluginsToInstall = from pluginName in pluginNames
-      //                     from matchingPlugin in updateLocation.GetAvailablePluginsByName(pluginName)
-      //                     select matchingPlugin;
-
-      //  var args = new PluginInfrastructureCancelEventArgs("Installing", pluginsToInstall);
-      //  OnPreInstall(args);
-      //  foreach (var pluginInfo in pluginsToInstall) {
-      //    var s = updateLocation.GetPluginFiles(pluginInfo);
-      //    Console.WriteLine("Downloading: {0} {1} {2}", pluginInfo.Name, pluginInfo.Version, pluginInfo.BuildDate);
-      //  }
-      //}
-      //OnInstalled(new PluginInfrastructureEventArgs("Installed", pluginsToInstall));
-    }
-
-    //private static PluginInformation GetMatchingPluginInformation(string pluginName, IEnumerable<PluginInformation> plugins) {
-    //  var exactMatch = from pluginDesc in plugins
+    }
+
+    //public IEnumerable<string> Show(IEnumerable<string> pluginNames) {
+    //  foreach (PluginDescription desc in GetPluginDescriptions(pluginNames)) {
+    //    yield return GetInformation(desc);
+    //  }
+    //}
+
+    //internal string GetInformation(string pluginName) {
+    //  return GetInformation(GetPluginDescription(pluginName));
+    //}
+
+    //private string GetInformation(PluginDescription desc) {
+    //  StringBuilder builder = new StringBuilder();
+    //  builder.Append("Name: ").AppendLine(desc.Name);
+    //  builder.Append("Version: ").AppendLine(desc.Version.ToString());
+    //  builder.AppendLine("Description:").AppendLine(desc.Description);
+    //  if (!string.IsNullOrEmpty(desc.ContactName)) {
+    //    builder.Append("Contact: ").Append(desc.ContactName).Append(", ").AppendLine(desc.ContactEmail);
+    //  }
+    //  builder.AppendLine("This plugin is " + desc.PluginState.ToString().ToLowerInvariant() + ".");
+    //  builder.AppendLine("Files: ");
+    //  foreach (var file in desc.Files) {
+    //    builder.AppendLine(file.Type + " " + file.Name);
+    //  }
+    //  builder.AppendLine().AppendLine("Directly depends on:");
+    //  if (desc.Dependencies.Count() == 0) builder.AppendLine("None");
+    //  foreach (var dependency in desc.Dependencies) {
+    //    builder.AppendLine(dependency.Name + " " + dependency.Version);
+    //  }
+    //  builder.AppendLine().AppendFormat("Plugins directly dependent on {0}:", desc.Name).AppendLine();
+    //  var dependents = from x in pluginManager.Plugins
+    //                   where x.Dependencies.Contains(desc)
+    //                   select x;
+    //  if (dependents.Count() == 0) builder.AppendLine("None");
+    //  foreach (var dependent in dependents) {
+    //    builder.AppendLine(dependent.Name + " " + dependent.Version);
+    //  }
+    //  builder.AppendLine();
+    //  if (desc.PluginState == PluginState.Disabled) {
+    //    builder.AppendLine(DetermineProblem(desc));
+    //  }
+
+    //  return builder.ToString();
+    //}
+
+    //private static string DetermineProblem(PluginDescription desc) {
+    //  // either any file is missing
+    //  StringBuilder builder = new StringBuilder();
+    //  builder.AppendLine("Problem report:");
+    //  builder.AppendLine(desc.LoadingErrorInformation);
+    //  return builder.ToString();
+    //}
+
+    //private PluginDescription GetPluginDescription(string pluginName) {
+    //  var exactMatch = from pluginDesc in pluginManager.Plugins
     //                   where string.Equals(pluginName, pluginDesc.Name, StringComparison.InvariantCultureIgnoreCase)
     //                   select pluginDesc;
-    //  var inexactMatch = from pluginDesc in plugins
+    //  var inexactMatch = from pluginDesc in pluginManager.Plugins
     //                     where MatchPluginNameInexact(pluginName, pluginDesc.Name)
     //                     select pluginDesc;
@@ -165,52 +108,124 @@
     //}
 
-    public void Remove(IEnumerable<string> pluginNames) {
-      var fileNames = from pluginToDelete in PluginDescriptionIterator.IterateDependentsTopDown(GetPluginDescriptions(pluginNames), pluginManager.Plugins)
+    //private IEnumerable<PluginDescription> GetPluginDescriptions(IEnumerable<string> pluginNames) {
+    //  return from pluginName in pluginNames
+    //         select GetPluginDescription(pluginName);
+    //}
+
+    //private static bool MatchPluginNameInexact(string similarName, string actualName) {
+    //  return
+    //    // Core-3.2 == HeuristicLab.Core-3.2
+    //    actualName.Equals("HeuristicLab." + similarName, StringComparison.InvariantCultureIgnoreCase) ||
+    //    // HeuristicLab.Core == HeuristicLab.Core-3.2 (this should be save because we checked for exact matches first)
+    //    (Math.Abs(actualName.Length - similarName.Length) <= 4 && actualName.StartsWith(similarName, StringComparison.InvariantCultureIgnoreCase)) ||
+    //    // Core == HeuristicLab.Core-3.2
+    //    (Math.Abs(actualName.Length - similarName.Length) <= 17 && actualName.StartsWith("HeuristicLab." + similarName, StringComparison.InvariantCultureIgnoreCase));
+    //}
+
+
+    /// <summary>
+    /// Retrieves a list of plugins available at the remote server
+    /// </summary>
+    /// <param name="connectionString"></param>
+    /// <returns></returns>
+    public IEnumerable<IPluginDescription> GetRemotePluginList(string connectionString) {
+      using (var client = new DeploymentService.UpdateClient()) {
+        return client.GetPlugins();
+      }
+    }
+
+    /// <summary>
+    /// Retrieves the list of products available at the remote server
+    /// </summary>
+    /// <param name="connectionString"></param>
+    /// <returns></returns>
+    public IEnumerable<DeploymentService.ProductDescription> GetRemoteProductList(string connectionString) {
+      using (var client = new DeploymentService.UpdateClient()) {
+        return client.GetProducts();
+      }
+    }
+
+    /// <summary>
+    ///  Installs plugins from remote server
+    /// </summary>
+    /// <param name="connectionString"></param>
+    /// <param name="pluginNames"></param>
+    public void Install(string connectionString, IEnumerable<IPluginDescription> plugins) {
+      using (var client = new DeploymentService.UpdateClient()) {
+        var args = new PluginInfrastructureCancelEventArgs(plugins.Select(x => x.Name + " " + x.Version));
+        OnPreInstall(args);
+        foreach (DeploymentService.PluginDescription plugin in plugins) {
+          byte[] zippedPackage = client.GetPlugin(plugin);
+          Unpack(zippedPackage);
+          OnInstalled(new PluginInfrastructureEventArgs(plugin));
+        }
+      }
+    }
+
+    /// <summary>
+    /// Updates plugins from remote server
+    /// </summary>
+    /// <param name="pluginNames"></param>
+    public void Update(string connectionString, IEnumerable<IPluginDescription> plugins) {
+      PluginInfrastructureCancelEventArgs args = new PluginInfrastructureCancelEventArgs(plugins.Select(x => x.Name + " " + x.Version));
+      OnPreUpdate(args);
+      if (!args.Cancel) {
+        using (var client = new DeploymentService.UpdateClient()) {
+          foreach (DeploymentService.PluginDescription plugin in plugins) {
+            byte[] zippedPackage = client.GetPlugin(plugin);
+            Unpack(zippedPackage);
+            OnUpdated(new PluginInfrastructureEventArgs(plugin));
+          }
+        }
+      }
+    }
+
+    /// <summary>
+    /// Deletes all plugin files from local installation
+    /// </summary>
+    /// <param name="pluginNames"></param>
+    public void Remove(IEnumerable<IPluginDescription> plugins) {
+      var fileNames = from pluginToDelete in plugins
                       from file in pluginToDelete.Files
                       select Path.Combine(pluginDir, file.Name);
-      var args = new PluginInfrastructureCancelEventArgs("Deleting", fileNames);
+      var args = new PluginInfrastructureCancelEventArgs(fileNames);
       OnPreDelete(args);
       if (!args.Cancel) {
         foreach (string fileName in fileNames) {
-          Console.WriteLine("Deleting file " + fileName);
-          // File.Delete(fileName);
-        }
-
-        OnDeleted(new PluginInfrastructureEventArgs("Deleted", fileNames));
-      }
-    }
-
-    public void Update(IEnumerable<string> pluginNames) {
-      var pluginDescriptions = from name in pluginNames
-                               select GetPluginDescription(name);
-      Dictionary<DeploymentService.PluginDescription, string> matchingPlugins = new Dictionary<DeploymentService.PluginDescription, string>();
-      foreach (var updateLocation in HeuristicLab.PluginInfrastructure.Properties.Settings.Default.UpdateLocations) {
-        using (var client = new DeploymentService.UpdateClient("", updateLocation)) {
-          var updateLocationMatchingPlugins = from desc in pluginDescriptions
-                                              from info in client.GetPlugins()
-                                              where desc.Name == info.Name
-                                              select info;
-          foreach (DeploymentService.PluginDescription info in updateLocationMatchingPlugins) {
-            // keep only the highest version of any plugin
-            var existingPlugin = matchingPlugins.Keys.FirstOrDefault(x => x.Name == info.Name);
-            if (existingPlugin == null || existingPlugin.Version < info.Version) {
-              matchingPlugins.Remove(existingPlugin);
-              matchingPlugins.Add(info, updateLocation);
+          File.Delete(fileName);
+          OnDeleted(new PluginInfrastructureEventArgs(fileName));
+        }
+      }
+    }
+
+    private void Unpack(byte[] zippedPackage) {
+      using (ZipInputStream s = new ZipInputStream(new MemoryStream(zippedPackage))) {
+        ZipEntry theEntry;
+        string tmpEntry = String.Empty;
+        while ((theEntry = s.GetNextEntry()) != null) {
+          string directoryName = pluginDir;
+          string fileName = Path.GetFileName(theEntry.Name);
+          // create directory 
+          if (directoryName != "") {
+            Directory.CreateDirectory(directoryName);
+          }
+          if (fileName != String.Empty) {
+            string fullPath = Path.Combine(directoryName, fileName);
+            string fullDirPath = Path.GetDirectoryName(fullPath);
+            if (!Directory.Exists(fullDirPath)) Directory.CreateDirectory(fullDirPath);
+            FileStream streamWriter = File.Create(fullPath);
+            int size = 2048;
+            byte[] data = new byte[2048];
+            while (true) {
+              size = s.Read(data, 0, data.Length);
+              if (size > 0) {
+                streamWriter.Write(data, 0, size);
+              } else {
+                break;
+              }
             }
+            streamWriter.Close();
           }
         }
-      }
-      PluginInfrastructureCancelEventArgs args = new PluginInfrastructureCancelEventArgs("Updating", matchingPlugins.Keys);
-      OnPreUpdate(args);
-      if (!args.Cancel) {
-        var groupedInfos = matchingPlugins.GroupBy(x => x.Value);
-        foreach (var group in groupedInfos) {
-          using (var client = new DeploymentService.UpdateClient(group.Key)) {
-            foreach (var info in group) {
-              client.GetPlugin(info.Key);
-            }
-          }
-        }
-        OnUpdated(new PluginInfrastructureEventArgs("Updated", matchingPlugins.Keys));
       }
     }
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerConsole.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerConsole.cs	(revision 2860)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerConsole.cs	(revision 2922)
@@ -27,4 +27,5 @@
 using System.IO;
 using System.ComponentModel;
+using HeuristicLab.PluginInfrastructure.Advanced.DeploymentService;
 
 
@@ -32,5 +33,12 @@
   public class InstallationManagerConsole {
     private InstallationManager installManager;
+    private string connectionString;
     public InstallationManagerConsole(string pluginDir) {
+
+      // get default connection string
+      using (var client = new UpdateClient()) {
+        connectionString = client.Endpoint.Address.ToString();
+      }
+
       this.installManager = new InstallationManager(pluginDir);
       installManager.PreInstallPlugin += new EventHandler<PluginInfrastructureCancelEventArgs>(installManager_PreInstallPlugin);
@@ -44,7 +52,6 @@
     void installManager_PreUpdatePlugin(object sender, PluginInfrastructureCancelEventArgs e) {
       Console.WriteLine("Following plugins are updated:");
-      var infos = (IEnumerable<PluginDescription>)e.Entity;
-      foreach (var info in infos) {
-        Console.WriteLine(info.Name + " " + info.Version);
+      foreach (var info in e.Entities) {
+        Console.WriteLine(e);
       }
       if (GetUserConfirmation()) e.Cancel = false;
@@ -54,5 +61,5 @@
 
     void installManager_PluginUpdated(object sender, PluginInfrastructureEventArgs e) {
-      foreach (var info in (IEnumerable<PluginDescription>)e.Entity)
+      foreach (var info in (IEnumerable<IPluginDescription>)e.Entity)
         Console.WriteLine("Updated: {0}", info.Name);
     }
@@ -60,6 +67,5 @@
     void installManager_PreRemovePlugin(object sender, PluginInfrastructureCancelEventArgs e) {
       Console.WriteLine("Following files are deleted:");
-      var fileNames = (IEnumerable<string>)e.Entity;
-      foreach (string fileName in fileNames) {
+      foreach (string fileName in e.Entities) {
         Console.WriteLine(fileName);
       }
@@ -90,18 +96,18 @@
 
     public void Show(IEnumerable<string> pluginNames) {
-      foreach (string pluginName in pluginNames)
-        Console.WriteLine(installManager.GetInformation(pluginName));
+      //foreach (string pluginName in pluginNames)
+      //  Console.WriteLine(installManager.GetInformation(pluginName));
     }
 
     public void Install(IEnumerable<string> pluginNames) {
-      installManager.Install(pluginNames);
+      //installManager.Install(connectionString, pluginNames);
     }
 
     public void Remove(IEnumerable<string> pluginNames) {
-      installManager.Remove(pluginNames);
+      // installManager.Remove(pluginNames);
     }
 
     public void Update(IEnumerable<string> pluginNames) {
-      installManager.Update(pluginNames);
+      // installManager.Update(connectionString, pluginNames);
     }
   }
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.Designer.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.Designer.cs	(revision 2860)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.Designer.cs	(revision 2922)
@@ -25,55 +25,183 @@
     private void InitializeComponent() {
       this.statusStrip = new System.Windows.Forms.StatusStrip();
-      this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
-      this.detailsTextBox = new System.Windows.Forms.TextBox();
-      this.pluginsListView = new System.Windows.Forms.ListView();
-      this.tableLayoutPanel.SuspendLayout();
+      this.toolStripProgressBar = new System.Windows.Forms.ToolStripProgressBar();
+      this.toolStripStatusLabel = new System.Windows.Forms.ToolStripStatusLabel();
+      this.removeButton = new System.Windows.Forms.Button();
+      this.serverUrlLabel = new System.Windows.Forms.Label();
+      this.serverUrlTextBox = new System.Windows.Forms.TextBox();
+      this.refreshButton = new System.Windows.Forms.Button();
+      this.installButton = new System.Windows.Forms.Button();
+      this.tabControl = new System.Windows.Forms.TabControl();
+      this.localPluginsTabPage = new System.Windows.Forms.TabPage();
+      this.localPluginManager = new HeuristicLab.PluginInfrastructure.Advanced.LocalPluginManager();
+      this.remotePluginsTabPage = new System.Windows.Forms.TabPage();
+      this.remotePluginInstaller = new HeuristicLab.PluginInfrastructure.Advanced.RemotePluginInstaller();
+      this.logTabPage = new System.Windows.Forms.TabPage();
+      this.logTextBox = new System.Windows.Forms.TextBox();
+      this.statusStrip.SuspendLayout();
+      this.tabControl.SuspendLayout();
+      this.localPluginsTabPage.SuspendLayout();
+      this.remotePluginsTabPage.SuspendLayout();
+      this.logTabPage.SuspendLayout();
       this.SuspendLayout();
       // 
       // statusStrip
       // 
-      this.statusStrip.Location = new System.Drawing.Point(0, 433);
+      this.statusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+            this.toolStripProgressBar,
+            this.toolStripStatusLabel});
+      this.statusStrip.Location = new System.Drawing.Point(0, 612);
       this.statusStrip.Name = "statusStrip";
-      this.statusStrip.Size = new System.Drawing.Size(394, 22);
+      this.statusStrip.Size = new System.Drawing.Size(606, 22);
       this.statusStrip.TabIndex = 0;
-      this.statusStrip.Text = "statusStrip1";
-      // 
-      // tableLayoutPanel
-      // 
-      this.tableLayoutPanel.ColumnCount = 1;
-      this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
-      this.tableLayoutPanel.Controls.Add(this.detailsTextBox, 0, 1);
-      this.tableLayoutPanel.Controls.Add(this.pluginsListView, 0, 0);
-      this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
-      this.tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
-      this.tableLayoutPanel.Name = "tableLayoutPanel";
-      this.tableLayoutPanel.RowCount = 2;
-      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 64F));
-      this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 36F));
-      this.tableLayoutPanel.Size = new System.Drawing.Size(394, 433);
-      this.tableLayoutPanel.TabIndex = 1;
-      // 
-      // detailsTextBox
-      // 
-      this.detailsTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
-      this.detailsTextBox.Location = new System.Drawing.Point(3, 280);
-      this.detailsTextBox.Multiline = true;
-      this.detailsTextBox.Name = "detailsTextBox";
-      this.detailsTextBox.ReadOnly = true;
-      this.detailsTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
-      this.detailsTextBox.Size = new System.Drawing.Size(388, 150);
-      this.detailsTextBox.TabIndex = 0;
-      // 
-      // pluginsListView
-      // 
-      this.pluginsListView.Dock = System.Windows.Forms.DockStyle.Fill;
-      this.pluginsListView.Location = new System.Drawing.Point(3, 3);
-      this.pluginsListView.MultiSelect = false;
-      this.pluginsListView.Name = "pluginsListView";
-      this.pluginsListView.Size = new System.Drawing.Size(388, 271);
-      this.pluginsListView.TabIndex = 1;
-      this.pluginsListView.UseCompatibleStateImageBehavior = false;
-      this.pluginsListView.View = System.Windows.Forms.View.List;
-      this.pluginsListView.SelectedIndexChanged += new System.EventHandler(this.pluginsListView_SelectedIndexChanged);
+      // 
+      // toolStripProgressBar
+      // 
+      this.toolStripProgressBar.MarqueeAnimationSpeed = 30;
+      this.toolStripProgressBar.Name = "toolStripProgressBar";
+      this.toolStripProgressBar.Size = new System.Drawing.Size(100, 16);
+      this.toolStripProgressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
+      this.toolStripProgressBar.Visible = false;
+      // 
+      // toolStripStatusLabel
+      // 
+      this.toolStripStatusLabel.Name = "toolStripStatusLabel";
+      this.toolStripStatusLabel.Size = new System.Drawing.Size(0, 17);
+      // 
+      // removeButton
+      // 
+      this.removeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.removeButton.Enabled = false;
+      this.removeButton.Location = new System.Drawing.Point(6, 557);
+      this.removeButton.Name = "removeButton";
+      this.removeButton.Size = new System.Drawing.Size(109, 23);
+      this.removeButton.TabIndex = 11;
+      this.removeButton.Text = "Remove Plugins";
+      this.removeButton.UseVisualStyleBackColor = true;
+      this.removeButton.Click += new System.EventHandler(this.removeButton_Click);
+      // 
+      // serverUrlLabel
+      // 
+      this.serverUrlLabel.AutoSize = true;
+      this.serverUrlLabel.Location = new System.Drawing.Point(9, 11);
+      this.serverUrlLabel.Name = "serverUrlLabel";
+      this.serverUrlLabel.Size = new System.Drawing.Size(73, 13);
+      this.serverUrlLabel.TabIndex = 13;
+      this.serverUrlLabel.Text = "Plugin Server:";
+      // 
+      // serverUrlTextBox
+      // 
+      this.serverUrlTextBox.Location = new System.Drawing.Point(88, 8);
+      this.serverUrlTextBox.Name = "serverUrlTextBox";
+      this.serverUrlTextBox.Size = new System.Drawing.Size(264, 20);
+      this.serverUrlTextBox.TabIndex = 12;
+      // 
+      // refreshButton
+      // 
+      this.refreshButton.Location = new System.Drawing.Point(358, 6);
+      this.refreshButton.Name = "refreshButton";
+      this.refreshButton.Size = new System.Drawing.Size(75, 23);
+      this.refreshButton.TabIndex = 11;
+      this.refreshButton.Text = "Refresh";
+      this.refreshButton.UseVisualStyleBackColor = true;
+      this.refreshButton.Click += new System.EventHandler(this.refreshButton_Click);
+      // 
+      // installButton
+      // 
+      this.installButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
+      this.installButton.Enabled = false;
+      this.installButton.Location = new System.Drawing.Point(6, 557);
+      this.installButton.Name = "installButton";
+      this.installButton.Size = new System.Drawing.Size(132, 23);
+      this.installButton.TabIndex = 15;
+      this.installButton.Text = "Download and Install";
+      this.installButton.UseVisualStyleBackColor = true;
+      this.installButton.Click += new System.EventHandler(this.updateButton_Click);
+      // 
+      // tabControl
+      // 
+      this.tabControl.Controls.Add(this.localPluginsTabPage);
+      this.tabControl.Controls.Add(this.remotePluginsTabPage);
+      this.tabControl.Controls.Add(this.logTabPage);
+      this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.tabControl.Location = new System.Drawing.Point(0, 0);
+      this.tabControl.Name = "tabControl";
+      this.tabControl.SelectedIndex = 0;
+      this.tabControl.Size = new System.Drawing.Size(606, 612);
+      this.tabControl.TabIndex = 16;
+      // 
+      // localPluginsTabPage
+      // 
+      this.localPluginsTabPage.Controls.Add(this.removeButton);
+      this.localPluginsTabPage.Controls.Add(this.localPluginManager);
+      this.localPluginsTabPage.Location = new System.Drawing.Point(4, 22);
+      this.localPluginsTabPage.Name = "localPluginsTabPage";
+      this.localPluginsTabPage.Padding = new System.Windows.Forms.Padding(3);
+      this.localPluginsTabPage.Size = new System.Drawing.Size(598, 586);
+      this.localPluginsTabPage.TabIndex = 0;
+      this.localPluginsTabPage.Text = "Installed Plugins";
+      this.localPluginsTabPage.UseVisualStyleBackColor = true;
+      // 
+      // localPluginManager
+      // 
+      this.localPluginManager.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+                  | System.Windows.Forms.AnchorStyles.Left)
+                  | System.Windows.Forms.AnchorStyles.Right)));
+      this.localPluginManager.Location = new System.Drawing.Point(6, 6);
+      this.localPluginManager.Name = "localPluginManager";
+      this.localPluginManager.Plugins = null;
+      this.localPluginManager.Size = new System.Drawing.Size(584, 545);
+      this.localPluginManager.TabIndex = 0;
+      this.localPluginManager.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.localPluginManager_ItemChecked);
+      // 
+      // remotePluginsTabPage
+      // 
+      this.remotePluginsTabPage.Controls.Add(this.serverUrlLabel);
+      this.remotePluginsTabPage.Controls.Add(this.remotePluginInstaller);
+      this.remotePluginsTabPage.Controls.Add(this.serverUrlTextBox);
+      this.remotePluginsTabPage.Controls.Add(this.refreshButton);
+      this.remotePluginsTabPage.Controls.Add(this.installButton);
+      this.remotePluginsTabPage.Location = new System.Drawing.Point(4, 22);
+      this.remotePluginsTabPage.Name = "remotePluginsTabPage";
+      this.remotePluginsTabPage.Padding = new System.Windows.Forms.Padding(3);
+      this.remotePluginsTabPage.Size = new System.Drawing.Size(598, 586);
+      this.remotePluginsTabPage.TabIndex = 1;
+      this.remotePluginsTabPage.Text = "Remote Plugins";
+      this.remotePluginsTabPage.UseVisualStyleBackColor = true;
+      // 
+      // remotePluginInstaller
+      // 
+      this.remotePluginInstaller.AllPlugins = new HeuristicLab.PluginInfrastructure.IPluginDescription[0];
+      this.remotePluginInstaller.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
+                  | System.Windows.Forms.AnchorStyles.Left)
+                  | System.Windows.Forms.AnchorStyles.Right)));
+      this.remotePluginInstaller.Location = new System.Drawing.Point(6, 35);
+      this.remotePluginInstaller.Name = "remotePluginInstaller";
+      this.remotePluginInstaller.NewPlugins = new HeuristicLab.PluginInfrastructure.IPluginDescription[0];
+      this.remotePluginInstaller.Products = new HeuristicLab.PluginInfrastructure.Advanced.DeploymentService.ProductDescription[0];
+      this.remotePluginInstaller.Size = new System.Drawing.Size(584, 516);
+      this.remotePluginInstaller.TabIndex = 14;
+      this.remotePluginInstaller.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.remotePluginInstaller_ItemChecked);
+      // 
+      // logTabPage
+      // 
+      this.logTabPage.Controls.Add(this.logTextBox);
+      this.logTabPage.Location = new System.Drawing.Point(4, 22);
+      this.logTabPage.Name = "logTabPage";
+      this.logTabPage.Size = new System.Drawing.Size(598, 586);
+      this.logTabPage.TabIndex = 2;
+      this.logTabPage.Text = "Log";
+      this.logTabPage.UseVisualStyleBackColor = true;
+      // 
+      // logTextBox
+      // 
+      this.logTextBox.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.logTextBox.Location = new System.Drawing.Point(0, 0);
+      this.logTextBox.Multiline = true;
+      this.logTextBox.Name = "logTextBox";
+      this.logTextBox.ReadOnly = true;
+      this.logTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
+      this.logTextBox.Size = new System.Drawing.Size(598, 586);
+      this.logTextBox.TabIndex = 0;
       // 
       // InstallationManagerForm
@@ -81,11 +209,17 @@
       this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
       this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
-      this.ClientSize = new System.Drawing.Size(394, 455);
-      this.Controls.Add(this.tableLayoutPanel);
+      this.ClientSize = new System.Drawing.Size(606, 634);
+      this.Controls.Add(this.tabControl);
       this.Controls.Add(this.statusStrip);
       this.Name = "InstallationManagerForm";
       this.Text = "InstallationManager";
-      this.tableLayoutPanel.ResumeLayout(false);
-      this.tableLayoutPanel.PerformLayout();
+      this.statusStrip.ResumeLayout(false);
+      this.statusStrip.PerformLayout();
+      this.tabControl.ResumeLayout(false);
+      this.localPluginsTabPage.ResumeLayout(false);
+      this.remotePluginsTabPage.ResumeLayout(false);
+      this.remotePluginsTabPage.PerformLayout();
+      this.logTabPage.ResumeLayout(false);
+      this.logTabPage.PerformLayout();
       this.ResumeLayout(false);
       this.PerformLayout();
@@ -96,7 +230,18 @@
 
     private System.Windows.Forms.StatusStrip statusStrip;
-    private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
-    private System.Windows.Forms.TextBox detailsTextBox;
-    private System.Windows.Forms.ListView pluginsListView;
+    private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar;
+    private System.Windows.Forms.ToolStripStatusLabel toolStripStatusLabel;
+    private System.Windows.Forms.Label serverUrlLabel;
+    private System.Windows.Forms.TextBox serverUrlTextBox;
+    private System.Windows.Forms.Button refreshButton;
+    private LocalPluginManager localPluginManager;
+    private RemotePluginInstaller remotePluginInstaller;
+    private System.Windows.Forms.Button removeButton;
+    private System.Windows.Forms.Button installButton;
+    private System.Windows.Forms.TabControl tabControl;
+    private System.Windows.Forms.TabPage localPluginsTabPage;
+    private System.Windows.Forms.TabPage remotePluginsTabPage;
+    private System.Windows.Forms.TabPage logTabPage;
+    private System.Windows.Forms.TextBox logTextBox;
   }
 }
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.cs	(revision 2860)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/InstallationManagerForm.cs	(revision 2922)
@@ -12,36 +12,336 @@
 namespace HeuristicLab.PluginInfrastructure.Advanced {
   public partial class InstallationManagerForm : Form {
+    private class UpdateOrInstallPluginsBackgroundWorkerArgument {
+      public string ConnectionString { get; set; }
+      public IEnumerable<IPluginDescription> PluginsToUpdate { get; set; }
+      public IEnumerable<IPluginDescription> PluginsToInstall { get; set; }
+    }
+
+    private class RemovePluginsBackgroundWorkerArgument {
+      public IEnumerable<IPluginDescription> PluginsToRemove { get; set; }
+    }
+
+    private class RefreshBackgroundWorkerResult {
+      public IEnumerable<IPluginDescription> RemotePlugins { get; set; }
+      public IEnumerable<DeploymentService.ProductDescription> RemoteProducts { get; set; }
+    }
+
     private InstallationManager installationManager;
+    private BackgroundWorker refreshServerPluginsBackgroundWorker;
+    private BackgroundWorker updateOrInstallPluginsBackgroundWorker;
+    private BackgroundWorker removePluginsBackgroundWorker;
+    private BackgroundWorker refreshLocalPluginsBackgroundWorker;
+    private string pluginDir;
 
     public InstallationManagerForm() {
       InitializeComponent();
-      this.installationManager = new InstallationManager(Path.GetDirectoryName(Application.ExecutablePath));
-
-      UpdatePluginsList();
-    }
-
-    private void UpdatePluginsList() {
-      foreach (var plugin in installationManager.Plugins) {
-        pluginsListView.Items.Add(CreatePluginItem(plugin));
-      }
-    }
-
-    private static ListViewItem CreatePluginItem(IPluginDescription plugin) {
-      ListViewItem item = new ListViewItem();
-      item.Tag = plugin;
-      item.Text = plugin.Name + "-" + plugin.Version.ToString();
-      return item;
-    }
-
-    private void pluginsListView_SelectedIndexChanged(object sender, EventArgs e) {
-      if (pluginsListView.SelectedItems.Count > 0) {
-        ListViewItem selecteditem = pluginsListView.SelectedItems[0];
-        IPluginDescription desc = (IPluginDescription)selecteditem.Tag;
-        UpdateDetailsBox((PluginDescription)desc);
-      }
-    }
-
-    private void UpdateDetailsBox(PluginDescription desc) {
-      detailsTextBox.Text = installationManager.GetInformation(desc.Name);
+
+      pluginDir = Application.StartupPath;
+
+      #region initialize background workers
+      refreshServerPluginsBackgroundWorker = new BackgroundWorker();
+      refreshServerPluginsBackgroundWorker.DoWork += new DoWorkEventHandler(refreshServerPluginsBackgroundWorker_DoWork);
+      refreshServerPluginsBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(refreshServerPluginsBackgroundWorker_RunWorkerCompleted);
+
+      updateOrInstallPluginsBackgroundWorker = new BackgroundWorker();
+      updateOrInstallPluginsBackgroundWorker.DoWork += new DoWorkEventHandler(updateOrInstallPluginsBackgroundWorker_DoWork);
+      updateOrInstallPluginsBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(updateOrInstallPluginsBackgroundWorker_RunWorkerCompleted);
+
+      removePluginsBackgroundWorker = new BackgroundWorker();
+      removePluginsBackgroundWorker.DoWork += new DoWorkEventHandler(removePluginsBackgroundWorker_DoWork);
+      removePluginsBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(removePluginsBackgroundWorker_RunWorkerCompleted);
+
+      refreshLocalPluginsBackgroundWorker = new BackgroundWorker();
+      refreshLocalPluginsBackgroundWorker.DoWork += new DoWorkEventHandler(refreshLocalPluginsBackgroundWorker_DoWork);
+      refreshLocalPluginsBackgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(refreshLocalPluginsBackgroundWorker_RunWorkerCompleted);
+      #endregion
+
+      // get default connection string
+      using (var client = new DeploymentService.UpdateClient()) {
+        serverUrlTextBox.Text = client.Endpoint.Address.ToString();
+      }
+
+      installationManager = new InstallationManager(pluginDir);
+      installationManager.PluginInstalled += new EventHandler<PluginInfrastructureEventArgs>(installationManager_PluginInstalled);
+      installationManager.PluginRemoved += new EventHandler<PluginInfrastructureEventArgs>(installationManager_PluginRemoved);
+      installationManager.PluginUpdated += new EventHandler<PluginInfrastructureEventArgs>(installationManager_PluginUpdated);
+      installationManager.PreInstallPlugin += new EventHandler<PluginInfrastructureCancelEventArgs>(installationManager_PreInstallPlugin);
+      installationManager.PreRemovePlugin += new EventHandler<PluginInfrastructureCancelEventArgs>(installationManager_PreRemovePlugin);
+      installationManager.PreUpdatePlugin += new EventHandler<PluginInfrastructureCancelEventArgs>(installationManager_PreUpdatePlugin);
+
+      RefreshLocalPluginListAsync();
+    }
+
+    #region event handlers for refresh local plugin list backgroundworker
+    private IEnumerable<PluginDescription> ReloadLocalPlugins() {
+      PluginManager pluginManager = new PluginManager(Application.StartupPath);
+      pluginManager.PluginLoaded += pluginManager_PluginLoaded;
+      pluginManager.PluginUnloaded += pluginManager_PluginUnloaded;
+      pluginManager.Initializing += pluginManager_Initializing;
+      pluginManager.Initialized += pluginManager_Initialized;
+
+      pluginManager.DiscoverAndCheckPlugins();
+
+      pluginManager.PluginLoaded -= pluginManager_PluginLoaded;
+      pluginManager.PluginUnloaded -= pluginManager_PluginUnloaded;
+      pluginManager.Initializing -= pluginManager_Initializing;
+      pluginManager.Initialized -= pluginManager_Initialized;
+
+      return pluginManager.Plugins;
+    }
+    void refreshLocalPluginsBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
+      if (!e.Cancelled && e.Error == null) {
+        UpdateLocalPluginList((IEnumerable<PluginDescription>)e.Result);
+        UpdateControlsConnected();
+      }
+    }
+
+    void refreshLocalPluginsBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
+      var plugins = ReloadLocalPlugins();
+      e.Result = plugins;
+    }
+    #endregion
+
+    #region event handlers for plugin removal background worker
+    void removePluginsBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
+      if (!e.Cancelled && e.Error == null) {
+        RefreshLocalPluginListAsync();
+        UpdateControlsConnected();
+      }
+    }
+
+    void removePluginsBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
+      IEnumerable<IPluginDescription> pluginsToRemove = (IEnumerable<IPluginDescription>)e.Argument;
+      installationManager.Remove(pluginsToRemove);
+    }
+    #endregion
+
+    #region event handlers for plugin update background worker
+    void updateOrInstallPluginsBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
+      if (!e.Cancelled && e.Error == null) {
+        RefreshLocalPluginListAsync();
+        RefreshRemotePluginListAsync();
+        UpdateControlsConnected();
+      } else {
+        UpdateControlsDisconnected();
+      }
+    }
+
+    void updateOrInstallPluginsBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
+      UpdateOrInstallPluginsBackgroundWorkerArgument info = (UpdateOrInstallPluginsBackgroundWorkerArgument)e.Argument;
+      installationManager.Install(info.ConnectionString, info.PluginsToInstall);
+      installationManager.Update(info.ConnectionString, info.PluginsToUpdate);
+    }
+    #endregion
+
+    #region event handlers for refresh server plugins background worker
+    void refreshServerPluginsBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
+      if (!e.Cancelled && e.Result != null) {
+        RefreshBackgroundWorkerResult refreshResult = (RefreshBackgroundWorkerResult)e.Result;
+        UpdateRemotePluginList(refreshResult.RemoteProducts, refreshResult.RemotePlugins);
+        UpdateControlsConnected();
+      } else {
+        UpdateControlsDisconnected();
+      }
+    }
+
+    void refreshServerPluginsBackgroundWorker_DoWork(object sender, DoWorkEventArgs e) {
+      string connectionString = (string)e.Argument;
+
+      RefreshBackgroundWorkerResult result = new RefreshBackgroundWorkerResult();
+      result.RemotePlugins = installationManager.GetRemotePluginList(connectionString);
+      result.RemoteProducts = installationManager.GetRemoteProductList(connectionString);
+      e.Cancel = false;
+      e.Result = result;
+    }
+
+
+
+    #endregion
+
+    #region plugin manager event handlers
+    void pluginManager_Initialized(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Initialized PluginInfrastructure");
+    }
+
+    void pluginManager_Initializing(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Initializing PluginInfrastructure");
+    }
+
+    void pluginManager_PluginUnloaded(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Unloaded " + e.Entity);
+    }
+
+    void pluginManager_PluginLoaded(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Loaded " + e.Entity);
+    }
+    #endregion
+
+    #region installation manager event handlers
+    void installationManager_PreUpdatePlugin(object sender, PluginInfrastructureCancelEventArgs e) {
+      if (ConfirmUpdateAction(e.Entities) == true) e.Cancel = false;
+      else e.Cancel = true;
+    }
+
+    void installationManager_PreRemovePlugin(object sender, PluginInfrastructureCancelEventArgs e) {
+      if (ConfirmRemoveAction(e.Entities) == true) e.Cancel = false;
+      else e.Cancel = true;
+    }
+
+    void installationManager_PreInstallPlugin(object sender, PluginInfrastructureCancelEventArgs e) {
+      if (e.Entities.Count() > 0)
+        SetStatusStrip("Installing " + e.Entities.Aggregate((a, b) => a + Environment.NewLine + b));
+    }
+
+    void installationManager_PluginUpdated(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Updated " + e.Entity);
+    }
+
+    void installationManager_PluginRemoved(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Removed " + e.Entity);
+    }
+
+    void installationManager_PluginInstalled(object sender, PluginInfrastructureEventArgs e) {
+      SetStatusStrip("Installed " + e.Entity);
+    }
+    #endregion
+
+    private void SetStatusStrip(string msg) {
+      if (InvokeRequired) Invoke((Action<string>)SetStatusStrip, msg);
+      else {
+        toolStripStatusLabel.Text = msg;
+        logTextBox.Text += DateTime.Now + ": " + msg + Environment.NewLine;
+      }
+    }
+
+    #region button events
+
+    private void refreshButton_Click(object sender, EventArgs e) {
+      RefreshRemotePluginListAsync();
+    }
+
+    private void updateButton_Click(object sender, EventArgs e) {
+      Cursor = Cursors.AppStarting;
+      toolStripProgressBar.Visible = true;
+      DisableControls();
+      var updateOrInstallInfo = new UpdateOrInstallPluginsBackgroundWorkerArgument();
+      // if there is a local plugin with same name and same major and minor version then it's an update
+      var pluginsToUpdate = from remotePlugin in remotePluginInstaller.CheckedPlugins
+                            let matchingLocalPlugins = from localPlugin in localPluginManager.Plugins
+                                                       where localPlugin.Name == remotePlugin.Name
+                                                       where localPlugin.Version.Major == remotePlugin.Version.Major
+                                                       where localPlugin.Version.Minor == remotePlugin.Version.Minor
+                                                       select localPlugin
+                            where matchingLocalPlugins.Count() > 0
+                            select remotePlugin;
+
+      // otherwise install a new plugin
+      var pluginsToInstall = remotePluginInstaller.CheckedPlugins.Except(pluginsToUpdate);
+
+      updateOrInstallInfo.ConnectionString = serverUrlTextBox.Text;
+      updateOrInstallInfo.PluginsToInstall = pluginsToInstall;
+      updateOrInstallInfo.PluginsToUpdate = pluginsToUpdate;
+      updateOrInstallPluginsBackgroundWorker.RunWorkerAsync(updateOrInstallInfo);
+    }
+
+    private void removeButton_Click(object sender, EventArgs e) {
+      Cursor = Cursors.AppStarting;
+      toolStripProgressBar.Visible = true;
+      DisableControls();
+      removePluginsBackgroundWorker.RunWorkerAsync(localPluginManager.CheckedPlugins);
+    }
+
+    #endregion
+
+    #region confirmation dialogs
+    private bool ConfirmRemoveAction(IEnumerable<string> fileNames) {
+      StringBuilder strBuilder = new StringBuilder();
+      strBuilder.AppendLine("Delete files:");
+      foreach (var fileName in fileNames) {
+        strBuilder.AppendLine(fileName);
+      }
+      return MessageBox.Show(strBuilder.ToString(), "Confirm Delete", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK;
+    }
+
+    private bool ConfirmUpdateAction(IEnumerable<string> plugins) {
+      StringBuilder strBuilder = new StringBuilder();
+      strBuilder.AppendLine("Update plugins:");
+      foreach (var plugin in plugins) {
+        strBuilder.AppendLine(plugin);
+      }
+      return MessageBox.Show(strBuilder.ToString(), "Confirm Update", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK;
+    }
+
+    #endregion
+
+    #region helper methods
+
+    private void UpdateLocalPluginList(IEnumerable<PluginDescription> plugins) {
+      localPluginManager.Plugins = plugins;
+    }
+
+    private void UpdateRemotePluginList(
+      IEnumerable<DeploymentService.ProductDescription> remoteProducts,
+      IEnumerable<IPluginDescription> remotePlugins) {
+
+      var mostRecentRemotePlugins = from remote in remotePlugins
+                                    where !remotePlugins.Any(x => x.Name == remote.Name && x.Version > remote.Version) // same name and higher version
+                                    select remote;
+
+      var newPlugins = from remote in mostRecentRemotePlugins
+                       let matchingLocal = (from local in localPluginManager.Plugins
+                                            where local.Name == remote.Name
+                                            where local.Version < remote.Version
+                                            select local).FirstOrDefault()
+                       where matchingLocal != null
+                       select remote;
+
+      remotePluginInstaller.NewPlugins = newPlugins;
+      remotePluginInstaller.Products = remoteProducts;
+      remotePluginInstaller.AllPlugins = remotePlugins;
+    }
+
+    private void RefreshRemotePluginListAsync() {
+      Cursor = Cursors.AppStarting;
+      toolStripProgressBar.Visible = true;
+      DisableControls();
+      refreshServerPluginsBackgroundWorker.RunWorkerAsync(serverUrlTextBox.Text);
+    }
+
+    private void RefreshLocalPluginListAsync() {
+      Cursor = Cursors.AppStarting;
+      toolStripProgressBar.Visible = true;
+      DisableControls();
+      refreshLocalPluginsBackgroundWorker.RunWorkerAsync();
+    }
+
+    private void UpdateControlsDisconnected() {
+      //localPluginsListView.Enabled = false;
+      //ClearPluginsList(remotePluginsListView);
+      refreshButton.Enabled = true;
+      serverUrlTextBox.Enabled = true;
+      toolStripProgressBar.Visible = false;
+      Cursor = Cursors.Default;
+    }
+
+    private void UpdateControlsConnected() {
+      refreshButton.Enabled = true;
+      serverUrlTextBox.Enabled = true;
+      toolStripProgressBar.Visible = false;
+      Cursor = Cursors.Default;
+    }
+
+    private void DisableControls() {
+      refreshButton.Enabled = false;
+      Cursor = Cursors.Default;
+    }
+    #endregion
+
+    private void localPluginManager_ItemChecked(object sender, ItemCheckedEventArgs e) {
+      removeButton.Enabled = localPluginManager.CheckedPlugins.Count() > 0;
+    }
+
+    private void remotePluginInstaller_ItemChecked(object sender, ItemCheckedEventArgs e) {
+      installButton.Enabled = remotePluginInstaller.CheckedPlugins.Count() > 0;
     }
   }
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.Designer.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.Designer.cs	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.Designer.cs	(revision 2922)
@@ -0,0 +1,101 @@
+﻿namespace HeuristicLab.PluginInfrastructure.Advanced {
+  partial class LocalPluginManager {
+    /// <summary> 
+    /// Required designer variable.
+    /// </summary>
+    private System.ComponentModel.IContainer components = null;
+
+    /// <summary> 
+    /// Clean up any resources being used.
+    /// </summary>
+    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+    protected override void Dispose(bool disposing) {
+      if (disposing && (components != null)) {
+        components.Dispose();
+      }
+      base.Dispose(disposing);
+    }
+
+    #region Component Designer generated code
+
+    /// <summary> 
+    /// Required method for Designer support - do not modify 
+    /// the contents of this method with the code editor.
+    /// </summary>
+    private void InitializeComponent() {
+      this.components = new System.ComponentModel.Container();
+      System.Windows.Forms.ListViewGroup listViewGroup1 = new System.Windows.Forms.ListViewGroup("Active Plugins", System.Windows.Forms.HorizontalAlignment.Left);
+      System.Windows.Forms.ListViewGroup listViewGroup2 = new System.Windows.Forms.ListViewGroup("Disabled Plugins", System.Windows.Forms.HorizontalAlignment.Left);
+      this.localPluginsListView = new System.Windows.Forms.ListView();
+      this.pluginNameHeader = new System.Windows.Forms.ColumnHeader();
+      this.versionHeader = new System.Windows.Forms.ColumnHeader();
+      this.pluginDescriptionHeader = new System.Windows.Forms.ColumnHeader();
+      this.imageListForLocalItems = new System.Windows.Forms.ImageList(this.components);
+      this.SuspendLayout();
+      // 
+      // localPluginsListView
+      // 
+      this.localPluginsListView.CheckBoxes = true;
+      this.localPluginsListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.pluginNameHeader,
+            this.versionHeader,
+            this.pluginDescriptionHeader});
+      this.localPluginsListView.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.localPluginsListView.FullRowSelect = true;
+      listViewGroup1.Header = "Active Plugins";
+      listViewGroup1.Name = "activePluginsGroup";
+      listViewGroup2.Header = "Disabled Plugins";
+      listViewGroup2.Name = "disabledPluginsGroup";
+      this.localPluginsListView.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {
+            listViewGroup1,
+            listViewGroup2});
+      this.localPluginsListView.Location = new System.Drawing.Point(0, 0);
+      this.localPluginsListView.Name = "localPluginsListView";
+      this.localPluginsListView.Size = new System.Drawing.Size(570, 547);
+      this.localPluginsListView.StateImageList = this.imageListForLocalItems;
+      this.localPluginsListView.TabIndex = 8;
+      this.localPluginsListView.UseCompatibleStateImageBehavior = false;
+      this.localPluginsListView.View = System.Windows.Forms.View.Details;
+      this.localPluginsListView.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.pluginsListView_ItemChecked);
+      // 
+      // pluginNameHeader
+      // 
+      this.pluginNameHeader.Text = "Name";
+      this.pluginNameHeader.Width = 220;
+      // 
+      // versionHeader
+      // 
+      this.versionHeader.Text = "Version";
+      this.versionHeader.Width = 120;
+      // 
+      // pluginDescriptionHeader
+      // 
+      this.pluginDescriptionHeader.Text = "Description";
+      this.pluginDescriptionHeader.Width = 243;
+      // 
+      // imageListForLocalItems
+      // 
+      this.imageListForLocalItems.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
+      this.imageListForLocalItems.ImageSize = new System.Drawing.Size(13, 13);
+      this.imageListForLocalItems.TransparentColor = System.Drawing.Color.Transparent;
+      // 
+      // LocalPluginManager
+      // 
+      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+      this.Controls.Add(this.localPluginsListView);
+      this.Name = "LocalPluginManager";
+      this.Size = new System.Drawing.Size(570, 547);
+      this.ResumeLayout(false);
+
+    }
+
+    #endregion
+
+    private System.Windows.Forms.ListView localPluginsListView;
+    private System.Windows.Forms.ColumnHeader pluginNameHeader;
+    private System.Windows.Forms.ColumnHeader versionHeader;
+    private System.Windows.Forms.ColumnHeader pluginDescriptionHeader;
+    private System.Windows.Forms.ImageList imageListForLocalItems;
+  }
+}
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.cs	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.cs	(revision 2922)
@@ -0,0 +1,95 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using HeuristicLab.PluginInfrastructure.Manager;
+
+namespace HeuristicLab.PluginInfrastructure.Advanced {
+  public partial class LocalPluginManager : UserControl {
+
+    public event ItemCheckedEventHandler ItemChecked;
+
+    private BackgroundWorker refreshPluginListBackgroundWorker = new BackgroundWorker();
+
+    private ListViewGroup enabledPluginsGroup;
+    private ListViewGroup disabledPluginsGroup;
+
+    public LocalPluginManager() {
+      InitializeComponent();
+
+      imageListForLocalItems.Images.Add(HeuristicLab.PluginInfrastructure.Resources.Resources.Assembly);
+      imageListForLocalItems.Images.Add(HeuristicLab.PluginInfrastructure.Resources.Resources.Remove);
+
+      enabledPluginsGroup = localPluginsListView.Groups["activePluginsGroup"];
+      disabledPluginsGroup = localPluginsListView.Groups["disabledPluginsGroup"];
+    }
+
+    private IEnumerable<PluginDescription> plugins;
+    public IEnumerable<PluginDescription> Plugins {
+      get { return plugins; }
+      set {
+        if (value != plugins) {
+          this.plugins = value;
+          UpdateControl();
+        }
+      }
+    }
+
+    public IEnumerable<IPluginDescription> CheckedPlugins {
+      get {
+        return (from item in localPluginsListView.Items.OfType<ListViewItem>()
+                where item.Checked
+                let plugin = item.Tag as IPluginDescription
+                where plugin != null
+                select plugin).ToList();
+      }
+    }
+
+    private void UpdateControl() {
+      ClearPluginList();
+      foreach (var plugin in plugins) {
+        var item = CreateListViewItem(plugin);
+        if (plugin.PluginState == PluginState.Enabled) {
+          item.Group = enabledPluginsGroup;
+        } else if (plugin.PluginState == PluginState.Disabled) {
+          item.Group = disabledPluginsGroup;
+        }
+        localPluginsListView.Items.Add(item);
+      }
+    }
+
+    private void ClearPluginList() {
+      List<ListViewItem> itemsToRemove = new List<ListViewItem>(from item in localPluginsListView.Items.OfType<ListViewItem>()
+                                                                select item);
+      itemsToRemove.ForEach(item => localPluginsListView.Items.Remove(item));
+    }
+
+    private ListViewItem CreateListViewItem(PluginDescription plugin) {
+      ListViewItem item = new ListViewItem(new string[] { plugin.Name, plugin.Version.ToString(), plugin.Description });
+      item.Tag = plugin;
+      return item;
+    }
+
+    private void pluginsListView_ItemChecked(object sender, ItemCheckedEventArgs e) {
+      // checked items are marked for removal
+      if (e.Item.Checked) {
+        var plugin = (IPluginDescription)e.Item.Tag;
+        foreach (ListViewItem item in localPluginsListView.Items) {
+          var dep = (IPluginDescription)item.Tag;
+          if (!item.Checked && dep.Dependencies.Contains(plugin)) {
+            item.Checked = true;
+          }
+        }
+      }
+      OnItemChecked(e);
+    }
+
+    private void OnItemChecked(ItemCheckedEventArgs e) {
+      if (ItemChecked != null) ItemChecked(this, e);
+    }
+  }
+}
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.resx
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.resx	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/LocalPluginManager.resx	(revision 2922)
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="imageListForLocalItems.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.Designer.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.Designer.cs	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.Designer.cs	(revision 2922)
@@ -0,0 +1,105 @@
+﻿namespace HeuristicLab.PluginInfrastructure.Advanced {
+  partial class RemotePluginInstaller {
+    /// <summary> 
+    /// Required designer variable.
+    /// </summary>
+    private System.ComponentModel.IContainer components = null;
+
+    /// <summary> 
+    /// Clean up any resources being used.
+    /// </summary>
+    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
+    protected override void Dispose(bool disposing) {
+      if (disposing && (components != null)) {
+        components.Dispose();
+      }
+      base.Dispose(disposing);
+    }
+
+    #region Component Designer generated code
+
+    /// <summary> 
+    /// Required method for Designer support - do not modify 
+    /// the contents of this method with the code editor.
+    /// </summary>
+    private void InitializeComponent() {
+      this.components = new System.ComponentModel.Container();
+      System.Windows.Forms.ListViewGroup listViewGroup1 = new System.Windows.Forms.ListViewGroup("Available Products", System.Windows.Forms.HorizontalAlignment.Left);
+      System.Windows.Forms.ListViewGroup listViewGroup2 = new System.Windows.Forms.ListViewGroup("Available Plugins (Most Recent Version)", System.Windows.Forms.HorizontalAlignment.Left);
+      System.Windows.Forms.ListViewGroup listViewGroup3 = new System.Windows.Forms.ListViewGroup("Available Plugins (All Versions)", System.Windows.Forms.HorizontalAlignment.Left);
+      this.remotePluginsListView = new System.Windows.Forms.ListView();
+      this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
+      this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
+      this.columnHeader3 = new System.Windows.Forms.ColumnHeader();
+      this.imageListForRemoteItems = new System.Windows.Forms.ImageList(this.components);
+      this.SuspendLayout();
+      // 
+      // remotePluginsListView
+      // 
+      this.remotePluginsListView.CheckBoxes = true;
+      this.remotePluginsListView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+            this.columnHeader1,
+            this.columnHeader2,
+            this.columnHeader3});
+      this.remotePluginsListView.Dock = System.Windows.Forms.DockStyle.Fill;
+      this.remotePluginsListView.FullRowSelect = true;
+      listViewGroup1.Header = "Available Products";
+      listViewGroup1.Name = "productsGroup";
+      listViewGroup2.Header = "Available Plugins (Most Recent Version)";
+      listViewGroup2.Name = "newPluginsGroup";
+      listViewGroup3.Header = "Available Plugins (All Versions)";
+      listViewGroup3.Name = "allPluginsGroup";
+      this.remotePluginsListView.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {
+            listViewGroup1,
+            listViewGroup2,
+            listViewGroup3});
+      this.remotePluginsListView.Location = new System.Drawing.Point(0, 0);
+      this.remotePluginsListView.Name = "remotePluginsListView";
+      this.remotePluginsListView.Size = new System.Drawing.Size(533, 558);
+      this.remotePluginsListView.StateImageList = this.imageListForRemoteItems;
+      this.remotePluginsListView.TabIndex = 10;
+      this.remotePluginsListView.UseCompatibleStateImageBehavior = false;
+      this.remotePluginsListView.View = System.Windows.Forms.View.Details;
+      this.remotePluginsListView.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.remotePluginsListView_ItemChecked);
+      // 
+      // columnHeader1
+      // 
+      this.columnHeader1.Text = "Name";
+      this.columnHeader1.Width = 220;
+      // 
+      // columnHeader2
+      // 
+      this.columnHeader2.Text = "Version";
+      this.columnHeader2.Width = 120;
+      // 
+      // columnHeader3
+      // 
+      this.columnHeader3.Text = "Description";
+      this.columnHeader3.Width = 240;
+      // 
+      // imageListForRemoteItems
+      // 
+      this.imageListForRemoteItems.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
+      this.imageListForRemoteItems.ImageSize = new System.Drawing.Size(13, 13);
+      this.imageListForRemoteItems.TransparentColor = System.Drawing.Color.Transparent;
+      // 
+      // RemotePluginInstaller
+      // 
+      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+      this.Controls.Add(this.remotePluginsListView);
+      this.Name = "RemotePluginInstaller";
+      this.Size = new System.Drawing.Size(533, 558);
+      this.ResumeLayout(false);
+
+    }
+
+    #endregion
+
+    private System.Windows.Forms.ListView remotePluginsListView;
+    private System.Windows.Forms.ColumnHeader columnHeader1;
+    private System.Windows.Forms.ColumnHeader columnHeader2;
+    private System.Windows.Forms.ColumnHeader columnHeader3;
+    private System.Windows.Forms.ImageList imageListForRemoteItems;
+  }
+}
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.cs
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.cs	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.cs	(revision 2922)
@@ -0,0 +1,205 @@
+﻿using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Data;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+
+namespace HeuristicLab.PluginInfrastructure.Advanced {
+  public partial class RemotePluginInstaller : UserControl {
+    private ListViewGroup newPluginsGroup;
+    private ListViewGroup productsGroup;
+    private ListViewGroup allPluginsGroup;
+
+    public event ItemCheckedEventHandler ItemChecked;
+
+    public RemotePluginInstaller() {
+      InitializeComponent();
+
+      imageListForRemoteItems.Images.Add(HeuristicLab.PluginInfrastructure.Resources.Resources.Assembly);
+      imageListForRemoteItems.Images.Add(HeuristicLab.PluginInfrastructure.Resources.Resources.Install);
+
+      newPluginsGroup = remotePluginsListView.Groups["newPluginsGroup"];
+      productsGroup = remotePluginsListView.Groups["productsGroup"];
+      allPluginsGroup = remotePluginsListView.Groups["allPluginsGroup"];
+    }
+
+    private IEnumerable<DeploymentService.ProductDescription> products;
+    public IEnumerable<DeploymentService.ProductDescription> Products {
+      get { return products ?? Enumerable.Empty<DeploymentService.ProductDescription>(); }
+      set {
+        if (value != this.products) {
+          this.products = value;
+          UpdateControl();
+        }
+      }
+    }
+
+    private IEnumerable<IPluginDescription> plugins;
+    public IEnumerable<IPluginDescription> AllPlugins {
+      get { return plugins ?? Enumerable.Empty<IPluginDescription>(); }
+      set {
+        if (value != this.plugins) {
+          this.plugins = value;
+          UpdateControl();
+        }
+      }
+    }
+
+    private IEnumerable<IPluginDescription> newPlugins;
+    public IEnumerable<IPluginDescription> NewPlugins {
+      get { return newPlugins ?? Enumerable.Empty<IPluginDescription>(); }
+      set {
+        if (value != this.newPlugins) {
+          this.newPlugins = value;
+          UpdateControl();
+        }
+      }
+    }
+
+    public IEnumerable<IPluginDescription> CheckedPlugins {
+      get {
+        return (from item in remotePluginsListView.Items.OfType<ListViewItem>()
+                where item.Checked
+                let plugin = item.Tag as IPluginDescription
+                where plugin != null
+                select plugin).ToList();
+      }
+    }
+
+    private void UpdateControl() {
+      ClearListView();
+
+      foreach (var newPlugin in NewPlugins) {
+        var item = CreateListViewItem(newPlugin);
+        item.Group = newPluginsGroup;
+        remotePluginsListView.Items.Add(item);
+      }
+
+      foreach (var product in Products) {
+        var item = CreateListViewItem(product);
+        item.Group = productsGroup;
+        remotePluginsListView.Items.Add(item);
+      }
+
+      foreach (var plugin in AllPlugins) {
+        var item = CreateListViewItem(plugin);
+        item.Group = allPluginsGroup;
+        remotePluginsListView.Items.Add(item);
+      }
+    }
+
+    private void ClearListView() {
+      List<ListViewItem> itemsToDelete = new List<ListViewItem>(remotePluginsListView.Items.OfType<ListViewItem>());
+      itemsToDelete.ForEach(item => remotePluginsListView.Items.Remove(item));
+    }
+
+    private ListViewItem CreateListViewItem(DeploymentService.ProductDescription product) {
+      ListViewItem item = new ListViewItem(new string[] { product.Name, product.Version.ToString(), string.Empty });
+      item.Tag = product;
+      return item;
+    }
+
+    private ListViewItem CreateListViewItem(IPluginDescription plugin) {
+      ListViewItem item = new ListViewItem(new string[] { plugin.Name, plugin.Version.ToString(), plugin.Description });
+      item.Tag = plugin;
+      return item;
+    }
+
+    #region item checked event handler
+    private void remotePluginsListView_ItemChecked(object sender, ItemCheckedEventArgs e) {
+      // dispatch by check state and type of item (product/plugin)
+      IPluginDescription plugin = e.Item.Tag as IPluginDescription;
+      if (plugin != null)
+        if (e.Item.Checked)
+          HandlePluginChecked(plugin);
+        else
+          HandlePluginUnchecked(plugin);
+      else {
+        DeploymentService.ProductDescription product = e.Item.Tag as DeploymentService.ProductDescription;
+        if (product != null)
+          if (e.Item.Checked)
+            HandleProductChecked(product);
+          else
+            HandleProductUnchecked(product);
+      }
+      OnItemChecked(e);
+    }
+
+    private void HandleProductUnchecked(HeuristicLab.PluginInfrastructure.Advanced.DeploymentService.ProductDescription product) {
+      // also uncheck the plugins of the product
+      foreach (var plugin in product.Plugins) {
+        var item = FindItemForPlugin(plugin);
+        if (item != null && item.Checked)
+          item.Checked = false;
+      }
+    }
+
+    private void HandleProductChecked(HeuristicLab.PluginInfrastructure.Advanced.DeploymentService.ProductDescription product) {
+      // also check all plugins of the product
+      foreach (var plugin in product.Plugins) {
+        var item = FindItemForPlugin(plugin);
+        if (item != null && !item.Checked) {
+          item.Checked = true;
+        }
+      }
+    }
+
+    private void HandlePluginUnchecked(IPluginDescription plugin) {
+      // also uncheck all dependent plugins
+      var dependentPlugins = from otherPlugin in plugins
+                             where otherPlugin.Dependencies.Any(dep => dep.Name == plugin.Name && dep.Version == plugin.Version)
+                             select otherPlugin;
+      foreach (var dependentPlugin in dependentPlugins) {
+        var item = FindItemForPlugin(dependentPlugin);
+        if (item != null && item.Checked) {
+          item.Checked = false;
+        }
+      }
+      // also uncheck all products containing this plugin
+      var dependentProducts = from product in products
+                              where product.Plugins.Any(p => p.Name == plugin.Name && p.Version == plugin.Version)
+                              select product;
+      foreach (var dependentProduct in dependentProducts) {
+        var item = FindItemForProduct(dependentProduct);
+        if (item != null && item.Checked) {
+          item.Checked = false;
+        }
+      }
+    }
+
+    private void HandlePluginChecked(IPluginDescription plugin) {
+      // also check all dependencies
+      foreach (var dep in plugin.Dependencies) {
+        var item = FindItemForPlugin(dep);
+        if (item != null && !item.Checked) {
+          item.Checked = true;
+        }
+      }
+    }
+
+    private void OnItemChecked(ItemCheckedEventArgs e) {
+      if (ItemChecked != null) ItemChecked(this, e);
+    }
+    #endregion
+
+    #region helper methods
+    private ListViewItem FindItemForPlugin(IPluginDescription plugin) {
+      return (from item in remotePluginsListView.Items.OfType<ListViewItem>()
+              let otherPlugin = item.Tag as IPluginDescription
+              where otherPlugin != null && otherPlugin.Name == plugin.Name && otherPlugin.Version == plugin.Version
+              select item).SingleOrDefault();
+    }
+
+    private ListViewItem FindItemForProduct(DeploymentService.ProductDescription product) {
+      return (from item in remotePluginsListView.Items.OfType<ListViewItem>()
+              let otherProduct = item.Tag as DeploymentService.ProductDescription
+              where otherProduct != null && otherProduct.Name == product.Name && otherProduct.Version == product.Version
+              select item).SingleOrDefault();
+    }
+
+    #endregion
+  }
+}
Index: trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.resx
===================================================================
--- trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.resx	(revision 2922)
+++ trunk/sources/HeuristicLab.PluginInfrastructure/Advanced/RemotePluginInstaller.resx	(revision 2922)
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <metadata name="imageListForRemoteItems.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
+    <value>17, 17</value>
+  </metadata>
+</root>
