using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Media.Imaging; using Install.Core.Common; namespace Install.Core { public class InstallWizard : INotifyPropertyChanged { public string Msg { get; private set; } /// <summary> /// 进度 100% 满 /// </summary> public double Progress { get; private set; } /// <summary> /// 总安装列表 /// </summary> [PropertyChanged.DoNotNotify] public InstallPackCollection InstallPacks { get; private set; } /// <summary> /// 需要再选择安装的软件列表 /// </summary> [PropertyChanged.DoNotNotify] public List<InstallPack> ToInstall { get; } = new List<InstallPack>(); /// <summary> /// //已经安装的软件列表 /// </summary> [PropertyChanged.DoNotNotify] public List<InstallInfo> HasInstalled { get; } = new List<InstallInfo>(); /// <summary> /// 图标列表 /// </summary> public Dictionary<string, BitmapSource> Icons { get; } = new Dictionary<string, BitmapSource>(); /// <summary> /// 有更加新的安装包 /// </summary> public bool HasNewestInstallZip { get; private set; } /// <summary> /// 从网络检测信息中 /// </summary> public bool IsCheckingNetwork { get; private set; } /// <summary> /// 最新的安装包版本 /// </summary> public string NewestInstallZipVersion { get; private set; } /// <summary> /// 当前的安装包版本 /// </summary> public string CurrentInstallZipVersion { get; private set; } public NewestInstallZipVersionInfo NewestInstallZipVersionInfo { get; private set; } #region 下载包 public double DownloadInfo_Progress => DownloadInfo_TotalSize > 0 ? 1.0 * DownloadInfo_CurrSize / DownloadInfo_TotalSize : 0; public long DownloadInfo_TotalSize { get; private set; } public long DownloadInfo_CurrSize { get; private set; } public string DownloadInfo_TotalSizeStr => fileSize2Str(DownloadInfo_TotalSize); public string DownloadInfo_CurrSizeStr => fileSize2Str(DownloadInfo_CurrSize); public string DownloadInfo_SpeedStr { get; private set; } public string DownloadInfo_ElapsedTimeStr { get; private set; } public string DownloadInfo_RemainingTimeStr { get; private set; } public string DownloadInfo_ErrMsg { get; private set; } #endregion public event PropertyChangedEventHandler PropertyChanged; CancellationTokenSource cancellation; List<UpdateScriptInfo> updateScripts = new List<UpdateScriptInfo>(); public InstallWizard() { } void NotifyPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public bool Init() { if(!LoadInstallPacks()) return false; UpdateHasInstalled(); UpdateToInstall(); UpdateIcons(); DownloadNewestInstallZipInfo(); return true; } /// <summary> /// 下载最新的安装包信息 /// </summary> void DownloadNewestInstallZipInfo() { IsCheckingNetwork = true; //新建线程 Task.Factory.StartNew(() => { var bytes = HttpExt.HttpDownload(InstallPacks.NewestInstallZipVersionInfoPath); if (bytes == null) return; //bytes 转 string string json = System.Text.Encoding.UTF8.GetString(bytes); NewestInstallZipVersionInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<NewestInstallZipVersionInfo>(json); NewestInstallZipVersion = NewestInstallZipVersionInfo.InstallZipVersion; int ret = VerExt.VersionCompare(InstallPacks.InstallZipVersion, NewestInstallZipVersionInfo.InstallZipVersion); IsCheckingNetwork = false; HasNewestInstallZip = (ret < 0); }); } string fileSize2Str(long fileSizeByte) { double fileSize = fileSizeByte; int i = 0; while (fileSize > 1024) { i++; fileSize /= 1024; if (i >= 2) break; } switch (i) { case 0: return $"{fileSize:F0}B"; case 1: return $"{fileSize:F3}KB"; default://case 2: return $"{fileSize:F3}MB"; } } string fileSpeed2Str(long fileSpeedBytePerSec) { double fileSpeed = fileSpeedBytePerSec; int i = 0; while (fileSpeed > 1024) { i++; fileSpeed /= 1024; if (i >= 2) break; } switch (i) { case 0: return $"{ fileSpeed:F0}B/s"; case 1: return $"{ fileSpeed:F1}KB/s"; default://case 2: return $"{ fileSpeed:F1}MB/s"; } } string sec2Str(int sec) { if (sec > 60) return $"{(int)(sec / 60)}分 {sec % 60}秒"; else return $"{sec % 60}秒"; } public string NewestInstallZipPath { get; private set; } /// <summary> /// 下载最新的安装包 /// </summary> public async Task<bool> DownloadNewestInstallZip() { //新建线程 var ret = await Task.Factory.StartNew(() => { string path; if (string.IsNullOrEmpty(InstallPacks.DefaultNewestInstallZipPath)) { //下载到当前 exe 的上级目录 path = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; path = Path.GetDirectoryName(path); path = Path.GetDirectoryName(path); } else { path = InstallPacks.DefaultNewestInstallZipPath; } path = Path.Combine(path, NewestInstallZipVersionInfo.InstallZipName); NewestInstallZipPath = path; try { HttpExt.BreakpointDownload( NewestInstallZipVersionInfo.InstallZipUrl, NewestInstallZipPath+".7z", false, (totalSize) => DownloadInfo_TotalSize = totalSize, (currSize) => DownloadInfo_CurrSize = currSize, (speed) => DownloadInfo_SpeedStr = fileSpeed2Str(speed), (sec) => DownloadInfo_ElapsedTimeStr = sec2Str(sec), (sec) => DownloadInfo_RemainingTimeStr = sec2Str(sec)); DownloadInfo_ErrMsg = null; return true; } catch (Exception e) { DownloadInfo_ErrMsg = e.Message; return false; } }); return ret; } /// <summary> /// 加载安装包 /// </summary> bool LoadInstallPacks() { string installpack_path = "install.json"; InstallPackCollection installPackCollection; try { Msg = $"加载 {installpack_path}"; string json = File.ReadAllText(installpack_path); installPackCollection = Newtonsoft.Json.JsonConvert.DeserializeObject<InstallPackCollection>(json); } catch { Msg = $"加载 {installpack_path} 失败"; return false; } string rootpath = Path.GetDirectoryName(installpack_path); List<InstallPack> invalids = new List<InstallPack>(); //读取exe 版本 foreach (var installPack in installPackCollection.Items) { string path=""; if (!string.IsNullOrEmpty(rootpath)) path = $@"{rootpath}\"; path+=$@"{installPack.PackPath}\{installPack.Exe}"; if (!File.Exists(path))//配置文件写了,但在安装包路径找不到 { invalids.Add(installPack); continue; } FileVersionInfo fileVerInfo = System.Diagnostics.FileVersionInfo.GetVersionInfo(path); //版本号显示为“主版本号.次版本号.内部版本号.专用部件号”。 string version = String.Format("{0}.{1}.{2}.{3}", fileVerInfo.FileMajorPart, fileVerInfo.FileMinorPart, fileVerInfo.FileBuildPart, fileVerInfo.FilePrivatePart); installPack.Version = version; } //删除不存在的安装包信息 foreach (var installPack in invalids) installPackCollection.Items.Remove(installPack); if (installPackCollection.Items.Count == 0) { Msg = $"加载 {installpack_path} 失败!! 没有任何安装包信息"; return false; } InstallPacks = installPackCollection; NotifyPropertyChanged(nameof(InstallPacks)); CurrentInstallZipVersion = InstallPacks.InstallZipVersion; return true; } /// <summary> /// 更新还没安装的程序列表 /// </summary> void UpdateToInstall() { ToInstall.Clear(); if (HasInstalled.Count == 0) { ToInstall.AddRange(InstallPacks.Items); NotifyPropertyChanged(nameof(ToInstall)); return; } var items = from item in InstallPacks.Items where !HasInstalled.Any(installinfo => installinfo.ProcessName == item.ProcessName) select item; ToInstall.AddRange(items); NotifyPropertyChanged(nameof(ToInstall)); } /// <summary> /// 更新已经安装的软件 /// </summary> void UpdateHasInstalled() { HasInstalled.Clear(); var installInfos = InstallInfoHelper.GetInstallInfos(); if (installInfos.Count() > 0) { HasInstalled.AddRange(installInfos); } //之前不是安装的,是直接复制到D盘运行的 installInfos = InstallInfoHelper.GetInstallInfosFromPath(InstallPacks); if (installInfos.Count() > 0) { //一样的不复制进去 foreach (var installInfo in installInfos) { if (HasInstalled.Exists(ii => ii.InstallPath == installInfo.InstallPath)) continue; HasInstalled.Add(installInfo); } } //安装包有的,更新 最新版本号 foreach (var installInfo in HasInstalled) { var item = InstallPacks.Items.Find(_item => installInfo.ProcessName == _item.ProcessName); if (item != null) { installInfo.NewestVersion = item.Version; if (installInfo.NewestVersion != installInfo.Version) { installInfo.NeedToUpdate = true; } } } NotifyPropertyChanged(nameof(HasInstalled)); } /// <summary> /// 更新在 安装包中的 图标列表 /// </summary> void UpdateIcons() { Icons.Clear(); foreach (var installPack in InstallPacks.Items) { string exePath = $@"{installPack.PackPath }\{installPack.Exe }"; var bitmapSource = IconExt.GetIcon(exePath); if (bitmapSource != null) { Icons.Add(installPack.ProcessName, bitmapSource); } } } InstallInfo Map(InstallPack installPack, string installBasePath) { string installPath = $@"{installBasePath}\{installPack.ProcessName}"; return new InstallInfo() { InstallPath = installPath, Exe = installPack.Exe, Name = installPack.Name, IsAutoRun = installPack.IsAutoRun, Version = installPack.Version }; } double updateProgress(double subProgress, int subStep,int stepCnt) { return subProgress * 1.0 / stepCnt + subStep * 1.0 / stepCnt; } string progressMsg() { return $"{Progress * 100:F0}% :"; } bool kill(string processName, out bool isRunning) { Process[] processes = Process.GetProcessesByName(processName); if (processes.Count() > 0) { Msg = $"{progressMsg()} 关闭正在运行的 {processName}"; isRunning = true; foreach (var p in processes) p.Kill(); bool isDoubleCheckClose = true; int checkCnt = 0; while (isDoubleCheckClose)//检查是否关闭了 { checkCnt++; if (checkCnt > 3) { // 检查次数太多,异常 Msg = $"{progressMsg()} 异常!!!!多次关闭 {processName} 失败"; return false; } Task.Delay(3000).Wait(); //检查关闭了没? processes = Process.GetProcessesByName(processName); if (processes.Count() == 0) { //终于关闭了 isDoubleCheckClose = false; } } } else { isRunning = false; } return true; } /// <summary> /// 安装程序 /// </summary> /// <param name="installBasePath"></param> /// <param name="installPacks"></param> /// <returns></returns> public async Task<bool> Install(string installBasePath, IEnumerable<InstallPack> installPacks) { cancellation = new CancellationTokenSource(); return await Task.Factory.StartNew(() => { Progress = 0; int stepCnt = installPacks.Count(); int subStep = 0; foreach (InstallPack installPack in installPacks) { if (cancellation.IsCancellationRequested) return false; Progress = updateProgress(0, subStep, stepCnt); Msg = $"{progressMsg()} 安装 {installPack.Name}"; //检查 //根据 安装信息, 先关闭正在运行的 安装信息.exe, if (!kill(installPack.ProcessName, out bool isRunning)) { return false; } string installPath = $@"{installBasePath}\{installPack.ProcessName}"; if (Directory.Exists(installPath)) { Progress = updateProgress(0.1, subStep, stepCnt); Msg = $"{progressMsg()} 删除 {installPath}"; Directory.Delete(installPath, true); } //复制到目标目录 Progress = updateProgress(0.2, subStep, stepCnt); Msg = $"{progressMsg()} 复制到 {installPath}"; string srcPath = installPack.PackPath; CopyExt.Copy(srcPath, installPath); //创建快捷方式 Progress = updateProgress(0.6, subStep, stepCnt); Msg = $"{progressMsg()} 创建快捷方式 {installPack.Name}"; string exePath = $@"{installPath}\{installPack.Exe}"; ShortcutCreator.CreateShortcut(installPath, installPack.Name, exePath); //创建桌面快捷方式 Progress = updateProgress(0.7, subStep, stepCnt); Msg = $"{progressMsg()} 创建桌面快捷方式 {installPack.Name}"; ShortcutCreator.CreateShortcutOnDesktop(installPack.Name, exePath); //把安装信息写入到注册表 Progress = updateProgress(0.8, subStep, stepCnt); Msg = $"{progressMsg()} 把安装信息写入到注册表"; var installinfo = Map(installPack, installBasePath); InstallInfoHelper.SetInstallInfo(Map(installPack, installBasePath)); //设置开机启动 if (installinfo.IsAutoRun) InstallInfoHelper.SetAutoRun(installinfo); else InstallInfoHelper.RemoveAutoRun(installinfo); subStep++; } //最后, 把 Progress = 1; Msg = $"{progressMsg()} 安装完成"; return true; }, cancellation.Token); } /// <summary> /// 升级程序 /// </summary> /// <returns></returns> public async Task<bool> Update(IEnumerable<InstallInfo> installInfos) { cancellation = new CancellationTokenSource(); return await Task.Factory.StartNew(() => { Progress = 0; int stepCnt = installInfos.Count() + 1; int subStep = 0; Progress = updateProgress(0, subStep, stepCnt); //先把全部软件停止了,再复制 foreach (var installInfo in installInfos) { //检查 //根据 安装信息, 先关闭正在运行的 安装信息.exe, if (!kill(installInfo.ProcessName, out bool isRunning)) { Msg = $"{progressMsg()} 无法关闭 {installInfo.Name}"; return false; } } foreach (var installInfo in installInfos) { if (cancellation.IsCancellationRequested) return false; Progress = updateProgress(0, subStep, stepCnt); Msg = $"{progressMsg()} 安装 {installInfo.Name}"; var installPack = InstallPacks.Items.Find(item => item.ProcessName == installInfo.ProcessName); if (installPack == null) { Msg = $"{progressMsg()} 找不到 {installInfo.ProcessName} 安装包资料"; return false; } string installPath = installInfo.InstallPath; string srcPath = installPack.PackPath; Progress = updateProgress(0.2, subStep, stepCnt); if (!string.IsNullOrEmpty(installPack.UpdateScript)) { if (!File.Exists(installPack.UpdateScript)) { Msg = $"{progressMsg()} {installPack.UpdateScript}升级脚本不存在"; return false; } if (string.IsNullOrEmpty(installPack.ScriptTypeFullName)) { Msg = $"{progressMsg()} {installPack.UpdateScript} 没有正确的 脚本类全名"; return false; } IUpdateScript updateScript; var _usi = updateScripts.Find(usi => usi.Dll == installPack.UpdateScript && usi.TypeFullName == installPack.ScriptTypeFullName); if (_usi == null) { string dll = installPack.UpdateScript; Assembly assembly = Assembly.LoadFrom(dll); var scriptType = assembly.GetType(installPack.ScriptTypeFullName); updateScript = (IUpdateScript)System.Activator.CreateInstance(scriptType); updateScripts.Add(new UpdateScriptInfo() { Dll = dll, TypeFullName = installPack.ScriptTypeFullName, updateScript = updateScript }); } else { updateScript = _usi.updateScript; } bool ret = updateScript.Update(installPack, installInfo.Version, installInfo.InstallPath, (msg) => { Msg = $"{progressMsg()} " + msg; }); if (ret == false) return false; } else { Msg = $"{progressMsg()} 删除 {installPath} 内的全部 *.exe / *.dll / x64文件夹 /x86文件夹"; DirectoryInfo directoryInfo = new DirectoryInfo(installPath); if (directoryInfo.Exists) { var fileinfos = directoryInfo.GetFiles("*.exe"); foreach (var fileinfo in fileinfos) File.Delete(fileinfo.FullName); fileinfos = directoryInfo.GetFiles("*.dll"); foreach (var fileinfo in fileinfos) File.Delete(fileinfo.FullName); string path = $@"{installPath}\x64"; if (Directory.Exists(path)) Directory.Delete(path, true); path = $@"{installPath}\x86"; if (Directory.Exists(path)) Directory.Delete(path, true); } //复制到目标目录 //找出安装包 Progress = updateProgress(0.3, subStep, stepCnt); Msg = $"{progressMsg()} 复制到 {installPath} 内的全部 *.exe 与 *.dll"; if (!CopyExt.Copy(srcPath, installPath, new string[] { ".exe", ".dll" })) { Msg = CopyExt.Error.ToString(); Msg = "文件写入失败, 请先关闭正在执行的程序,再重试。"; return false; } //--------------------------------------------------------------------------------- if (Directory.Exists($@"{srcPath}\x64")) { Msg = $@"复制到 {installPath}\x64"; if (!CopyExt.Copy($@"{srcPath}\x64", $@"{installPath}\x64")) { Msg = CopyExt.Error.ToString(); Msg = "文件写入失败, 请先关闭正在执行的程序,再重试。"; return false; } } //--------------------------------------------------------------------------------- if (Directory.Exists($@"{srcPath}\x86")) { Msg = $@"复制到 {installPath}\x86"; if (!CopyExt.Copy($@"{srcPath}\x86", $@"{installPath}\x86")) { Msg = CopyExt.Error.ToString(); Msg = "文件写入失败, 请先关闭正在执行的程序,再重试。"; return false; } } //复制其它文件 if (installPack.Others != null && installPack.Others.Count() > 0) { Progress = updateProgress(0.5, subStep, stepCnt); Msg = $"{progressMsg()} 复制其它文件到 {installPath}"; foreach (var other in installPack.Others) { if (!CopyExt.CopyFileOrDirectory($@"{srcPath}\{other}", $@"{installPath}\{other}")) { Msg = CopyExt.Error.ToString(); Msg = "文件写入失败, 请先关闭正在执行的程序,再重试。"; return false; } } } } //创建快捷方式 Progress = updateProgress(0.6, subStep, stepCnt); Msg = $"{progressMsg()} 创建快捷方式 {installInfo.Name}"; string exePath = $@"{installPath}\{installInfo.Exe}"; ShortcutCreator.CreateShortcut(installPath, installInfo.Name, exePath); //创建桌面快捷方式 Progress = updateProgress(0.7, subStep, stepCnt); Msg = $"{progressMsg()} 创建桌面快捷方式 {installInfo.Name}"; ShortcutCreator.CreateShortcutOnDesktop(installInfo.Name, exePath); //把安装信息写入到注册表 Progress = updateProgress(0.8, subStep, stepCnt); Msg = $"{progressMsg()} 把安装信息写入到注册表"; InstallInfoHelper.SetInstallInfo(installInfo); //设置开机启动 if (installInfo.IsAutoRun) InstallInfoHelper.SetAutoRun(installInfo); else InstallInfoHelper.RemoveAutoRun(installInfo); subStep++; } Progress = 1; Msg = $"{progressMsg()} 完成"; return true; }, cancellation.Token); } /// <summary> /// 设置开机启动项 & 启动项 /// </summary> /// <param name="installInfos"></param> /// <returns></returns> public async Task<bool> SetAutoRun(IEnumerable<InstallInfo> installInfos) { cancellation = new CancellationTokenSource(); return await Task.Factory.StartNew(() => { Progress = 0; int stepCnt = installInfos.Count(); int subStep = 0; foreach (InstallInfo installInfo in installInfos) { if (cancellation.IsCancellationRequested) return false; Progress = updateProgress(0, subStep, stepCnt); //创建桌面快捷方式 Msg = $"{progressMsg()} 创建桌面快捷方式 {installInfo.Name}"; ShortcutCreator.CreateShortcutOnDesktop(installInfo.Name, installInfo.ExePath); Msg = $"{progressMsg()} 设置 {installInfo.Name} 开机启动项"; //设置开机启动 if (installInfo.IsAutoRun) InstallInfoHelper.SetAutoRun(installInfo); else InstallInfoHelper.RemoveAutoRun(installInfo); subStep++; } Progress = 1; Msg = $"{progressMsg()} 完成"; return true; }, cancellation.Token); } /// <summary> /// 删除软件 /// </summary> /// <param name="installInfos"></param> /// <returns></returns> public async Task<bool> Remove(IEnumerable<InstallInfo> installInfos) { cancellation = new CancellationTokenSource(); return await Task.Factory.StartNew(() => { Progress = 0; int stepCnt = installInfos.Count(); int subStep = 0; foreach (InstallInfo installInfo in installInfos) { if (cancellation.IsCancellationRequested) return false; //检查 //根据 安装信息, 先关闭正在运行的 安装信息.exe, Progress = updateProgress(0, subStep, stepCnt); if (!kill(installInfo.ProcessName, out bool isRunning)) { return false; } Progress = updateProgress(0.1, subStep, stepCnt); Msg = $"{progressMsg()} 删除 {installInfo.Name} 硬盘数据"; Directory.Delete(installInfo.InstallPath, true); Msg = $"{progressMsg()} 删除 {installInfo.Name} 注册表信息"; InstallInfoHelper.RemoveInstallInfo(installInfo); InstallInfoHelper.RemoveAutoRun(installInfo); Msg = $"{progressMsg()} 删除 {installInfo.Name} 桌面快捷方式"; System.IO.File.Delete( System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), installInfo.Name + ".lnk")); subStep++; } Progress = 1; Msg = $"{progressMsg()} 完成"; return true; }, cancellation.Token); } /// <summary> /// 中断操作 /// </summary> public void Abort() { if (cancellation != null && !cancellation.IsCancellationRequested) cancellation.Cancel(); } } public class UpdateScriptInfo { public string Dll; public string TypeFullName; public IUpdateScript updateScript; } }