using Microsoft.Win32; using Splat; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; namespace Shadowsocks.WPF.Utils { public static class AutoStartup { private static readonly string registryRunKey = @"Software\Microsoft\Windows\CurrentVersion\Run"; private static readonly string Key = "Shadowsocks_" + Utilities.ExecutablePath.GetHashCode(); public static bool Set(bool enabled) { RegistryKey? runKey = null; try { runKey = Registry.CurrentUser.CreateSubKey(registryRunKey, RegistryKeyPermissionCheck.ReadWriteSubTree); if (runKey == null) { LogHost.Default.Error(@"Cannot find HKCU\{registryRunKey}."); return false; } if (enabled) { runKey.SetValue(Key, Utilities.ExecutablePath); } else { runKey.DeleteValue(Key); } // When autostartup setting change, change RegisterForRestart state to avoid start 2 times RegisterForRestart(!enabled); return true; } catch (Exception e) { LogHost.Default.Error(e, "An error occurred while setting auto startup registry entry."); return false; } finally { if (runKey != null) { try { runKey.Close(); runKey.Dispose(); } catch (Exception e) { LogHost.Default.Error(e, "An error occurred while setting auto startup registry entry."); } } } } public static bool Check() { RegistryKey? runKey = null; try { runKey = Registry.CurrentUser.CreateSubKey(registryRunKey, RegistryKeyPermissionCheck.ReadWriteSubTree); if (runKey == null) { LogHost.Default.Error(@"Cannot find HKCU\{registryRunKey}."); return false; } var check = false; foreach (var valueName in runKey.GetValueNames()) { if (valueName.Equals(Key, StringComparison.InvariantCultureIgnoreCase)) { check = true; continue; } // Remove other startup keys with the same executable path. fixes #3011 and also assures compatibility with older versions if (Utilities.ExecutablePath.Equals(runKey.GetValue(valueName)?.ToString(), StringComparison.InvariantCultureIgnoreCase) is bool matchedDuplicate && matchedDuplicate) { runKey.DeleteValue(valueName); runKey.SetValue(Key, Utilities.ExecutablePath); check = true; } } return check; } catch (Exception e) { LogHost.Default.Error(e, "An error occurred while checking auto startup registry entries."); return false; } finally { if (runKey != null) { try { runKey.Close(); runKey.Dispose(); } catch (Exception e) { LogHost.Default.Error(e, "An error occurred while checking auto startup registry entries."); } } } } [DllImport("kernel32.dll", SetLastError = true)] static extern int RegisterApplicationRestart([MarshalAs(UnmanagedType.LPWStr)] string commandLineArgs, int Flags); [DllImport("kernel32.dll", SetLastError = true)] static extern int UnregisterApplicationRestart(); [Flags] enum ApplicationRestartFlags { RESTART_ALWAYS = 0, RESTART_NO_CRASH = 1, RESTART_NO_HANG = 2, RESTART_NO_PATCH = 4, RESTART_NO_REBOOT = 8, } // register restart after system reboot/update public static void RegisterForRestart(bool register) { // requested register and not autostartup if (register && !Check()) { // escape command line parameter string[] args = new List(Environment.GetCommandLineArgs()) .Select(p => p.Replace("\"", "\\\"")) // escape " to \" .Select(p => p.IndexOf(" ") >= 0 ? "\"" + p + "\"" : p) // encapsule with " .ToArray(); string cmdline = string.Join(" ", args); // first parameter is process command line parameter // needn't include the name of the executable in the command line RegisterApplicationRestart(cmdline, (int)(ApplicationRestartFlags.RESTART_NO_CRASH | ApplicationRestartFlags.RESTART_NO_HANG)); LogHost.Default.Debug("Register restart after system reboot, command line:" + cmdline); } // requested unregister, which has no side effect else if (!register) { UnregisterApplicationRestart(); LogHost.Default.Debug("Unregister restart after system reboot"); } } } }