吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2547|回复: 24
收起左侧

[原创工具] C# 极简小工具:按文件夹一键启动/隐藏或结束同批 exe/lnk

  [复制链接]
星峰 发表于 2026-5-15 09:17
本帖最后由 星峰 于 2026-5-20 15:43 编辑

C# 极简小工具:按文件夹一键启动/隐藏或结束同批 exe/lnk

工具名: WorkLauncher(WorkLauncher.exe


做什么

指定文件夹的根目录(不扫子文件夹)里查找 .exe.lnk

  • 默认:依次启动这些项。对于快捷方式属性设为“最小化运行”(WindowStyle=7)的程序,会自动启动并强制隐藏其窗口(防止部分程序启动后又自己显示出来)。
  • close:按规则结束对应进程(无界面、无成功提示)。

条件不满足时(目录不存在、没有 exe/lnk、参数非法等)会静默退出

环境

  • .NET Framework 2.0+(Win7/10/11 系统自带,免安装)
  • 极致体积,单文件仅 8.5 KB

命令行

支持无序参数,可识别三个参数:路径close超时时间(ms)

调用示例 作用
WorkLauncher.exe 使用同目录下的 WorkApps启动
WorkLauncher.exe close 使用同目录下的 WorkApps关闭
WorkLauncher.exe "D:\Path\Folder" 使用 D:\Path\Folder启动
WorkLauncher.exe "D:\Path\Folder" close 使用 D:\Path\Folder关闭
WorkLauncher.exe 5000 使用同目录下的 WorkApps启动,隐藏窗口监控超时设为 5000ms
WorkLauncher.exe close "D:\Path" 3000 参数乱序组合 → 关闭(超时参数对 close 无实际影响)
  • close 不区分大小写
  • 纯数字参数代表隐藏窗口的监控超时时间(毫秒),默认 6000(6秒)。
  • 同类型参数重复传入会导致程序直接退出。

启动时的规则

类型 行为
.exe 正常启动,工作目录为该文件夹。
.lnk (常规) 正常启动,工作目录为该文件夹。
.lnk (WindowStyle=7) 读取快捷方式目标路径,仅当目标为 .exe 时处理:若进程已运行则跳过启动,若未运行则以隐藏方式启动。启动后,程序会通过 Win32 Hook 主动监控该进程的所有窗口,一旦其窗口试图显示则立即拦截隐藏(持续监控至超时时间为止,防闪屏/自显)。

close 时的规则

类型 行为
.exe 文件名(映像名)结束进程。
.lnk 通过原生 COM 接口读取快捷方式目标路径;仅当目标为 .exe 时,按该 exe 文件名结束进程;否则跳过。

已知缺陷

  • 一闪而过:对于需要隐藏运行的程序,由于操作系统启动进程和 Hook 机制生效之间存在微小的时间差,部分程序的窗口在启动瞬间可能会出现一闪而过的现象,随后才会被彻底隐藏。这是 C# 托管程序在用户态 Hook 的固有时序局限。

注意

  • 只处理文件夹根目录一层.exe / .lnk
  • 路径需为已存在的目录;目录内至少需有一个符合条件的项,否则会退出。
  • 全程无弹窗;若「没反应」,多半是上述退出条件之一。
  • 采用底层 Win32 API Hook 机制拦截窗口,速度极快且兼容 Win11;若 Hook 安装失败,会自动降级为高频轮询兜底。

压缩包下载

WorkLauncher.zip

原AHK版压缩包下载

AHK版WorkLauncher.zip



[C#] 纯文本查看 复制代码
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;

class P
{
    [DllImport("user32")] static extern bool ShowWindow(IntPtr h, int n);
    [DllImport("user32")] static extern bool IsWindowVisible(IntPtr h);
    [DllImport("user32")] static extern bool EnumWindows(EnumWindowsProc d, IntPtr p);
    [DllImport("user32")] static extern uint GetWindowThreadProcessId(IntPtr h, out uint p);
    [DllImport("user32")] static extern IntPtr SetWinEventHook(uint a, uint b, IntPtr c, WinEventDelegate d, uint e, uint f, uint g);
    [DllImport("user32")] static extern bool UnhookWinEvent(IntPtr h);
    [DllImport("user32")] static extern bool PeekMessage(ref MSG m, IntPtr a, uint b, uint c, uint d);
    [DllImport("user32")] static extern bool TranslateMessage(ref MSG m);
    [DllImport("user32")] static extern IntPtr DispatchMessage(ref MSG m);
    [DllImport("kernel32")] static extern uint GetTickCount(); // 替代 Stopwatch

    delegate bool EnumWindowsProc(IntPtr h, IntPtr p);
    delegate void WinEventDelegate(IntPtr a, uint b, IntPtr c, int d, int e, uint f, uint g);

    [StructLayout(LayoutKind.Sequential)]
    struct MSG { public IntPtr H; public uint M; public IntPtr W; public IntPtr L; public uint T; public int X, Y; }

    const int SW_HIDE = 0;
    const uint EVENT_SHOW = 0x8002, WINEVENT_OUTOFCONTEXT = 0, PM_REMOVE = 1;

    static WinEventDelegate _hookDel;
    static IntPtr _hookHandle = IntPtr.Zero; // 只会有一个 Hook,不需要集合

    // 用原生数组替代 Hashtable 和 ArrayList,彻底移除 System.Collections 引用
    static int[] _pids = new int[128];
    static int _pidCount = 0;
    static string[] _names = new string[128];
    static int _nameCount = 0;

    [ComImport, Guid("00021401-0000-0000-C000-000000000046")]
    class ShellLink { }

    [ComImport, Guid("0000010b-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IPersistFile
    {
        void GetClassID(out Guid g);
        void IsDirty();
        void Load([MarshalAs(UnmanagedType.LPWStr)] string f, uint m);
    }

    [ComImport, Guid("000214F9-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    interface IShellLinkW
    {
        void GetPath(IntPtr b, int c, IntPtr p, uint f);
        void GetIDList(out IntPtr p);
        void SetIDList(IntPtr p);
        void GetDescription(IntPtr b, int c);
        void SetDescription(string s);
        void GetWorkingDirectory(IntPtr b, int c);
        void SetWorkingDirectory(string s);
        void GetArguments(IntPtr b, int c);
        void SetArguments(string s);
        void GetHotkey(out ushort k);
        void SetHotkey(ushort k);
        void GetShowCmd(out int c);
    }

    [STAThread]
    static void Main(string[] args)
    {
        string path = "";
        bool close = false;
        int time = 6000;

        foreach (string a in args)
        {
            string t = a.Trim('"');
            if (t.ToLower() == "close") close = true;
            else if (int.TryParse(t, out int i) && i > 0) time = i;
            else path = t;
        }

        if (string.IsNullOrEmpty(path)) path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "WorkApps");
        if (!Directory.Exists(path)) return;

        string[] files = Directory.GetFiles(path);
        if (files.Length == 0) return;

        if (close)
        {
            foreach (string f in files)
            {
                string ext = Path.GetExtension(f).ToLower();
                string exe = null;
                if (ext == ".exe") exe = Path.GetFileName(f);
                else if (ext == ".lnk") { int cmd; exe = Path.GetFileName(GetLnkInfo(f, out cmd)); }

                if (exe != null)
                    foreach (Process p in Process.GetProcessesByName(Path.GetFileNameWithoutExtension(exe)))
                        try { p.Kill(); } catch { }
            }
            return;
        }

        foreach (string f in files)
        {
            string ext = Path.GetExtension(f).ToLower();
            if (ext == ".exe")
            {
                try { Process.Start(new ProcessStartInfo(f) { WorkingDirectory = path }); } catch { }
            }
            else if (ext == ".lnk")
            {
                try
                {
                    int showCmd;
                    string target = GetLnkInfo(f, out showCmd);
                    if (string.IsNullOrEmpty(target) || Path.GetExtension(target).ToLower() != ".exe") continue;

                    string exe = Path.GetFileNameWithoutExtension(target).ToLower();
                    if (showCmd == 7)
                    {
                        if (!HasName(exe)) AddName(exe);
                        if (Process.GetProcessesByName(exe).Length == 0)
                        {
                            Process p = Process.Start(new ProcessStartInfo(f) { WorkingDirectory = path, WindowStyle = ProcessWindowStyle.Hidden });
                            if (p != null && !HasPid(p.Id)) AddPid(p.Id);
                        }
                    }
                    else
                    {
                        Process.Start(new ProcessStartInfo(f) { WorkingDirectory = path });
                    }
                }
                catch { }
            }
        }

        if (_nameCount > 0)
        {
            EnumWindows(delegate (IntPtr h, IntPtr l)
            {
                if (IsWindowVisible(h)) HideIfMatch(h);
                return true;
            }, IntPtr.Zero);

            _hookDel = new WinEventDelegate(delegate (IntPtr hh, uint et, IntPtr h, int idObj, int idChild, uint thread, uint ms)
            {
                if (idObj == 0 && h != IntPtr.Zero && IsWindowVisible(h)) HideIfMatch(h);
            });

            _hookHandle = SetWinEventHook(EVENT_SHOW, EVENT_SHOW, IntPtr.Zero, _hookDel, 0, 0, WINEVENT_OUTOFCONTEXT);
            if (_hookHandle != IntPtr.Zero)
            {
                uint start = GetTickCount();
                MSG m = new MSG();
                while (GetTickCount() - start < time) // 替代 Stopwatch
                {
                    if (PeekMessage(ref m, IntPtr.Zero, 0, 0, PM_REMOVE))
                    {
                        TranslateMessage(ref m);
                        DispatchMessage(ref m);
                    }
                    else Thread.Sleep(10);
                }
                try { UnhookWinEvent(_hookHandle); } catch { }
            }
            else
            {
                uint start = GetTickCount();
                while (GetTickCount() - start < time)
                {
                    EnumWindows(delegate (IntPtr h, IntPtr l)
                    {
                        if (IsWindowVisible(h)) HideIfMatch(h);
                        return true;
                    }, IntPtr.Zero);
                    Thread.Sleep(10);
                }
            }
        }
    }

    static bool HideIfMatch(IntPtr hWnd)
    {
        try
        {
            GetWindowThreadProcessId(hWnd, out uint pid);
            int id = (int)pid;
            if (HasPid(id)) { ShowWindow(hWnd, SW_HIDE); return true; }

            Process p = Process.GetProcessById(id);
            string name = p.ProcessName.ToLower();
            if (HasName(name))
            {
                ShowWindow(hWnd, SW_HIDE);
                if (!HasPid(id)) AddPid(id);
                return true;
            }
        }
        catch { }
        return false;
    }

    // --- 极简数组操作,代替 Hashtable ---
    static bool HasPid(int id) { for (int i = 0; i < _pidCount; i++) if (_pids[i] == id) return true; return false; }
    static void AddPid(int id) { if (_pidCount < 128) _pids[_pidCount++] = id; }
    static bool HasName(string n) { for (int i = 0; i < _nameCount; i++) if (_names[i] == n) return true; return false; }
    static void AddName(string n) { if (_nameCount < 128) _names[_nameCount++] = n; }

    static string GetLnkInfo(string lnk, out int showCmd)
    {
        showCmd = 1;
        try
        {
            ShellLink sl = new ShellLink();
            IPersistFile pf = (IPersistFile)sl;
            pf.Load(lnk, 0);

            IShellLinkW sw = (IShellLinkW)sl;
            IntPtr buf = Marshal.AllocCoTaskMem(520);
            try
            {
                sw.GetPath(buf, 260, IntPtr.Zero, 0);
                string target = Marshal.PtrToStringUni(buf);
                sw.GetShowCmd(out showCmd);
                return target;
            }
            finally
            {
                Marshal.FreeCoTaskMem(buf);
            }
        }
        catch { return null; }
    }
}
ScreenShot_2026-05-20_153444_493.jpg
ScreenShot_2026-05-20_153415_623.jpg
snipaste_20260515_091736.jpg

免费评分

参与人数 2吾爱币 +8 热心值 +1 收起 理由
风之暇想 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
策士 + 1 热心回复!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

ql1307096 发表于 2026-5-20 09:05
我也是用autohotkey,我是这么实现的

;系统自动登陆,三秒钟后锁定计算机
;Sleep(3000)
DllCall("LockWorkStation")
;启动后等待3分钟启动微信
Sleep(180000)
Run "D:\Program Files\Tencent\Weixin4\Weixin\Weixin.exe"
;Sleep(3000)
Run ""D:\Program Files (x86)\Tencent\QQ\Bin\QQScLauncher.exe""
randomnany 发表于 2026-5-19 14:18
支持下。
我都是用 微软的  PowerToys 里面的 工作区 来实现,把工作的软件加入到工作区,开机进入系统后,点击这个快捷方式就行了。
jsj86 发表于 2026-5-15 09:40
hymqid 发表于 2026-5-15 10:41
每天需要用的软件一键全启动?
 楼主| 星峰 发表于 2026-5-15 11:15
hymqid 发表于 2026-5-15 10:41
每天需要用的软件一键全启动?

嗯,是的
yourbestrun 发表于 2026-5-15 15:03
就是说每天要启动的软件,把exe整理到一起,批量启动是嘛 ?
sylar2022 发表于 2026-5-15 15:12
不是很明白这个软件的用途。
afti 发表于 2026-5-15 15:43
可以批量开启工作用软件
kwai0324 发表于 2026-5-15 21:21
我试试,我现在用的bat
 楼主| 星峰 发表于 2026-5-16 11:51
yourbestrun 发表于 2026-5-15 15:03
就是说每天要启动的软件,把exe整理到一起,批量启动是嘛 ?

是的,还可以批量关闭
info99 发表于 2026-5-16 13:33
yourbestrun 发表于 2026-5-15 15:03
就是说每天要启动的软件,把exe整理到一起,批量启动是嘛 ?

我的理解应该也是这样,就好像每天开机自定义后台启动微信,浏览器等
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-7-17 16:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表