在Windows系统上,如何强制让任意GUI软件在后台/最小化启动而不显示窗口?

如标题所述,在Windows系统上(win 10+),有什么办法让本身没有静默启动功能的GUI软件,能够在后台/最小化启动而不显示窗口?
另外,只在软件启动时不显示,之后能够恢复窗口

例如:雷电模拟器

最简单的方法是用cmd命令start /min或者Start-Process -WindowStyle Minimized或者快捷方式属性-运行方式-最小化,但个别软件却会忽略这些参数,仍然在前台显示窗口。

当然还可以用像AHK之类的自动化工具或者用powershell调用winapi,启动软件然后自动点击窗口的最小化来实现。但这样会使得屏幕上短暂闪现窗口,仍然不够完美。

因此问问大家有没有办法完美解决这个问题?

update:我又想到,也许可以通过虚拟桌面或者多桌面的功能,让软件在当前不显示的桌面上打开

总结:
根据解决方案中的源码,移植到powershell,并大大简化了
作用于雷电模拟器:

# Windows API
Add-Type @"
	using System;
	using System.Runtime.InteropServices;
	public static class WinAPI {
		[DllImport("user32.dll", CharSet = CharSet.Unicode)]
		public static extern IntPtr FindWindow(String sClassName, IntPtr sAppName);

		[DllImport("user32.dll")]
		public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
	}
"@

$SWP_NOSIZE = 0x0001
$SWP_NOMOVE = 0x0002
$SWP_NOZORDER = 0x0004
$SWP_NOACTIVATE = 0x0010
$SWP_HIDEWINDOW = 0x0080
$windowFlags = $SWP_HIDEWINDOW -bor $SWP_NOSIZE -bor $SWP_NOMOVE -bor $SWP_NOZORDER -bor $SWP_NOACTIVATE

Start-Process dnplayer.exe

while ($true)
{
	[System.IntPtr] $hwnd = [WinAPI]::FindWindow("LDPlayerMainFrame", [System.IntPtr]::Zero);
	if ($hwnd -ne [System.IntPtr]::Zero) {
		[WinAPI]::SetWindowPos($hwnd, 0, 0, 0, 0, 0, $windowFlags) | Out-Null
		break
	}
	Start-Sleep -Milliseconds 1
}
1 个赞

请问一下用ahk 怎么写?

我不会AHK,但搜索论坛时有人说到了这个方法

我可以提供一个powershell把窗口完全隐藏的例子

# 窗口状态与对应值
$WindowStates = @{
	'FORCEMINIMIZE'   = 11
	'HIDE'            = 0
	'MAXIMIZE'        = 3
	'MINIMIZE'        = 6
	'RESTORE'         = 9
	'SHOW'            = 5
	'SHOWDEFAULT'     = 10
	'SHOWMAXIMIZED'   = 3
	'SHOWMINIMIZED'   = 2
	'SHOWMINNOACTIVE' = 7
	'SHOWNA'          = 8
	'SHOWNOACTIVATE'  = 4
	'SHOWNORMAL'      = 1
}


# 调用 Windows API
Add-Type @"
	using System;
	using System.Runtime.InteropServices;
	public class WinAPI {
		[DllImport("user32.dll")]
		public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
	}
"@
#启动程序
Start-Process dnplayer.exe
#循环40次,避免多次弹出窗口
for ($i = 0; $i -lt 40; $i++)
{
	# 进程名是dnplayer
	$process = Get-Process -Name "dnplayer" -ErrorAction SilentlyContinue
	# 如果主窗口存在
	if ($process.MainWindowHandle -ne 0) {
		[System.IntPtr] $MainWindowHandle = $process.MainWindowHandle
		# 最小化窗口
		[WinAPI]::ShowWindowAsync($MainWindowHandle, $WindowStates['MINIMIZE']) | Out-Null
		Write-Host "Hide dnplayer!"
	}
	# 睡眠50ms
	Start-Sleep -Milliseconds 50
}
1 个赞

我想出有几个办法
1.简单粗暴,使用程序语言(如c)以轮询的方式查找并隐藏窗口,不需要特殊权限,因为编译的代码执行很快,所以大概率来不及闪一下
优点:
五行代码就能完成
缺点:*
大概率来不及闪一下,所以小概率来得及闪一下…
CPU占用会很高

2.一般思路,调用hook相关api进行全局hook,每一个窗口显示前先检查它是否属于待拦截进程,若是则拦截,否则放行
优点:
不会闪一下
缺点:
全局钩子必须创建一个用于注入进程的dll和一个设置hook的程序,所以必须使用可以创建dll的语言
必须取得管理员权限
大量设置hook会拖慢系统性能

3.利用轮子,基于上一个思路,我们发现这很像弹窗拦截程序。于是直接在弹窗拦截程序(如火绒)中拦截主窗口即可
优点:
不会闪一下
不需要编程
缺点:
得安装一个弹窗拦截程序,失去可移植性
同理,失去自定义能力

4.dll注入,使用dll劫持,直接hook程序中显示窗口的api,不让他显示
优点:
不会闪一下
只需要增加一个dll了,不需要管理员权限
缺点:
可能被报毒
需要写一个dll

5.非法修改,直接把要隐藏的程序进行反汇编,去掉显示窗口的那一条指令。不显示=隐藏
优点:
不会闪一下
并且不会损失性能,不需要新增组件
缺点:
需要修改者需要一定(不多)的反汇编能力
剩余缺点的数量取决于待隐藏软件具体情况

  1. 其实还是先有窗口显示出来后,再把窗口最小化/隐藏。这肯定还是会有窗口闪现的,个人理解是这样的

  2. 过于困难,这估计都到系统底层了。是否有现成的工具?等人寻找

  3. 弹窗拦截程序:这就不是启动时最小化/隐藏了,弹窗拦截是关闭窗口……要的只是启动时不显示而已,之后想看的时候还是需要显示窗口的

4和5 有没有例子?

您已经选定了2、4、5方案
接着:
若您可以直接提供软件,我可以为您选择一个最合适的方案,编写一个程序完成
若您不能直接提供软件,则我们可能需要您确认一些信息,然后再确定是否有可用的解决方案

目前我遇到的软件是 雷电模拟器9.1.30 其中的 dnplayer.exe
直链下载 飞书云 123云盘
(无需安装,修改为7Z后缀解压即可)

如果需要提供信息的话,就直接问吧

你搜一下nircmd,这个程序可以让程序在前台运行,但不显示窗口。

试过了
nircmd exec hide是吗?不起作用的
根据参数的可选类型show/hide/min/max,我推测它实际就是Start-Process -WindowStyle 的包装罢了

我回来啦!因为不可抗力耽搁了…昨天晚上就应该发出来了
LDHIDER
密码:2jak

下载ldhook.7z,解压缩文件到雷电模拟器主目录,启动pecmd.exe即可
ldhook_src.7z则是源码


以下写给开发人员

这个程序为了性能应该是使用了某种直接写入显存来显示画面的库(按导入表看应该是OpenGL);而隐藏启动参数则是针对使用GDI绘制标准窗口的程序。它们的底层实现不一样,所以没办法通用

实际上主窗口是一层GDI标准窗口,底下再加一层OpenGL窗口,所以我的解决方案实现原理大致如下

1 挂钩到每一个窗口,只要任意窗口有窗口消息就触发以下逻辑:
(因为”只要任意窗口有窗口消息“就触发,所以雷电模拟器创建窗口时也会触发)

2 检查雷电模拟器那层GDI标准窗口是否存在
(那层GDI标准窗口先被创建,而且调用winapi就可以检查,故以此为执行条件)

3 若存在,隐藏之,并将其移动到显示屏外
(OpenGL窗口跟随GDI窗口移动,而OpenGL又不会绘制显示屏外的部分,这样直接避免了OpenGL窗口被绘制)
(如果只隐藏GDI窗口,不移动它的话,启动时会在原地留下一个大黑框——其实是找不到gdi窗口的OpenGL窗口)

4 在开始挂钩的五秒钟后卸载挂钩
(挂钩很浪费系统资源,故需要尽可能快卸载)

所以是用全局钩子做的吗?

是的,基于思路2,但是因为情况复杂所以多了好几步额外处理

我试了下,模拟器还是正常显示了窗口
环境:windows 11 关闭了安全软件(火绒)

可恶…我暂时没有设备可供调试
如果您已经确保pecmd.exe、ldhook.dll与dnplayer.exe处于同一目录下,则您可以尝试以管理员身份启动pecmd.exe
您也可以先下载ldhook_src.7z进行调试,我要在获取可用设备后才能进行调试…

是的,都在同一目录下,不然也启动不了dnplayer.exe。并且运行时是要求必须管理员权限的,不给是不行的

请重试,已更新

很遗憾,同样的结果,窗口正常显示
我把程序都放在D:\Program\LDPlayer9里面

ps:照着别的hook示例和你的源码,想改写成powershell,发现有的函数还是必须用dll……算了

要不我们试试看思路1?我测了十几遍,没有出现闪一下的情况
在原链接里面的那个未命名1.exe

的确完全不闪
不过这个程序自身就会显示一个终端窗口……能改成不显示终端窗口吗?

还有个问题,这会导致被隐藏的窗口,在这次运行中,无法显示了。只能关掉模拟器,重开
需要双击托盘在状态栏显示出图标,然后右键图标,再打开,会在左上角显示一个小窗口……

如果只是不需要显示控制台,原链接中的t_ldhider.7z可以