[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": []
}
使用方法
- 确保您已安装 动作构建 (Action Builder) 动作。
- 将本文提供的
.cs和.json文件下载到同一目录。 - 选中
.json文件,运行「动作构建」即可生成并安装动作。
浙公网安备 33010602011771号