441 lines
18 KiB
C#
441 lines
18 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
|
|
namespace WpsInstaller
|
|
{
|
|
class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
Console.WriteLine("========================================");
|
|
Console.WriteLine(" WPS Add-in Installer Tool");
|
|
Console.WriteLine("========================================");
|
|
Console.WriteLine();
|
|
|
|
string mode = "wps"; // default
|
|
if (args.Length > 0)
|
|
{
|
|
mode = args[0].ToLower();
|
|
}
|
|
|
|
try
|
|
{
|
|
// 1. Locate WordAddIn.dll and WordAddIn.vsto
|
|
string currentDir = AppDomain.CurrentDomain.BaseDirectory;
|
|
string dllPath = Path.Combine(currentDir, "WordAddIn.dll");
|
|
string vstoPath = Path.Combine(currentDir, "WordAddIn.vsto");
|
|
|
|
if (!File.Exists(dllPath))
|
|
{
|
|
// Fallback for Dev scenario
|
|
string devPathDll = Path.GetFullPath(Path.Combine(currentDir, @"..\..\..\WordAddIn\bin\Debug\WordAddIn.dll"));
|
|
if (!File.Exists(devPathDll))
|
|
{
|
|
devPathDll = Path.GetFullPath(Path.Combine(currentDir, @"..\..\..\WordAddIn\bin\Release\WordAddIn.dll"));
|
|
}
|
|
|
|
if (File.Exists(devPathDll))
|
|
{
|
|
dllPath = devPathDll;
|
|
vstoPath = Path.ChangeExtension(dllPath, ".vsto");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("Error: Could not find WordAddIn.dll.");
|
|
Pause();
|
|
return;
|
|
}
|
|
}
|
|
|
|
Console.WriteLine("Found DLL: " + dllPath);
|
|
|
|
if (mode == "word")
|
|
{
|
|
Console.WriteLine("Mode: Switch to MS Word (VSTO)");
|
|
// 1. Clean WPS Keys
|
|
CleanupWpsKeys();
|
|
|
|
// 1.5 Clear ClickOnce cache to avoid upgrade conflicts
|
|
Console.WriteLine("Clearing ClickOnce cache to avoid upgrade conflicts...");
|
|
ClearClickOnceCache();
|
|
|
|
// 2. Register VSTO
|
|
if (RegisterVsto(vstoPath))
|
|
{
|
|
Console.WriteLine(" [OK] VSTO registered successfully.");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" [FAIL] Failed to register VSTO.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine("Mode: Switch to WPS Office");
|
|
// 1. Register Assembly (COM Registration)
|
|
Console.WriteLine("Registering Assembly for COM Interop...");
|
|
if (RegisterAssembly(dllPath))
|
|
{
|
|
Console.WriteLine(" [OK] Assembly registered successfully.");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" [FAIL] Failed to register assembly.");
|
|
Pause();
|
|
return;
|
|
}
|
|
|
|
// 2. Register for WPS (Registry Keys)
|
|
Console.WriteLine("Configuring WPS Registry Keys...");
|
|
if (RegisterForWPS())
|
|
{
|
|
Console.WriteLine(" [OK] WPS Registry keys configured successfully.");
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" [FAIL] Failed to configure WPS registry keys.");
|
|
Pause();
|
|
return;
|
|
}
|
|
|
|
// 3. Cleanup conflicting VSTO registration
|
|
Console.WriteLine("Checking for conflicting VSTO registrations...");
|
|
CleanupVstoKeys();
|
|
}
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine("========================================");
|
|
Console.WriteLine(" Operation Completed Successfully!");
|
|
Console.WriteLine("========================================");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine("An unexpected error occurred: " + ex.Message);
|
|
Console.WriteLine(ex.StackTrace);
|
|
}
|
|
|
|
Pause();
|
|
}
|
|
|
|
static void UninstallOldVersions()
|
|
{
|
|
Console.WriteLine("Checking for existing versions to uninstall...");
|
|
|
|
// 1. Cleanup VSTO Keys
|
|
CleanupVstoKeys();
|
|
|
|
// 2. Cleanup WPS Keys
|
|
CleanupWpsKeys();
|
|
|
|
// 3. Cleanup COM Registration (WPS)
|
|
UnregisterCom("{D8E52004-9D68-4509-8A11-66774D4F0523}", "WordAddIn.WpsAddIn");
|
|
}
|
|
|
|
static void UnregisterCom(string guid, string progId)
|
|
{
|
|
try
|
|
{
|
|
// Try to find CodeBase from HKCR\CLSID\{GUID}\InprocServer32
|
|
string clsidKeyPath = @"CLSID\" + guid;
|
|
string inprocKeyPath = clsidKeyPath + @"\InprocServer32";
|
|
string oldDllPath = null;
|
|
|
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey(inprocKeyPath))
|
|
{
|
|
if (key != null)
|
|
{
|
|
oldDllPath = key.GetValue("CodeBase") as string;
|
|
if (string.IsNullOrEmpty(oldDllPath))
|
|
{
|
|
oldDllPath = key.GetValue("") as string; // Default value
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(oldDllPath))
|
|
{
|
|
// Fix file:/// prefix if present
|
|
if (oldDllPath.StartsWith("file:///", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
oldDllPath = oldDllPath.Substring(8).Replace('/', '\\');
|
|
}
|
|
|
|
if (File.Exists(oldDllPath))
|
|
{
|
|
Console.WriteLine(" Found old COM registration pointing to: " + oldDllPath);
|
|
Console.WriteLine(" Unregistering assembly...");
|
|
|
|
string regAsmPath = GetRegAsmPath();
|
|
if (!string.IsNullOrEmpty(regAsmPath))
|
|
{
|
|
string args = string.Format("\"{0}\" /u /tlb", oldDllPath);
|
|
ProcessStartInfo psi = new ProcessStartInfo(regAsmPath, args);
|
|
psi.UseShellExecute = false;
|
|
psi.RedirectStandardOutput = true;
|
|
psi.CreateNoWindow = true;
|
|
|
|
using (Process p = Process.Start(psi))
|
|
{
|
|
string output = p.StandardOutput.ReadToEnd();
|
|
p.WaitForExit();
|
|
// Console.WriteLine(output); // Optional: show output
|
|
}
|
|
Console.WriteLine(" [OK] RegAsm /u completed.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" Old COM DLL not found at: " + oldDllPath + ". Skipping RegAsm /u.");
|
|
}
|
|
}
|
|
|
|
// Force delete registry keys to be sure
|
|
try
|
|
{
|
|
Registry.ClassesRoot.DeleteSubKeyTree(clsidKeyPath, false);
|
|
Console.WriteLine(" [OK] Deleted HKCR\\" + clsidKeyPath);
|
|
}
|
|
catch {}
|
|
|
|
try
|
|
{
|
|
if (!string.IsNullOrEmpty(progId))
|
|
{
|
|
Registry.ClassesRoot.DeleteSubKeyTree(progId, false);
|
|
Console.WriteLine(" [OK] Deleted HKCR\\" + progId);
|
|
}
|
|
}
|
|
catch {}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Warning during COM unregistration: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
static void Pause()
|
|
{
|
|
Console.WriteLine("\nPress any key to exit...");
|
|
Console.ReadKey();
|
|
}
|
|
|
|
static void ClearClickOnceCache()
|
|
{
|
|
try
|
|
{
|
|
string localApp = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
|
string cachePath = System.IO.Path.Combine(localApp, "Apps", "2.0");
|
|
if (Directory.Exists(cachePath))
|
|
{
|
|
Directory.Delete(cachePath, true);
|
|
Console.WriteLine(" [OK] Cleared ClickOnce cache at " + cachePath);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine(" ClickOnce cache not found, skipping.");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Warning: Failed to clear ClickOnce cache: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
static bool RegisterVsto(string vstoPath)
|
|
{
|
|
if (!File.Exists(vstoPath))
|
|
{
|
|
Console.WriteLine(" Error: .vsto file not found at " + vstoPath);
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
// Register via Registry for Development/Local use
|
|
// HKCU\Software\Microsoft\Office\Word\Addins\WordAddIn
|
|
string keyPath = @"Software\Microsoft\Office\Word\Addins\WordAddIn";
|
|
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
|
|
{
|
|
if (key == null) return false;
|
|
|
|
key.SetValue("Description", "WordAddIn", RegistryValueKind.String);
|
|
key.SetValue("FriendlyName", "WordAddIn", RegistryValueKind.String);
|
|
key.SetValue("LoadBehavior", 3, RegistryValueKind.DWord);
|
|
// Point to vsto file with |vstolocal to indicate local file
|
|
key.SetValue("Manifest", vstoPath + "|vstolocal", RegistryValueKind.String);
|
|
|
|
Console.WriteLine(" Created Key: HKCU\\" + keyPath);
|
|
Console.WriteLine(" Manifest: " + vstoPath + "|vstolocal");
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Error registering VSTO: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void CleanupWpsKeys()
|
|
{
|
|
try
|
|
{
|
|
string keyPath = @"Software\Kingsoft\Office\Word\Addins\WordAddIn.WpsAddIn";
|
|
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyPath, true))
|
|
{
|
|
if (key != null)
|
|
{
|
|
Console.WriteLine(" Found WPS key. Deleting...");
|
|
Registry.CurrentUser.DeleteSubKeyTree(keyPath);
|
|
Console.WriteLine(" [OK] WPS key deleted.");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Warning: Failed to cleanup WPS keys: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
static bool RegisterAssembly(string assemblyPath)
|
|
{
|
|
try
|
|
{
|
|
// Use RegistrationServices to register the assembly
|
|
// This is equivalent to regasm /codebase
|
|
// Note: This requires the assembly to be strong-named if registered in GAC,
|
|
// but for simple codebase registration, it might work if run as Admin.
|
|
|
|
// However, standard RegistrationServices doesn't easily support /codebase option programmatically
|
|
// without some caveats.
|
|
// The most robust way for a utility is often to shell out to RegAsm.exe if available,
|
|
// OR use RegistrationServices.RegisterAssembly.
|
|
|
|
// Let's try RegistrationServices first.
|
|
RegistrationServices regService = new RegistrationServices();
|
|
Assembly asm = Assembly.LoadFrom(assemblyPath);
|
|
|
|
// RegisterAssembly adds entries to HKCR\CLSID\{GUID}
|
|
// We need to set the CodeBase registry key manually if not in GAC,
|
|
// or ensure RegistrationServices does it.
|
|
// Actually, RegistrationServices.RegisterAssembly does NOT set the CodeBase by default.
|
|
// It works like RegAsm without /codebase.
|
|
|
|
// So calling RegAsm.exe via process is safer to ensure /codebase behavior.
|
|
string regAsmPath = GetRegAsmPath();
|
|
if (string.IsNullOrEmpty(regAsmPath))
|
|
{
|
|
Console.WriteLine(" Warning: Could not find RegAsm.exe. Attempting manual registration...");
|
|
return ManualRegister(asm, assemblyPath);
|
|
}
|
|
|
|
string args = string.Format("\"{0}\" /codebase /tlb", assemblyPath);
|
|
ProcessStartInfo psi = new ProcessStartInfo(regAsmPath, args);
|
|
psi.UseShellExecute = false;
|
|
psi.RedirectStandardOutput = true;
|
|
psi.CreateNoWindow = true;
|
|
|
|
Console.WriteLine(string.Format(" Executing: {0} {1}", psi.FileName, psi.Arguments));
|
|
|
|
using (Process p = Process.Start(psi))
|
|
{
|
|
string output = p.StandardOutput.ReadToEnd();
|
|
p.WaitForExit();
|
|
Console.WriteLine(output);
|
|
return p.ExitCode == 0;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Error registering assembly: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static bool ManualRegister(Assembly asm, string assemblyPath)
|
|
{
|
|
// Fallback: This is complex to do fully manually for all COM types.
|
|
// If RegAsm is missing, we might be in trouble.
|
|
// But usually on a machine with .NET Framework (needed to run this app), RegAsm is present.
|
|
return false;
|
|
}
|
|
|
|
static string GetRegAsmPath()
|
|
{
|
|
// Usually in C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe
|
|
// or Framework64
|
|
string windir = Environment.GetEnvironmentVariable("windir");
|
|
string fxDir = Path.Combine(windir, @"Microsoft.NET\Framework\v4.0.30319");
|
|
// Check for 64 bit OS and prefer Framework64 if this process is 64 bit,
|
|
// BUT WordAddIn is likely AnyCPU.
|
|
// If Word/WPS is 32-bit, we need 32-bit RegAsm. If 64-bit, we need 64-bit RegAsm.
|
|
// Ideally register both.
|
|
|
|
// For safety, let's try to find the one matching the current process architecture,
|
|
// or just use the 32-bit one as it is most common for Office.
|
|
|
|
string regAsm = Path.Combine(fxDir, "RegAsm.exe");
|
|
if (File.Exists(regAsm)) return regAsm;
|
|
|
|
return null;
|
|
}
|
|
|
|
static bool RegisterForWPS()
|
|
{
|
|
try
|
|
{
|
|
string keyPath = @"Software\Kingsoft\Office\Word\Addins\WordAddIn.WpsAddIn";
|
|
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyPath))
|
|
{
|
|
if (key == null)
|
|
{
|
|
Console.WriteLine(" Error: Could not create registry key HKCU\\" + keyPath);
|
|
return false;
|
|
}
|
|
|
|
key.SetValue("LoadBehavior", 3, RegistryValueKind.DWord);
|
|
key.SetValue("FriendlyName", "AI Word Assistant for WPS", RegistryValueKind.String);
|
|
key.SetValue("Description", "AI Assistant", RegistryValueKind.String);
|
|
key.SetValue("CommandLineSafe", 0, RegistryValueKind.DWord);
|
|
|
|
Console.WriteLine(" Created Key: HKCU\\" + keyPath);
|
|
Console.WriteLine(" Set LoadBehavior = 3");
|
|
}
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Error writing WPS registry keys: " + ex.Message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void CleanupVstoKeys()
|
|
{
|
|
try
|
|
{
|
|
// VSTO adds keys in HKCU\Software\Microsoft\Office\Word\Addins\WordAddIn
|
|
string vstoKeyPath = @"Software\Microsoft\Office\Word\Addins\WordAddIn";
|
|
|
|
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(vstoKeyPath, true))
|
|
{
|
|
if (key != null)
|
|
{
|
|
Console.WriteLine(" Found VSTO key in HKCU\\" + vstoKeyPath + ". Deleting to avoid conflicts...");
|
|
Registry.CurrentUser.DeleteSubKeyTree(vstoKeyPath);
|
|
Console.WriteLine(" [OK] VSTO key deleted.");
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine(" Warning: Failed to cleanup VSTO keys: " + ex.Message);
|
|
}
|
|
}
|
|
}
|
|
}
|