AIGC标识 摸鱼笔记[10]-windows开机软件延迟自启动工具

摘要

在windows10上使用DelayedStartupTool管理开机自启动软件, 为每个程序设置自定义延迟启动时间, 避免开机时大量程序同时启动导致系统卡顿.

声明

本文内容由 AI 辅助生成, 已经人工审核和编辑。

简介

手动设置开机自启动及延迟启动

Windows 开机自启动的常见方式有以下几种:

方式 位置 特点
注册表 Run 键 HKCU\Software\Microsoft\Windows\CurrentVersion\Run 用户级, 最常见
注册表 Run 键 HKLM\Software\Microsoft\Windows\CurrentVersion\Run 系统级, 需要管理员权限
启动文件夹 %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup 用户级, 直观可见
启动文件夹 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp 系统级, 需要管理员权限
任务计划程序 Task Scheduler 最灵活, 支持延迟触发
服务 Windows Services 后台运行, 无界面

延迟启动的核心思路是: 不让所有程序在登录瞬间同时启动, 而是按优先级分批启动, 减轻 CPU/磁盘 IO 峰值压力.

手动实现延迟启动的方法:

方法一: 任务计划程序 + 延迟触发器

schtasks /create /tn "MyApp" /tr "C:\Path\To\MyApp.exe" /sc onlogon /delay 0000:30 /rl HIGHEST /f

方法二: 启动文件夹 + 批处理倒计时

@echo off
echo 等待 10 秒后启动 MyApp...
timeout /t 10 /nobreak >nul
start "" "C:\Path\To\MyApp.exe"

方法三: 使用第三方工具 — 本文介绍的 DelayedStartupTool 就是专门为此设计的开源工具.

DelayedStartupTool简介

[https://github.com/zhang00956/DelayedStartupTool]
[https://github.com/syx594/DelayedStartupToolPro]

DelayedStartupTool 是一款 Windows 平台的开机启动项管理工具, 支持为每个启动项设置独立的延迟时间, 按顺序分批启动, 有效避免开机风暴.

DelayedStartupToolPro

📢 主要功能:

  • 支持 图形界面(GUI)命令行(BAT)后台隐藏(Hidden) 三种启动模式
  • 每个启动项可设置 独立延迟秒数 (0~999 秒)
  • 支持 拖拽排序, 调整启动顺序
  • 支持为 EXE 添加 启动参数
  • 支持 备注 标识每个启动项
  • 系统启动项扫描: 扫描注册表 Run 键、启动文件夹、计划任务
  • 一键优化: 根据软件类型自动分配最佳延迟档位
  • 导入系统启动项: 将系统启动项迁移到本工具管理
  • 美观的 WebView2 启动画面, 实时显示启动进度
  • UAC 检测与提示, 自动处理权限问题

系统要求:

  • Windows 10 / 11 (Pro 版)
  • Windows 7 / 10 (原版, .NET Framework 4.7.2)
  • Pro 版需要 Microsoft Edge WebView2 Runtime

安装方式:

从 GitHub Releases 页面下载编译好的可执行文件, 解压即用, 无需安装.

版本说明:

版本 技术栈 特点
原版 V2.1.2 WinForms + .NET Framework 4.7.2 轻量, 兼容 Win7, 单文件
Pro版 V2.0.2 WPF + WebView2 + .NET 8 现代化 UI, 系统扫描, 一键优化
典藏版 原版编译产物 工程收藏, 绿色便携

开源协议:

采用 MIT License, 欢迎 Fork 和贡献代码.

工程

软件各个版本及github链接

[https://github.com/syx594/DelayedStartupToolPro]
[https://github.com/zhang00956/DelayedStartupTool]
DelayedStartupToolPro

  1. 原版 — WinForms + .NET Framework 4.7.2, 轻量便携, 适合老系统
  2. Pro — WPF + WebView2 + .NET 8, 现代化界面, 功能更全面
  3. 典藏版 — 原版编译产物收藏, 绿色单文件

软件原理

DelayedStartupTool 的核心技术架构围绕 Windows 启动项管理进程延迟调度 展开. 以下是其五大核心原理:

1. 三种启动模式 — 互斥切换

模式 启动方式 适用场景
GUI 计划任务 + /launch 参数 显示美观进度条, 可视化启动过程
BAT 启动文件夹 + .bat 批处理 传统命令行窗口, 兼容性好
Hidden 计划任务 + /launch /hidden 参数 完全静默, 无界面

三种模式互斥, 切换时自动清理旧模式的启动项:

// 互锁: 先清理所有旧的启动方式, 保证三种模式互斥
if (File.Exists(startupBatPath))
    try { File.Delete(startupBatPath); } catch { }
if (File.Exists(startupShortcutPath))
    try { File.Delete(startupShortcutPath); } catch { }
DeleteScheduledTask();

2. 开机自启动 — 计划任务 / 批处理

BAT 模式: 生成 DelayedStartup.bat 到 Windows 启动文件夹, 批处理内部用 for /l 循环实现倒计时:

sb.AppendLine($"echo 将在 {item.Delay} 秒后启动...");
sb.AppendLine($"for /l %%t in ({item.Delay},-1,1) do (");
sb.AppendLine("    echo 剩余时间: %%t 秒...");
sb.AppendLine("    timeout /t 1 >nul");
sb.AppendLine(")");
sb.AppendLine($"start \"\" \"{expandedPath}\"{args}");

GUI/Hidden 模式: 调用 schtasks 创建计划任务, /rl HIGHEST 以管理员权限运行:

var psi = new ProcessStartInfo("schtasks")
{
    Arguments = $"/create /tn \"{TaskName}\" /tr \"\\\"{exePath}\\\" {arguments}\" /sc onlogon /rl HIGHEST /f",
    CreateNoWindow = true,
    UseShellExecute = false,
    RedirectStandardOutput = true,
    RedirectStandardError = true
};

3. 延迟调度 — DispatcherTimer 链式触发

Pro 版使用 WPF 的 DispatcherTimer 实现精确的延迟调度, 每个启动项按顺序触发:

void StartNext()
{
    if (currentIndex >= items.Count) { Environment.Exit(0); return; }
    var item = items[currentIndex];
    if (item.Delay > 0)
    {
        var timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromSeconds(item.Delay);
        timer.Tick += (s, e) =>
        {
            timer.Stop();
            LaunchItemHidden(item);
            currentIndex++;
            StartNext();  // 递归触发下一个
        };
        timer.Start();
    }
    else
    {
        LaunchItemHidden(item);
        currentIndex++;
        // 短暂间隔避免同时启动
        var timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(500);
        timer.Tick += (s, e) => { timer.Stop(); StartNext(); };
        timer.Start();
    }
}

4. 系统启动项扫描 — 注册表 + 文件夹 + 计划任务

Pro 版可扫描系统所有启动项, 支持一键导入管理:

private void HandleScanSystem()
{
    systemItems.Clear();
    ScanRegistryRunKeys();      // 注册表 Run/RunOnce
    ScanStartupFolder();         // 启动文件夹
    ScanTaskScheduler();         // 计划任务 (PowerShell)
    // ... 标记已管理项, 保存结果
}

扫描结果保存到 scan_results.json, 支持区分来源和禁用状态.

5. 一键优化 — 软件类型分类算法

基于 品牌规则 + 文件后缀 的智能分类, 自动分配延迟档位:

档位 延迟 类别 示例
0秒 立即 系统安全/驱动 杀毒软件、驱动服务
2秒 尽快 系统辅助 输入法、托盘工具
3秒 较早 网络工具 VPN、代理、网盘同步
5秒 适中 通讯社交 微信、QQ、钉钉
8秒 稍后 浏览器/办公 Chrome、WPS、Office
10秒 较晚 工具/播放器 压缩工具、编辑器
15秒 最后 大型软件 IDE、虚拟机、游戏平台
private static readonly (string category, int delay, string reason, string[] keywords)[] BrandRules = new[]
{
    ("office", 8, "浏览器/办公软件可延迟启动", new[] { "chrome","edge","firefox","wps","office",... }),
    ("comm", 5, "通讯社交软件无需过早启动", new[] { "wechat","qq","dingtalk","TG",... }),
    ("heavy", 15, "大型软件启动较慢", new[] { "visual studio","eclipse","steam","vmware",... }),
    ("system", 0, "系统安全软件需立即启动", new[] { "360","火绒","defender","driver",... }),
    // ...
};

核心代码

原版入口 — Mutex 单实例 + 命令行解析 (Program.cs)

private static Mutex mutex = new Mutex(true, "{C8A9E8E0-1B4A-4E4A-8D1E-9F6B3F4E5D7C}");

[STAThread]
private static void Main(string[] args)
{
    // 全局异常兜底
    Application.ThreadException += (s, e) => MessageBox.Show(e.Exception.Message);
    AppDomain.CurrentDomain.UnhandledException += (s, e) => MessageBox.Show("严重错误");

    bool isLaunch = args.Length > 0 && args[0].Equals("/launch", StringComparison.OrdinalIgnoreCase);
    bool isHidden = args.Length > 1 && args[1].Equals("/hidden", StringComparison.OrdinalIgnoreCase);

    if (isLaunch)
        Application.Run(new SplashForm(isHidden));  // 启动模式, 不检查互斥体
    else if (mutex.WaitOne(TimeSpan.Zero))
        Application.Run(new Form1());               // 主界面, 单实例
    else
        MessageBox.Show("程序已在运行中!");
}

原版启动画面 — WinForms 原生控件 (SplashForm.cs)

private void StartNextItem()
{
    if (currentIndex >= startupItems.Count) { FinishSplash(); return; }
    Show();
    StartupItem item = startupItems[currentIndex];
    lblTitle.Text = $"DelayedStartupTool... ({currentIndex + 1}/{startupItems.Count})";
    lblAppName.Text = "正在启动: " + displayName;
    remainingSeconds = item.Delay;
    progressBarFill.Width = progressBarBack.Width;

    if (item.Delay > 0)
    {
        lblTime.Text = $"{item.Delay}s";
        countdownTimer = new Timer { Interval = 1000 };
        countdownTimer.Tick += CountdownTimer_Tick;
        countdownTimer.Start();
    }
    else
        LaunchItem(item);  // 无延迟, 立即启动
}

private void CountdownTimer_Tick(object sender, EventArgs e)
{
    remainingSeconds--;
    if (remainingSeconds > 0)
    {
        lblTime.Text = $"{remainingSeconds}s";
        progressBarFill.Width = (int)(progressBarBack.Width * ((double)remainingSeconds / startupItems[currentIndex].Delay));
    }
    else
    {
        countdownTimer.Stop();
        LaunchItem(startupItems[currentIndex]);
    }
}

原版 BAT 生成 — 批处理倒计时 (Form1.cs)

private string GenerateBatContent()
{
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("@echo off");
    sb.AppendLine("chcp 65001 >nul 2>&1");
    sb.AppendLine("title 延迟启动工具 - Delayed Startup Tool");
    foreach (StartupItem item in startupItems)
    {
        string expandedPath = Environment.ExpandEnvironmentVariables(item.FilePath);
        sb.AppendLine($":: 准备启动: {displayName}");
        if (item.Delay > 0)
        {
            sb.AppendLine($"echo 将在 {item.Delay} 秒后启动...");
            sb.AppendLine($"for /l %%t in ({item.Delay},-1,1) do (");
            sb.AppendLine("    echo 剩余时间: %%t 秒...");
            sb.AppendLine("    timeout /t 1 >nul");
            sb.AppendLine(")");
        }
        sb.AppendLine($"cd /d \"{directory}\"");
        sb.AppendLine($"start \"\" \"{expandedPath}\"{args}");
    }
    sb.AppendLine("echo 所有任务已完成。");
    sb.AppendLine("exit");
    return sb.ToString();
}

Pro版 WebView2 启动画面 (App.xaml.cs)

public class SplashForm : Window
{
    private WebView2 webView;
    private DispatcherTimer countdownTimer;
    private List<StartupItemForSplash> startupItems;

    private async void SplashForm_Load(object sender, RoutedEventArgs e)
    {
        this.Left = (SystemParameters.PrimaryScreenWidth - this.Width) / 2;
        this.Top = 15;
        startupItems = LoadStartupItems();
        if (startupItems == null || startupItems.Count == 0) { this.Close(); return; }
        await InitializeWebView();
    }

    private async Task InitializeWebView()
    {
        await webView.EnsureCoreWebView2Async();
        webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
        string uiPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "datashui", "startup-ui.html");
        webView.Source = new Uri($"file:///{uiPath.Replace("\\", "/")}");
    }

    private void StartNextItem()
    {
        if (currentIndex >= startupItems.Count) { FadeOutAndClose(); return; }
        this.Show();
        var item = startupItems[currentIndex];
        // 发送消息到 WebView2 更新 UI
        SendMessageToWebView(new { action = "update", current = currentIndex + 1, total = startupItems.Count, fileName = displayName, delay = item.Delay });
        remainingSeconds = item.Delay;
        if (item.Delay > 0)
        {
            countdownTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
            countdownTimer.Tick += CountdownTimer_Tick;
            countdownTimer.Start();
        }
        else { LaunchItem(item); currentIndex++; StartNextAfterDelay(); }
    }
}

Pro版 WebView2 消息桥接 (MainWindow.xaml.cs)

private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
    string json = e.WebMessageAsJson;
    var message = JsonSerializer.Deserialize<JsonElement>(json);
    string action = message.TryGetProperty("action", out var el) ? el.GetString() : "";

    switch (action)
    {
        case "loadConfig":    SendConfigLoaded(); break;
        case "addFiles":      HandleAddFiles(message); break;
        case "deleteItem":    HandleDeleteItem(message); break;
        case "moveItem":      HandleMoveItem(message); break;
        case "updateDelay":   HandleUpdateDelay(message); break;
        case "saveConfig":    HandleSaveConfig(message); break;
        case "scanSystem":    HandleScanSystem(); break;
        case "applyOptimization": HandleApplyOptimization(message); break;
        case "toggleStartup": HandleToggleStartup(message); break;
        case "testStartup":   HandleTestStartup(); break;
        case "toggleTheme":   HandleToggleTheme(); break;
        // ... 30+ 种消息
    }
}

Pro版 系统启动项扫描 — 注册表 Run 键

private void ScanRegistryRunKeys()
{
    string[] keys = {
        @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run",
        @"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run",
        @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce",
        @"HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\RunOnce"
    };
    foreach (string key in keys)
    {
        using var regKey = rootKey.OpenSubKey(subKey);
        if (regKey != null)
        {
            foreach (string name in regKey.GetValueNames())
            {
                string value = regKey.GetValue(name)?.ToString() ?? "";
                // 解析路径和参数
                var (path, args) = ParsePathAndArgs(value);
                systemItems.Add(new SystemStartupEntry { Name = name, FilePath = path, Source = "registry", ... });
            }
        }
    }
}

Pro版 一键优化算法 — 智能分类

private (string category, int delay, string reason) ClassifyItem(StartupItem item)
{
    string resolved = ResolveEffectivePath(item.FilePath);
    string name = Path.GetFileName(resolved).ToLower();
    string dir = (Path.GetDirectoryName(resolved) ?? "").ToLower();
    string hay = name + " " + dir + " " + item.FilePath.ToLower();

    // 1) 系统核心目录一律立即启动
    if (dir.Contains("system32") || dir.Contains("windowsapps"))
        return ("system", 0, "系统核心目录组件需立即启动");

    // 2) 品牌/软件签名规则匹配
    foreach (var rule in BrandRules)
        foreach (var kw in rule.keywords)
            if (hay.Contains(kw) && IsWordMatch(hay, kw))
                return (rule.category, rule.delay, rule.reason);

    // 3) 文件后缀兜底
    string ext = Path.GetExtension(resolved).ToLower();
    return ext switch
    {
        ".bat" or ".cmd" or ".ps1" => ("tool", 5, "脚本/批处理启动器可稍后启动"),
        ".cpl" or ".msc" or ".scr" => ("system", 0, "系统控制组件需立即启动"),
        _ => ("tool", 10, "常规应用程序可延迟启动")
    };
}

Pro版 COM IDropTarget 文件拖放 (MainWindow.xaml.cs)

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[Guid("D5BF8E70-7E8A-4D3C-9B1F-A6E4D83C5F20")]
private class FileDropTarget : IDropTarget
{
    public int Drop(IntPtr pDataObj, uint grfKeyState, WinPoint pt, ref uint pdwEffect)
    {
        var dataObj = (IComDataObject)Marshal.GetObjectForIUnknown(pDataObj);
        var files = ExtractFilePaths(dataObj);  // 提取 HDROP 文件路径
        if (files.Count > 0)
            _owner.Dispatcher.BeginInvoke(new Action(() => _owner.AddDroppedFiles(files)));
        pdwEffect = DROPEFFECT_COPY;
        return 0;
    }
}

// 注册到 WebView2 子窗口
private void RegisterCustomDropTarget()
{
    EnumChildWindows(mainWindowHandle, (childHwnd, _) =>
    {
        RevokeDragDrop(childHwnd);
        DragAcceptFiles(childHwnd, true);
        RegisterDragDrop(childHwnd, _fileDropTarget);
        return true;
    }, IntPtr.Zero);
}

源码

原版工程结构 (WinForms + .NET Framework 4.7.2)

DelayedStartupTool/
├── DelayedStartupTool.csproj    # 项目文件
├── Program.cs                   # 入口, Mutex单实例, 命令行解析
├── Form1.cs                     # 主窗体 (1847行), 启动项管理
├── Form1.Designer.cs            # 设计器代码
├── SplashForm.cs                # 启动画面, 倒计时进度
├── StartupItem.cs               # 数据模型 (FilePath/Delay/Comment/Arguments)
├── JsonHelper.cs                # 手写JSON序列化 (无第三方依赖)
├── DoubleBufferedListBox.cs     # 自定义双缓冲列表框, 拖拽排序
├── ItemMovedEventArgs.cs        # 拖拽移动事件参数
├── Properties/
│   ├── AssemblyInfo.cs
│   └── Resources.cs             # 资源文件
└── app.ico                      # 程序图标

Pro版工程结构 (WPF + WebView2 + .NET 8)

DelayedStartupToolPro-main/
├── DelayedStartupTool.Wpf.csproj  # 项目文件 (.NET 8, WPF)
├── App.xaml.cs                    # 入口, 三层全局异常, Mutex, SplashForm
├── MainWindow.xaml.cs             # 主逻辑 (2890行), WebView2, 消息桥接
├── MainWindow.xaml                # 主窗口 XAML
├── StyledMessageBox.xaml.cs       # 自定义消息框
├── AssemblyInfo.cs                # 程序集信息
├── app.manifest                   # requireAdministrator
├── sign.ps1                       # 自动数字签名脚本
└── datashui/
    ├── index.html                 # 主界面 HTML (3154行)
    ├── startup-ui.html            # 启动画面 HTML (285行)
    └── max.ico                    # 程序图标

配置文件格式

config.json — 启动项配置:

[
  {
    "filePath": "C:\\Program Files\\MyApp\\MyApp.exe",
    "delay": 5,
    "comment": "我的应用",
    "arguments": "-minimized",
    "enabled": true
  }
]

settings.json — 用户设置:

{ "Mode": "gui", "IsDark": false }

scan_results.json — 系统扫描结果:

{
  "scanTime": "2026-09-13 12:00:00",
  "entries": [
    { "name": "MyApp", "filePath": "...", "source": "registry", "disabled": false }
  ]
}

optimization_backup.json — 优化前备份:

{ "C:\\Path\\To\\App.exe": 0 }

关键设计决策

设计点 原版方案 Pro版方案
UI 框架 WinForms 原生控件 WPF + WebView2 HTML
启动画面 WinForms Form + Label WebView2 + HTML/CSS 动画
JSON 序列化 手写 JsonHelper (无依赖) System.Text.Json
文件拖放 WinForms DragDrop COM IDropTarget + WM_DROPFILES
计划任务 WScript.Shell 快捷方式 schtasks /rl HIGHEST
全局异常 ThreadException + UnhandledException Dispatcher + TaskScheduler + AppDomain 三层
数字签名 自动 Authenticode 签名 (sign.ps1)
posted @ 2026-09-13 12:02  qsBye  阅读(11)  评论(0)    收藏  举报