using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; using System.Diagnostics; using System.Runtime.InteropServices; using Microsoft.Win32; using System.IO; using System.Threading; namespace Disco.ClientBootstrapper.Interop { public static class InstallInterop { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, MoveFileFlags dwFlags); [Flags] enum MoveFileFlags { MOVEFILE_REPLACE_EXISTING = 0x00000001, MOVEFILE_COPY_ALLOWED = 0x00000002, MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004, MOVEFILE_WRITE_THROUGH = 0x00000008, MOVEFILE_CREATE_HARDLINK = 0x00000010, MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020 } private static void Install(string RootFilesystemLocation, RegistryKey RootRegistryLocation, string FilesystemInstallLocation, string VirtualRootFilesystemLocation) { var SourceLocation = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); var InstallLocation = Path.Combine(RootFilesystemLocation, FilesystemInstallLocation); var BootstrapperCmdLinePath = Path.Combine(VirtualRootFilesystemLocation, FilesystemInstallLocation, "Disco.ClientBootstrapper.exe"); var GroupPolicyScriptsIniLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\scripts.ini"); var GroupPolicyScriptsIniBackupLocation = Path.Combine(RootFilesystemLocation, "Windows\\System32\\GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); // Create file system Location #region "Create File System Location" Program.Status.UpdateStatus(null, null, "Creating Installation Location"); Program.SleepThread(500, false); if (Directory.Exists(InstallLocation)) { // Try and Delete Directory try { Directory.Delete(InstallLocation, true); } catch (Exception ex) { throw new IOException(string.Format("Unable to delete folder: ", InstallLocation), ex); } } if (!Directory.Exists(InstallLocation)) { var installDir = Directory.CreateDirectory(InstallLocation); installDir.Attributes = installDir.Attributes | FileAttributes.Hidden; } #endregion // Copy files to file system location #region "Copy to File System" Program.Status.UpdateStatus(null, null, "Copying Files"); Program.SleepThread(500, false); // Copy Bootstrapper // ie: Executing Assembly File.Copy(System.Reflection.Assembly.GetExecutingAssembly().Location, Path.Combine(InstallLocation, "Disco.ClientBootstrapper.exe")); foreach (var file in Directory.EnumerateFiles(SourceLocation)) { var fileName = Path.GetFileName(file); // Only Copy Certain Files // Copy Wireless Certificates if (fileName.StartsWith("WLAN_Cert_Root_", StringComparison.InvariantCultureIgnoreCase) || fileName.StartsWith("WLAN_Cert_Intermediate_", StringComparison.InvariantCultureIgnoreCase) || fileName.StartsWith("WLAN_Cert_Personal_", StringComparison.InvariantCultureIgnoreCase)) File.Copy(file, Path.Combine(InstallLocation, fileName)); // Copy Wireless Profiles if (fileName.StartsWith("WLAN_Profile_", StringComparison.InvariantCultureIgnoreCase) && fileName.EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) File.Copy(file, Path.Combine(InstallLocation, fileName)); } #endregion // Backup & Create Group Policy Scripts.ini #region "Group Policy Scripts.ini" Program.Status.UpdateStatus(null, null, "Creating Group Policy Script Entry"); Program.SleepThread(500, false); // Backup if (!File.Exists(GroupPolicyScriptsIniBackupLocation)) { if (File.Exists(GroupPolicyScriptsIniLocation)) { File.Move(GroupPolicyScriptsIniLocation, GroupPolicyScriptsIniBackupLocation); } } // Create if (File.Exists(GroupPolicyScriptsIniLocation)) File.Delete(GroupPolicyScriptsIniLocation); if (!Directory.Exists(Path.GetDirectoryName(GroupPolicyScriptsIniLocation))) Directory.CreateDirectory(Path.GetDirectoryName(GroupPolicyScriptsIniLocation)); using (var scriptsIniStream = File.Open(GroupPolicyScriptsIniLocation, FileMode.Create, FileAccess.Write)) { using (var scriptsIniStreamWriter = new StreamWriter(scriptsIniStream, Encoding.Unicode)) { scriptsIniStreamWriter.Write(string.Format("[Startup]{0}0CmdLine={1}{0}0Parameters=/AllowUninstall", Environment.NewLine, BootstrapperCmdLinePath)); scriptsIniStreamWriter.Flush(); } } #endregion // Backup & Create Group Policy Registry #region "Group Policy Registry" Program.Status.UpdateStatus(null, null, "Creating Group Policy Registry Entries"); Program.SleepThread(500, false); // Backup Scripts using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) { if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) { RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); } } // Create Scripts RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown").Dispose(); using (var regScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup\\0")) { regScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine"), RegistryValueKind.String); regScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String); regScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord); using (var regScriptsStartup0 = regScriptsStartup.CreateSubKey("0")) { regScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); regScriptsStartup0.SetValue("IsPowershell", 0, RegistryValueKind.DWord); regScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); } } RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown").Dispose(); // Backup Scripts State using (var regGroupPolicy = RootRegistryLocation.OpenSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) { if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Scripts") && !regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) { RegistryUtilities.RenameSubKey(regGroupPolicy, "Scripts", "Disco_Scripts"); } } // Create Scripts State using (var regStateScriptsStartup = RootRegistryLocation.CreateSubKey("Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup\\0")) { regStateScriptsStartup.SetValue("GPO-ID", "LocalGPO", RegistryValueKind.String); regStateScriptsStartup.SetValue("SOM-ID", "Local", RegistryValueKind.String); regStateScriptsStartup.SetValue("FileSysPath", Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine"), RegistryValueKind.String); regStateScriptsStartup.SetValue("DisplayName", "Local Group Policy", RegistryValueKind.String); regStateScriptsStartup.SetValue("GPOName", "Local Group Policy", RegistryValueKind.String); regStateScriptsStartup.SetValue("PSScriptOrder", 1, RegistryValueKind.DWord); using (var regStateScriptsStartup0 = regStateScriptsStartup.CreateSubKey("0")) { regStateScriptsStartup0.SetValue("Script", BootstrapperCmdLinePath, RegistryValueKind.String); regStateScriptsStartup0.SetValue("Parameters", "/AllowUninstall", RegistryValueKind.String); regStateScriptsStartup0.SetValue("ExecTime", new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, RegistryValueKind.Binary); } } #endregion // Set Registry Startup Environment Policies #region "Registry Startup Policies" Program.Status.UpdateStatus(null, null, "Creating Startup Policy Registry Entries"); Program.SleepThread(500, false); using (var regWinlogon = RootRegistryLocation.OpenSubKey("Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) { regWinlogon.SetValue("HideStartupScripts", 0, RegistryValueKind.DWord); regWinlogon.SetValue("RunStartupScriptSync", 1, RegistryValueKind.DWord); } #endregion } public static void Install(string InstallLocation, string WimImageId = null) { Program.Status.UpdateStatus("Installing Bootstrapper", "Starting", "Please wait...", false); if (string.IsNullOrWhiteSpace(InstallLocation)) InstallLocation = Path.Combine(Path.GetPathRoot(Environment.SystemDirectory), "Disco"); if (InstallLocation.EndsWith(".wim", StringComparison.InvariantCultureIgnoreCase)) { // Offline File System (WIM) Program.Status.UpdateStatus("Installing Bootstrapper (Offline)", "Installing", string.Format("Install Location: {0}", InstallLocation)); Program.SleepThread(1000, false); // Mount WIM int wimImageIndex = 0; using (var wim = new Interop.WIMInterop.WindowsImageContainer(InstallLocation, WIMInterop.WindowsImageContainer.CreateFileMode.OpenExisting, WIMInterop.WindowsImageContainer.CreateFileAccess.Write)) { if (WimImageId == null) WimImageId = "1"; if (!int.TryParse(WimImageId, out wimImageIndex)) { Program.Status.UpdateStatus(null, "Analysing WIM", string.Format("Looking for Image Name: {0}", WimImageId)); Program.SleepThread(500, false); for (int i = 0; i < wim.ImageCount; i++) { var wimImageInfo = new System.Xml.XmlDocument(); using (var wimImage = wim[i]) wimImageInfo.LoadXml(wimImage.ImageInformation); var wimImageInfoName = wimImageInfo.SelectSingleNode("//IMAGE/NAME"); if (wimImageInfoName != null && wimImageInfoName.InnerText.Equals(WimImageId, StringComparison.InvariantCultureIgnoreCase)) { wimImageIndex = i + 1; Program.Status.UpdateStatus(null, "Analysing WIM", string.Format("Found Image Id '{0}' at Index {1}", WimImageId, wimImageIndex)); Program.SleepThread(500, false); break; } } } } if (wimImageIndex == 0) { Program.Status.UpdateStatus(null, "Error", string.Format("Unable to load WIM Image Id: {0}", WimImageId)); Program.SleepThread(5000, false); return; } // Get Temp Path var wimMountPath = Path.Combine(Path.GetTempPath(), "DiscoClientBootstrapperWimMount"); if (Directory.Exists(wimMountPath)) Directory.Delete(wimMountPath, true); Directory.CreateDirectory(wimMountPath); var wimTempMountPath = Path.Combine(Path.GetTempPath(), "DiscoClientBootstrapperWimTempMount"); if (Directory.Exists(wimTempMountPath)) Directory.Delete(wimTempMountPath, true); Directory.CreateDirectory(wimTempMountPath); bool wimCommitChanges = true; Interop.WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback m_MessageCallback = null; try { // Mount WIM Program.Status.UpdateStatus(null, "Mounting WIM", string.Format("Mounting WIM Image to '{0}'", wimMountPath)); Program.SleepThread(500, false); m_MessageCallback = new Interop.WIMInterop.WindowsImageContainer.NativeMethods.MessageCallback(WimImageEventMessagePump); Interop.WIMInterop.WindowsImageContainer.NativeMethods.RegisterCallback(m_MessageCallback); Interop.WIMInterop.WindowsImageContainer.NativeMethods.MountImage(wimMountPath, InstallLocation, wimImageIndex, wimTempMountPath); // Load Local Machine Registry var wimHivePath = Path.Combine(wimMountPath, "Windows\\System32\\config\\SOFTWARE"); Program.Status.UpdateStatus(null, "Mounting Offline Registry Hive", string.Format("Mounting Offline Registry Hive at '{0}'", wimHivePath)); Program.SleepThread(500, false); using (var wimReg = new Interop.RegistryInterop(RegistryInterop.RegistryHives.HKEY_LOCAL_MACHINE, "DiscoClientBootstrapperWimHive", wimHivePath)) { using (RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("DiscoClientBootstrapperWimHive", true)) { string rootFileSystemLocation = wimMountPath; string fileSystemInstallLocation = "Disco"; string virtualRootFileSystemLocation = "C:\\"; Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, virtualRootFileSystemLocation); } // Unload Local Machine Registry Program.Status.UpdateStatus(null, "Unmounting Offline Registry Hive", string.Format("Unmounting Offline Registry Hive at '{0}'", wimHivePath)); Program.SleepThread(500, false); wimReg.Unload(); } } catch (Exception) { wimCommitChanges = false; throw; } finally { // Unmount WIM Program.Status.UpdateStatus(null, "Unmounting WIM", string.Format("Unmounting WIM Image at '{0}'", wimMountPath)); Program.SleepThread(500, false); Interop.WIMInterop.WindowsImageContainer.NativeMethods.DismountImage(wimMountPath, InstallLocation, wimImageIndex, wimCommitChanges); if (m_MessageCallback != null) { Interop.WIMInterop.WindowsImageContainer.NativeMethods.UnregisterMessageCallback(m_MessageCallback); m_MessageCallback = null; } if (Directory.Exists(wimMountPath)) Directory.Delete(wimMountPath, true); if (Directory.Exists(wimTempMountPath)) Directory.Delete(wimTempMountPath, true); } } else { // Online File System Program.Status.UpdateStatus("Installing Bootstrapper (Online)", "Installing", string.Format("Install Location: {0}", InstallLocation), true, -1); Program.SleepThread(1000, false); string rootFileSystemLocation = Path.GetPathRoot(InstallLocation); RegistryKey rootRegistryLocation = Registry.LocalMachine.OpenSubKey("SOFTWARE", true); string fileSystemInstallLocation = InstallLocation.Substring(rootFileSystemLocation.Length); Install(rootFileSystemLocation, rootRegistryLocation, fileSystemInstallLocation, rootFileSystemLocation); Program.Status.UpdateStatus(null, "Online File System Installation Complete", string.Empty, true, -1); Program.SleepThread(1000, false); } Program.Status.UpdateStatus(null, "Complete", "Finished Installing Bootstrapper"); Program.SleepThread(1500, false); } private static uint WimImageEventMessagePump( uint MessageId, IntPtr wParam, IntPtr lParam, IntPtr UserData ) { uint status = (uint)Interop.WIMInterop.WindowsImageContainer.NativeMethods.WIMMessage.WIM_MSG_SUCCESS; Interop.WIMInterop.DefaultImageEventArgs eventArgs = new Interop.WIMInterop.DefaultImageEventArgs(wParam, lParam, UserData); //System.Diagnostics.Debug.WriteLine(MessageId); switch ((Interop.WIMInterop.WindowsImageContainer.ImageEventMessage)MessageId) { case Interop.WIMInterop.WindowsImageContainer.ImageEventMessage.Progress: case Interop.WIMInterop.WindowsImageContainer.ImageEventMessage.MountCleanupProgress: var timeRemainingMil = eventArgs.LeftParameter.ToInt32(); string timeRemainingMessage; if (timeRemainingMil > 0) timeRemainingMessage = TimeSpan.FromMilliseconds(timeRemainingMil).ToString(@"hh\:mm\:ss"); else timeRemainingMessage = "Calculating, please wait..."; var progress = eventArgs.WideParameter.ToInt32(); Program.Status.UpdateStatus(null, null, string.Format("Time remaining: {0}", timeRemainingMessage), true, progress); break; default: break; } return status; } public static void Uninstall() { // Application Directory var appDirectory = Program.InlinePath.Value; if (Program.AllowUninstall && !appDirectory.StartsWith("\\\\")) { Program.Status.UpdateStatus("System Preparation (Bootstrapper)", "Uninstalling Bootstrapper...", string.Empty, false, 0); Program.SleepThread(1000, true); //var uninstallScriptLocation = System.IO.Path.Combine(appDirectory, "UninstallBootstrapper.vbs"); //if (System.IO.File.Exists(uninstallScriptLocation)) //{ // var bootstrapperPID = System.Diagnostics.Process.GetCurrentProcess().Id; // var cscriptPath = System.IO.Path.Combine(Environment.SystemDirectory, "cscript.exe"); // var cscriptArgs = string.Format("\"{0}\" /WaitForProcessID:{1}", uninstallScriptLocation, bootstrapperPID); // var startProc = new ProcessStartInfo(cscriptPath, cscriptArgs); // startProc.WorkingDirectory = Environment.SystemDirectory; // startProc.WindowStyle = ProcessWindowStyle.Hidden; // Process.Start(startProc); //} // Remove Registry Entries using (var regWinlogon = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", true)) { regWinlogon.DeleteValue("HideStartupScripts", false); regWinlogon.DeleteValue("RunStartupScriptSync", false); } Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\Scripts\\Startup", false); Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Shutdown", false); Registry.LocalMachine.DeleteSubKeyTree("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine\\Scripts\\Startup", false); // Restore Registry Backups using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy", true)) { if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) { regGroupPolicy.DeleteSubKeyTree("Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts"); } } using (var regGroupPolicy = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\State\\Machine", true)) { if (regGroupPolicy != null && regGroupPolicy.GetSubKeyNames().Contains("Disco_Scripts")) { regGroupPolicy.DeleteSubKeyTree("Scripts"); RegistryUtilities.RenameSubKey(regGroupPolicy, "Disco_Scripts", "Scripts"); } } // Delete Group Policy Script File var groupPolicyScriptsPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\scripts.ini"); if (File.Exists(groupPolicyScriptsPath)) File.Delete(groupPolicyScriptsPath); var groupPolicyScriptsBackupPath = Path.Combine(Environment.SystemDirectory, "GroupPolicy\\Machine\\Scripts\\disco_scripts.ini"); if (File.Exists(groupPolicyScriptsBackupPath)) File.Move(groupPolicyScriptsBackupPath, groupPolicyScriptsPath); // Queue Folder for Deletion at next startup ForceDeleteFolder(new DirectoryInfo(appDirectory)); } } private static void ForceDeleteFolder(DirectoryInfo d) { foreach (var sd in d.GetDirectories()) ForceDeleteFolder(sd); foreach (var f in d.GetFiles()) { try { File.Delete(f.FullName); } catch (Exception) { MoveFileEx(f.FullName, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT); } } try { Directory.Delete(d.FullName); } catch (Exception) { MoveFileEx(d.FullName, null, MoveFileFlags.MOVEFILE_DELAY_UNTIL_REBOOT); } } } public static class RegistryUtilities { /// /// Renames a subkey of the passed in registry key since /// the Framework totally forgot to include such a handy feature. /// /// The RegistryKey that contains the subkey /// you want to rename (must be writeable) /// The name of the subkey that you want to rename /// /// The new name of the RegistryKey /// True if succeeds public static bool RenameSubKey(RegistryKey parentKey, string subKeyName, string newSubKeyName) { CopyKey(parentKey, subKeyName, newSubKeyName); parentKey.DeleteSubKeyTree(subKeyName); return true; } /// /// Copy a registry key. The parentKey must be writeable. /// /// /// /// /// public static bool CopyKey(RegistryKey parentKey, string keyNameToCopy, string newKeyName) { //Create new key using (RegistryKey destinationKey = parentKey.CreateSubKey(newKeyName)) { //Open the sourceKey we are copying from using (RegistryKey sourceKey = parentKey.OpenSubKey(keyNameToCopy)) { RecurseCopyKey(sourceKey, destinationKey); } } return true; } private static void RecurseCopyKey(RegistryKey sourceKey, RegistryKey destinationKey) { //copy all the values foreach (string valueName in sourceKey.GetValueNames()) { object objValue = sourceKey.GetValue(valueName); RegistryValueKind valKind = sourceKey.GetValueKind(valueName); if (valueName == "ExecTime") { destinationKey.SetValue(valueName, objValue, RegistryValueKind.Binary); } else { destinationKey.SetValue(valueName, objValue, valKind); } } //For Each subKey //Create a new subKey in destinationKey //Call myself foreach (string sourceSubKeyName in sourceKey.GetSubKeyNames()) { using (RegistryKey sourceSubKey = sourceKey.OpenSubKey(sourceSubKeyName)) { using (RegistryKey destSubKey = destinationKey.CreateSubKey(sourceSubKeyName)) { RecurseCopyKey(sourceSubKey, destSubKey); } } } } } }