/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; using System35; using JetBrains.Annotations; using JetBrains.Build.InstallationData; using Microsoft.Win32; namespace JetBrains.Omea.Base.Install { /// /// Handles the Registry entries by writing them into the Windows Registry. /// public class LocalInstaller { #region Data /// /// Caches the macro substitution regex. /// private static Regex myRegexMacro; #endregion #region Operations /// /// Performs the local installation of the given installation data by writing the Registry keys and copying the files. /// /// The installation data. /// Stage, either install or uninstall. /// The logging facility. /// Resolves the source directory, for copying the files from. /// Resolves the target directory, for copying the files into. /// The maros to be substituted on install, if needed. public static void Install(InstallationDataXml dataxml, RegistrationStage stage, IDictionary macros, Func ResolveSourceDirRoot, Func ResolveTargetDirRoot, Action LogMessage) { InstallRegistry(dataxml.Registry, stage, macros); InstallFiles(dataxml, stage, LogMessage, ResolveSourceDirRoot, ResolveTargetDirRoot); } /// /// Substs the macros in a string, throws if there are undefined macros. /// [NotNull] public static string SubstituteMacros([NotNull] IDictionary macros, [NotNull] string sample) { if(macros == null) throw new ArgumentNullException("macros"); if(sample == null) throw new ArgumentNullException("sample"); string retval = sample; foreach(var pair in macros) retval = retval.Replace(string.Format("$({0})", pair.Key), pair.Value); // Are there any undefined macros? if(myRegexMacro == null) myRegexMacro = new Regex(@"\$\((?[^)]*)\)", RegexOptions.Compiled); MatchCollection matches = myRegexMacro.Matches(retval); if(matches.Count != 0) { var sw = new StringWriter(); sw.Write("Error: undefined macros "); foreach(Match match in matches) sw.Write("“{0}” ", match.Groups["MacroName"].Value); sw.Write("in “{0}”.", sample); throw new InvalidOperationException(sw.ToString()); } return retval; } #endregion #region Implementation /// /// Gets the proper Windows Registry root key. /// private static RegistryKey GetWindowsRegistryRootKey(RegistryHiveXml hive) { switch(hive) { case RegistryHiveXml.Hkcr: return Registry.ClassesRoot; case RegistryHiveXml.Hklm: return Registry.LocalMachine; case RegistryHiveXml.Hkcu: return Registry.CurrentUser; case RegistryHiveXml.Hkmu: return Registry.LocalMachine; // TODO: differentiate per-machine and per-user, if ever needed default: throw new ArgumentOutOfRangeException("hive", hive, "Unexpected Registry hive."); } } /// /// Copies or deletes the files. /// private static void InstallFiles(InstallationDataXml dataxml, RegistrationStage stage, Action LogMessage, Func ResolveSourceDirRoot, Func ResolveTargetDirRoot) { dataxml.AssertValid(); foreach(FolderXml folderxml in dataxml.Files) { var diSource = new DirectoryInfo(Path.Combine(ResolveSourceDirRoot(folderxml.SourceRoot).FullName, folderxml.SourceDir)); var diTarget = new DirectoryInfo(Path.Combine(ResolveTargetDirRoot(folderxml.TargetRoot).FullName, folderxml.TargetDir)); diTarget.Create(); foreach(FileXml filexml in folderxml.Files) { FileInfo[] files = diSource.GetFiles(filexml.SourceName); if(files.Length == 0) throw new InvalidOperationException(string.Format("There are no files matching the “{0}” mask in the source folder “{1}”.", filexml.SourceName, diSource.FullName)); if((files.Length > 1) && (filexml.TargetName.Length > 0)) throw new InvalidOperationException(string.Format("There are {2} files matching the “{0}” mask in the source folder “{1}”, in which case it's illegal to specify a target name for the file.", filexml.SourceName, diSource.FullName, files.Length)); foreach(FileInfo fiSource in files) { var fiTarget = new FileInfo(Path.Combine(diTarget.FullName, (filexml.TargetName.Length > 0 ? filexml.TargetName : fiSource.Name))); // Explicit target name, if present and if a single file; otherwise, use from source // Don't copy and, especially, don't delete the inplace files if(fiSource.FullName == fiTarget.FullName) { LogMessage(string.Format("Skipping “{0}” because source and target are the same file.", fiSource.FullName, fiTarget.FullName)); continue; } switch(stage) { case RegistrationStage.Register: LogMessage(string.Format("Installing “{0}” -> “{1}”.", fiSource.FullName, fiTarget.FullName)); fiSource.CopyTo(fiTarget.FullName, true); break; case RegistrationStage.Unregister: LogMessage(string.Format("Uninstalling “{0}”.", fiTarget.FullName)); fiTarget.Delete(); break; default: throw new InvalidOperationException(string.Format("Unexpected stage {0}.", stage)); } } } } } /// /// Executes the registration/unregistration operations on the Registry keys. /// /// The Registry to process. /// Processing type. /// The macros to substitute when processing the keys. private static void InstallRegistry(RegistryXml registry, RegistrationStage registrationStage, IDictionary macros) { if(registry == null) throw new ArgumentNullException("registry"); if(macros == null) throw new ArgumentNullException("macros"); registry.AssertValid(); switch(registrationStage) { case RegistrationStage.Register: foreach(RegistryKeyXml key in registry.Key) RegisterKey(key, macros); foreach(RegistryValueXml value in registry.Value) RegisterValue(value, macros); break; case RegistrationStage.Unregister: for(int a = registry.Value.Length - 1; a >= 0; a--) // Reverse order on uninstallation UnregisterValue(registry.Value[a], macros); for(int a = registry.Key.Length - 1; a >= 0; a--) UnregisterKey(registry.Key[a], macros); break; default: throw new ArgumentOutOfRangeException("registrationStage", registrationStage, "Unexpected registration stage."); } } /// /// Writes the key to the Registry. /// private static void RegisterKey([NotNull] RegistryKeyXml key, [NotNull] IDictionary macros) { if(key == null) throw new ArgumentNullException("key"); if(macros == null) throw new ArgumentNullException("macros"); try { string sPath = SubstituteMacros(macros, key.Key); RegistryKey regkeyRoot = GetWindowsRegistryRootKey(key.Hive); using(regkeyRoot.CreateSubKey(sPath)) { } } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to process the key {0}. {1}", key, ex.Message), ex); } } /// /// Writes the value to the Registry. /// private static void RegisterValue([NotNull] RegistryValueXml value, [NotNull] IDictionary macros) { if(value == null) throw new ArgumentNullException("value"); if(macros == null) throw new ArgumentNullException("macros"); try { string sPath = SubstituteMacros(macros, value.Key); RegistryKey regkeyRoot = GetWindowsRegistryRootKey(value.Hive); RegistryKey regkey = regkeyRoot.OpenSubKey(sPath, true); if(regkey == null) regkey = regkeyRoot.CreateSubKey(sPath); // TODO: check if this works when more than one level is missing using(regkey) { string sName = SubstituteMacros(macros, value.Name); object oValue; switch(value.Type) { case RegistryValueTypeXml.Dword: oValue = unchecked((int)(uint)long.Parse(value.Value)); // Ensure it qualifies as an Int32 for the DWORD registry value break; case RegistryValueTypeXml.String: oValue = SubstituteMacros(macros, value.Value); break; default: throw new InvalidOperationException(string.Format("The registry value type “{0}” is unknown.", value.Type)); } regkey.SetValue(sName, oValue); } } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to process the value {0}. {1}", value, ex.Message), ex); } } /// /// Deletes the key from the Registry. /// private static void UnregisterKey([NotNull] RegistryKeyXml key, [NotNull] IDictionary macros) { if(key == null) throw new ArgumentNullException("key"); if(macros == null) throw new ArgumentNullException("macros"); try { string sKeyPath = SubstituteMacros(macros, key.Key); RegistryKey regkeyRoot = GetWindowsRegistryRootKey(key.Hive); using(RegistryKey regkey = regkeyRoot.OpenSubKey(sKeyPath, false)) { if(regkey == null) return; // No key, nothing to delete } regkeyRoot.DeleteSubKeyTree(sKeyPath); // Must do a check because this function will throw if there is no key } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to process the key {0}. {1}", key, ex.Message), ex); } } /// /// Deletes the value from the Registry. /// private static void UnregisterValue([NotNull] RegistryValueXml value, [NotNull] IDictionary macros) { if(value == null) throw new ArgumentNullException("value"); if(macros == null) throw new ArgumentNullException("macros"); try { string sKeyPath = SubstituteMacros(macros, value.Key); RegistryKey regkeyRoot = GetWindowsRegistryRootKey(value.Hive); using(RegistryKey regkey = regkeyRoot.OpenSubKey(sKeyPath, true)) { if(regkey == null) return; // No key, no value to delete string sName = SubstituteMacros(macros, value.Name); regkey.DeleteValue(sName, false); } } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to process the value {0}. {1}", value, ex.Message), ex); } } #endregion } }