[Quicker] 软件管家 - 源码归档

动作:软件管家

基于 WinGet 的开源软件管理工具,支持一键搜索、安装、卸载、更新软件。类似 360 软件管家的开源替代方案。

更新时间:2025-12-27 22:20
原始文件:SoftwareManager.cs

核心代码

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using Microsoft.Win32;
using Quicker.Public;

public static void Exec(IStepContext context)
{
    // 【首发自检】:确保用户环境具备 Winget
    if (!WingetLogic.IsWingetInstalled())
    {
        var res = MessageBox.Show(
            "【环境自检失败】\n\n您的系统似乎尚未安装或启用微软的 Winget 程序包管理器。\n\n" +
            "Win10/11 用户通常只需在应用商店中更新「应用安装程序」即可解决。\n\n" +
            "是否立即打开浏览器查看安装指南?", 
            "软件管家 Pro", 
            MessageBoxButtons.YesNo, 
            MessageBoxIcon.Warning);
        if (res == DialogResult.Yes) {
            try { Process.Start("https://github.com/microsoft/winget-cli/releases"); } catch { }
        }
        return;
    }

    using (var form = new SoftwareManagerForm())
    {
        form.ShowDialog();
    }
}

public class SoftwareItem
{
    public string Name { get; set; }
    public string Id { get; set; }
    public string Version { get; set; }
    public string Source { get; set; }
    public bool IsInstalled { get; set; }
    public string IconKey { get; set; }
}

public class SoftwareManagerForm : Form
{
    private ListView _lvSoftware;
    private TextBox _txtSearch;
    private RichTextBox _txtLog;
    private Label _lblStatus;
    private string _currentMode = "Search";
    private Panel _pnlLoading;
    private List<Button> _menuButtons = new List<Button>();
    private ImageList _imgList;
    
    private Dictionary<string, List<SoftwareItem>> _dataCache = new Dictionary<string, List<SoftwareItem>>();
    private Dictionary<string, string> _regIconCache = null;
    private HashSet<string> _installedIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

    public SoftwareManagerForm()
    {
        this.Text = "软件管家 Pro (2025 发行版)";
        this.Size = new Size(1150, 850);
        this.StartPosition = FormStartPosition.CenterScreen;
        this.Font = new Font("Microsoft YaHei UI", 10F);
        this.BackColor = Color.FromArgb(248, 250, 252);
        
        _imgList = new ImageList { ImageSize = new Size(32, 32), ColorDepth = ColorDepth.Depth32Bit };
        _imgList.Images.Add("default", SystemIcons.Application);

        InitializeLayout();
        UpdateMenuHighlight();
        LoadWindowIcon();
        this.Load += (s, e) => {
            Log("环境自检通过。Winget 引擎已就绪。");
            RefreshData(false);
        };
    }

    private void LoadWindowIcon() {
        Task.Run(() => {
            try {
                string url = "https://files.getquicker.net/_icons/45801F6F94EAB26495C9803A3F290DC2224C0092.png";
                using (WebClient wc = new WebClient()) {
                    byte[] data = wc.DownloadData(url);
                    using (MemoryStream ms = new MemoryStream(data)) {
                        Bitmap bitmap = new Bitmap(ms);
                        this.Invoke(new Action(() => {
                            try {
                                IntPtr hIcon = bitmap.GetHicon();
                                this.Icon = Icon.FromHandle(hIcon);
                            } catch { }
                        }));
                    }
                }
            } catch { }
        });
    }

    private void InitializeLayout()
    {
        Panel pnlLeft = new Panel { Dock = DockStyle.Left, Width = 180, BackColor = Color.FromArgb(15, 23, 42), Padding = new Padding(0, 20, 0, 0) };
        Label lblBrand = new Label { Text = "软件管家", Font = new Font("Microsoft YaHei UI", 16F, FontStyle.Bold), ForeColor = Color.White, Dock = DockStyle.Top, Height = 60, TextAlign = ContentAlignment.MiddleCenter };
        pnlLeft.Controls.Add(lblBrand);
        
        _menuButtons.Add(CreateMenuBtn(" 全部软件", "Search", 85, pnlLeft));
        _menuButtons.Add(CreateMenuBtn("✅ 已安装", "Installed", 140, pnlLeft));
        _menuButtons.Add(CreateMenuBtn("✨ 有更新", "Updates", 195, pnlLeft));

        Panel pnlHeader = new Panel { Dock = DockStyle.Top, Height = 90, BackColor = Color.White };
        _txtSearch = new TextBox { Location = new Point(25, 25), Width = 400, Font = new Font("Microsoft YaHei UI", 12F) };
        _txtSearch.KeyDown += (s, e) => { if (e.KeyCode == Keys.Enter) RefreshData(true); };
        
        Button btnSearch = new Button { Text = " 检索", Location = new Point(435, 22), Size = new Size(100, 42), BackColor = Color.FromArgb(37, 99, 235), ForeColor = Color.White, FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand };
        btnSearch.FlatAppearance.BorderSize = 0;
        btnSearch.Click += (s, e) => RefreshData(true);

        Button btnRefresh = new Button { Text = " 刷新", Location = new Point(545, 22), Size = new Size(100, 42), BackColor = Color.FromArgb(241, 245, 249), FlatStyle = FlatStyle.Flat, Cursor = Cursors.Hand };
        btnRefresh.FlatAppearance.BorderSize = 0;
        btnRefresh.Click += (s, e) => { RefreshData(true); };
        pnlHeader.Controls.AddRange(new Control[] { _txtSearch, btnSearch, btnRefresh });

        _txtLog = new RichTextBox { Dock = DockStyle.Bottom, Height = 180, BackColor = Color.FromArgb(30, 30, 30), ForeColor = Color.Gainsboro, Font = new Font("Consolas", 10F), BorderStyle = BorderStyle.None, ReadOnly = true };
        _lblStatus = new Label { Dock = DockStyle.Bottom, Height = 35, Text = " 正在就绪...", TextAlign = ContentAlignment.MiddleLeft, BackColor = Color.FromArgb(241, 245, 249), Font = new Font("微软雅黑", 9F) };

        _lvSoftware = new ListView { Dock = DockStyle.Fill, View = View.Details, FullRowSelect = true, BackColor = Color.White, BorderStyle = BorderStyle.None, Font = new Font("微软雅黑", 10F), SmallImageList = _imgList };
        _lvSoftware.Columns.Add("软件名称", 320);
        _lvSoftware.Columns.Add("应用 ID", 340);
        _lvSoftware.Columns.Add("当前版本", 130);
        _lvSoftware.Columns.Add("来源", 150);
        _lvSoftware.MouseDoubleClick += (s, e) => RunAction("install");

        ContextMenuStrip cms = new ContextMenuStrip();
        cms.Items.Add(" 一键静默安装/更新", null, (s, e) => RunAction("install"));
        cms.Items.Add(new ToolStripSeparator());
        cms.Items.Add("️ 直接卸载", null, (s, e) => RunAction("uninstall"));
        cms.Items.Add("⚙️ 系统控制面板卸载", null, (s, e) => { try { Process.Start("appwiz.cpl"); } catch { } });
        _lvSoftware.ContextMenuStrip = cms;

        _pnlLoading = new Panel { Dock = DockStyle.Fill, BackColor = Color.FromArgb(200, 255, 255, 255), Visible = false };
        var lblLoading = new Label { Text = "正在同步 Winget 数据...", Size = new Size(300, 50), TextAlign = ContentAlignment.MiddleCenter, Font = new Font("微软雅黑", 12F, FontStyle.Bold), ForeColor = Color.FromArgb(37, 99, 235) };
        _pnlLoading.SizeChanged += (s, e) => lblLoading.Location = new Point((_pnlLoading.Width - lblLoading.Width) / 2, (_pnlLoading.Height - lblLoading.Height) / 2);
        _pnlLoading.Controls.Add(lblLoading);

        this.Controls.Add(_pnlLoading);
        this.Controls.Add(_lvSoftware);
        this.Controls.Add(pnlHeader);
        this.Controls.Add(pnlLeft);
        this.Controls.Add(_lblStatus);
        this.Controls.Add(_txtLog);
        _pnlLoading.BringToFront();
    }

    private Button CreateMenuBtn(string text, string mode, int y, Panel parent)
    {
        Button btn = new Button { Text = text, Tag = mode, Location = new Point(0, y), Size = new Size(180, 50), BackColor = Color.Transparent, ForeColor = Color.FromArgb(148, 163, 184), FlatStyle = FlatStyle.Flat, TextAlign = ContentAlignment.MiddleLeft, Padding = new Padding(25, 0, 0, 0), Cursor = Cursors.Hand };
        btn.FlatAppearance.BorderSize = 0;
        btn.Click += (s, e) => { _currentMode = (string)btn.Tag; UpdateMenuHighlight(); RefreshData(false); };
        parent.Controls.Add(btn);
        return btn;
    }

    private void UpdateMenuHighlight() {
        foreach (var b in _menuButtons) {
            bool isActive = (string)b.Tag == _currentMode;
            b.BackColor = isActive ? Color.FromArgb(30, 41, 59) : Color.Transparent;
            b.ForeColor = isActive ? Color.White : Color.FromArgb(148, 163, 184);
        }
    }

    private void Log(string msg) {
        if (this.InvokeRequired) { this.Invoke(new Action<string>(Log), msg); return; }
        _txtLog.AppendText(string.Format("[{0:HH:mm:ss}] {1}\n", DateTime.Now, msg));
        _txtLog.ScrollToCaret();
    }

    private async void RefreshData(bool force) {
        string q = _txtSearch.Text;
        string modeKey = string.Format("{0}_{1}", _currentMode, q);
        if (!force && _dataCache.ContainsKey(modeKey)) { DisplayResults(_dataCache[modeKey]); return; }
        _pnlLoading.Visible = true;
        _lvSoftware.Items.Clear();

        var list = await Task.Run(() => {
            if (_regIconCache == null || force) _regIconCache = ScanRegistryIcons();
            var instItems = WingetLogic.FetchList("list --accept-source-agreements");
            _installedIds = new HashSet<string>(instItems.Select(i => i.Id), StringComparer.OrdinalIgnoreCase);

            string args = _currentMode == "Search" ? (string.IsNullOrEmpty(q) ? "search \"\"" : string.Format("search \"{0}\"", q)) : (_currentMode == "Installed" ? "list" : "upgrade");
            var results = WingetLogic.FetchList(args + " --accept-source-agreements");
            if (!string.IsNullOrEmpty(q) && _currentMode != "Search") {
                results = results.Where(i => i.Name.IndexOf(q, StringComparison.OrdinalIgnoreCase) >= 0 || i.Id.IndexOf(q, StringComparison.OrdinalIgnoreCase) >= 0).ToList();
            }
            foreach(var item in results) {
                item.IsInstalled = _installedIds.Contains(item.Id);
                if (_regIconCache.ContainsKey(item.Id)) item.IconKey = _regIconCache[item.Id];
                else if (_regIconCache.ContainsKey(item.Name)) item.IconKey = _regIconCache[item.Name];
            }
            return results;
        });

        _dataCache[modeKey] = list;
        DisplayResults(list);
        _pnlLoading.Visible = false;
    }

    private void DisplayResults(List<SoftwareItem> list) {
        _lvSoftware.BeginUpdate();
        _lvSoftware.Items.Clear();
        foreach (var item in list) {
            var lvi = new ListViewItem(item.Name);
            lvi.SubItems.AddRange(new[] { item.Id, item.Version, (item.IsInstalled ? "✅ " : "") + (item.Source ?? "-") });
            lvi.Tag = item;
            if (!string.IsNullOrEmpty(item.IconKey) && File.Exists(item.IconKey)) {
                if (!_imgList.Images.ContainsKey(item.IconKey)) {
                    try { using (var i = Icon.ExtractAssociatedIcon(item.IconKey)) _imgList.Images.Add(item.IconKey, i); } catch { }
                }
                lvi.ImageKey = item.IconKey;
            } else { lvi.ImageIndex = 0; }
            if (item.IsInstalled) lvi.BackColor = Color.FromArgb(242, 245, 250);
            _lvSoftware.Items.Add(lvi);
        }
        _lvSoftware.EndUpdate();
        _lblStatus.Text = string.Format(" 共找到 {0} 个项。", list.Count);
    }

    private Dictionary<string, string> ScanRegistryIcons() {
        var icons = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        string[] roots = { @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" };
        foreach (var rp in roots) {
            using (var rk = Registry.LocalMachine.OpenSubKey(rp)) {
                if (rk == null) continue;
                foreach (var sn in rk.GetSubKeyNames()) {
                    using (var sk = rk.OpenSubKey(sn)) {
                        string dn = sk.GetValue("DisplayName") as string;
                        string di = sk.GetValue("DisplayIcon") as string;
                        if (string.IsNullOrEmpty(di)) continue;
                        string p = di.Split(',')[0].Trim('\"');
                        if (p.EndsWith(",0") || p.EndsWith(",1")) p = p.Substring(0, p.Length - 2); 
                        if (File.Exists(p)) { if (!string.IsNullOrEmpty(dn)) icons[dn] = p; icons[sn] = p; }
                    }
                }
            }
        }
        return icons;
    }

    private async void RunAction(string op) {
        if (_lvSoftware.SelectedItems.Count == 0) return;
        var item = (SoftwareItem)_lvSoftware.SelectedItems[0].Tag;
        string pureId = Regex.Match(item.Id, @"^[^\s]+").Value;
        
        StringBuilder sb = new StringBuilder();
        sb.Append(string.Format("{0} --id \"{1}\" --exact", op, pureId));
        if (!string.IsNullOrEmpty(item.Source)) {
            string src = item.Source.ToLower();
            if (src.Contains("winget")) sb.Append(" --source winget");
            else if (src.Contains("msstore")) sb.Append(" --source msstore");
        }
        if (op != "uninstall") sb.Append(" --accept-package-agreements");
        sb.Append(" --accept-source-agreements --disable-interactivity --silent --force");
        
        Log(string.Format(">>> 正在执行 {0}: \"{1}\"...", op, item.Name));
        bool hasError = false;
        await WingetLogic.RunStreamed(sb.ToString(), (l) => {
            Log("  " + l);
            if (l.Contains("找不到与输入条件匹配")) hasError = true;
        });

        if (op == "uninstall" && hasError) {
            var res = MessageBox.Show("WinGet 无法管理该软件的卸载。\n是否打开系统的控制面板尝试手动移除?", "建议操作", MessageBoxButtons.YesNo, MessageBoxIcon.Information);
            if (res == DialogResult.Yes) { try { Process.Start("appwiz.cpl"); } catch { } }
        }
        Log("<<< 指令结束。");
        _dataCache.Clear();
        RefreshData(true);
    }
}

public static class WingetLogic
{
    private static ProcessStartInfo GetPsi(string a) {
        return new ProcessStartInfo("winget", a) { UseShellExecute = false, RedirectStandardOutput = true, StandardOutputEncoding = Encoding.UTF8, CreateNoWindow = true };
    }
    
    public static bool IsWingetInstalled() {
        try {
            using (var p = Process.Start(new ProcessStartInfo("winget", "--version") { UseShellExecute = false, CreateNoWindow = true })) {
                p.WaitForExit();
                return p.ExitCode == 0;
            }
        } catch { return false; }
    }

    public static async Task RunStreamed(string a, Action<string> o) {
        try { using (var p = Process.Start(GetPsi(a))) { using (var r = p.StandardOutput) { string l; while ((l = await r.ReadLineAsync()) != null) o(l); } p.WaitForExit(); } }
        catch (Exception ex) { o("底层通讯异常: " + ex.Message); }
    }
    
    public static List<SoftwareItem> FetchList(string a) {
        var res = new List<SoftwareItem>();
        try {
            string raw; using (var p = Process.Start(GetPsi(a))) raw = p.StandardOutput.ReadToEnd();
            if (string.IsNullOrEmpty(raw)) return res;
            var lines = raw.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            int idx = lines.FindIndex(l => l.Contains("---"));
            if (idx < 1) return res;
            
            string header = lines[idx - 1];
            int idStart = Math.Max(0, header.IndexOf("ID"));
            int verStart = Math.Max(idStart + 5, header.IndexOf("版本") > 0 ? header.IndexOf("版本") : (header.IndexOf("Version") > 0 ? header.IndexOf("Version") : idStart + 30));
            int srcStart = Math.Max(verStart + 5, header.IndexOf("源") > 0 ? header.IndexOf("源") : (header.IndexOf("Source") > 0 ? header.IndexOf("Source") : header.Length - 10));

            for (int i = idx + 1; i < lines.Count; i++) {
                string line = lines[i];
                if (line.Length <= idStart) continue;
                var it = new SoftwareItem();
                it.Name = line.Substring(0, Math.Min(line.Length, idStart)).Trim();
                string rawIdPart = (line.Length > idStart) ? line.Substring(idStart, Math.Min(line.Length - idStart, verStart - idStart)).Trim() : "";
                it.Id = Regex.Match(rawIdPart, @"^[^\s]+").Value;
                it.Version = (line.Length > verStart) ? line.Substring(verStart, Math.Min(line.Length - verStart, srcStart - verStart)).Trim() : "";
                it.Source = (line.Length > srcStart) ? line.Substring(srcStart).Trim() : "";
                if (!string.IsNullOrEmpty(it.Id)) res.Add(it);
            }
        } catch { }
        return res;
    }
}

动作配置 (JSON)

{
  "ActionId": "63f52428-d530-4dc6-8265-c24b5cf41410",
  "Title": "软件管家",
  "Description": "基于 WinGet 的开源软件管理工具,支持一键搜索、安装、卸载、更新软件。类似 360 软件管家的开源替代方案。",
  "Icon": "https://files.getquicker.net/_icons/45801F6F94EAB26495C9803A3F290DC2224C0092.png",
  "Variables": []
}

使用方法

  1. 确保您已安装 动作构建 (Action Builder) 动作。
  2. 将本文提供的 .cs.json 文件下载到同一目录
  3. 选中 .json 文件,运行「动作构建」即可生成并安装动作。
posted @ 2025-12-27 22:20  luoluoluo22  阅读(0)  评论(0)    收藏  举报