将Windows Server 2025打造成“ChatGPT公用机”

有一些平台的登录是要看用户浏览器的指纹信息的,如果指纹不同,可能会弹出额外的验证,比如Google,比如ChatGPT。这是一种安全机制,能有效防止用户帐户被盗,但也导致一些麻烦:如果你想要多人共享一个帐号的话。传统的做法是在局域网内设置一个代理,并提供Google、ChatGPT的账号。但因为这种机制的存在,很快Google就会要求提供发送到验证邮箱的验证码,或使用手机短信验证,非常麻烦。

是否可以换个思路,在局域网中建立一台Windows Server,利用其“同一账户可同时存在多个登录”的特点,预先在Chrome中把Google和ChatGPT登录好,然后用户直接远程,即点即用呢?

我用一台Windows Server 2025一番试验,可以实现,但需要对服务器作一点点的改造。

一、Windows Server本身设置

1、破解远程桌面连接限制

很多人误以为Windows Server的“同一账户可同时登录”的远程桌面连接数是不受限制的,实际上并非如此。Windows Server只支持2个活动连接,当试图建立第3个连接时,就会提示“已登录的用户太多”。即使以不同的本地用户来远程连接,其活动连接总数仍然不能超过2个。当然可以通过购买RDS CAL许可来突破这个限制,但这不是我们想要的。

只好再次请出RDPWrap。RDPWrap是一款神一样的工具,专门用来解决Windows的多用户登录问题。它将破解部分写入ini文件,从而实现了程序的不败金身:最新版本为1.6.2,2017年即已发布,但至今适用。RDPWrap.ini则由一个很大的爱好者社区们共同维护,每次Windows版本升级修改了termsrv.dll,爱好者马上就能发布一个新的配置块。

RDPWrap下载:https://github.com/stascorp/rdpwrap/releases

RDPWrap.ini更新:https://github.com/sebaxakerhtc/rdpwrap.ini/blob/master/rdpwrap.ini

Telegram群组:https://t.me/rdpwrap

下载RDPWrap.zip,解压缩后,双击install.bat即可安装到默认文件夹 C:\Program Files\RDP Wrapper。默认的RDPWrap.ini上古版本只有100多KB,如今已经更新到了400多KB。下载最新版本的RDPWrap.ini,替换C:\Program Files\RDP Wrapper下的同名文件,然后重启Remote Desktop Services(TermService)服务,即可应用新的ini配置了。

确认ini文件是否生效有两种方式:

1)检查c:\windows\system32\termsrv.dll的版本号。右键 - 属性 - 详细信息,查看文件版本,例如:10.0.26100.4484。然后在RDPWrap.ini搜索该版本号,能搜索到以它为标题的配置块,说明RDPWrap.ini已经支持了你的termsrv.dll版本。

2)运行安装程序包中的RDPConf.exe。在listener state中应该能看到绿色的listening和[fully supported]。通常来说,Authentication Mode可以选择“Default RDP Authentication”,Session Shadowing Mode选择“Full access without permission”。有所区别的是“Single session per user”,一般来说是勾上的,但在本例中,我们要取消勾选,以允许同一个本地用户可以多次登录。

好,经过上述配置后,Windows Server在底层层面已经可以不受限制地进行远程连接了:总数不受限制,且单用户可以同时多次登录。

2、组策略配置

打开组策略(gpedit.msc),进入:计算机配置 - 管理模板 - Windows组件 - 远程桌面服务 - 连接。将右侧“限制连接的数量”启用并设置为999999;将“将远程桌面服务用户限制到单独的远程桌面服务会话”设置为“已禁用”。

为了防止用户直接挂起登录而不是注销(用户的习惯性操作是用完后直接叉掉远程桌面),导致不断堆积新的挂起登录占用资源,组策略中进入:计算机配置 - 管理模板 - Windows组件 - 远程桌面服务 - 会话时间限制,将右侧“设置活动但空闲的远程桌面服务会话的时间限制”设置为“已启用”,值建议设置为30分钟。

3、创建远程桌面公用账户

创建公用账户,例如public,并设置足够强的密码。将用户加入到“remote desktop users”组和“Power users”组。

进入设置 - 远程桌面设置,启用服务器的远程桌面连接,将public用户添加到允许远程桌面连接的用户清单(等价于加入remote desktop users组)。

二、代理设置

到这里下载Clash Verge Rev:https://github.com/clash-verge-rev/clash-verge-rev/releases,最新版本是2.3.2。

Clash Verge Rev安装时默认会同时安装服务,假设使用Administrator用户安装,那么即使Administrator用户已经注销,Clash Verge Rev仍然会有效工作。

它的配置文件位于:C:\Users\Administrator\AppData\Roaming\io.github.clash-verge-rev.clash-verge-rev\profiles,妥善设置该文件夹的NTFS权限,防止被公共账户读取。

它的程序文件夹位于:C:\Program Files\Clash Verge,同样设置好NTFS权限,避免被公共账户用户破坏。

它的默认服务端口是127.0.0.1:7897,也可以在“设置 - Clash设置 - 端口设置”修改端口。

注意,“设置 - Clash设置 - 外部控制”中,将默认API访问密钥修改为复杂密码,以确保公共账户无法通过9097的controller端口对代理配置进行修改。

三、Chrome配置文件夹隔离

经过以上配置,public用户已经可以不受限制地多次登录,比如局域网内10个客户端都可以同时登录public,共同操作又互不影响。

你以为这样就解决问题了?No。

同一个用户public,它的Chrome配置文件夹是固定的,位于:C:\Users\public\AppData\Local\Google\Chrome\User Data。当第一个public用户(简称public-01)登录,打开Chrome时,它便会锁定配置数据库,于是public-02、03……再登录时,会发现无法打开Chrome了。这显然不是想要的效果。

好在,Chrome是支持“--user-data-dir”参数的,可以自定义运行时的配置文件夹。那么,就可以将一个已经登录了Google、ChatGPT账号的Chrome配置文件夹整个copy出来,然后手动指定不同的public登录各自使用“自己的”配置文件夹。为了做到这一点,可以读取当前public登录的sessionid,用它作为对不同文件夹的区分。

我们创建文件夹:C:\ChromeProfiles,将在这里保存不同的配置文件夹。它们的名称将是:public_SID[SID],例如:public_SID3、public_SID4……,但首先得要有一个public_template。

在默认Chrome中登录好相关账号,设置好代理“127.0.0.1:7897”,保存好cookies,然后将C:\Users\public\AppData\Local\Google\Chrome\User Data中的全部文件,copy到:C:\ChromeProfiles\public_template。

创建Chrome.bat:

@echo off
rem === 1. 计算 SID(首选 PowerShell,失败再回退) =============
for /f %%i in ('powershell -NoLogo -NoProfile -command "(Get-Process -Id $PID).SessionId"') do set "SID=%%i"

if not defined SID (
    rem -- 回退方案:SESSIONNAME + query session --
    for /f "tokens=2 delims=#" %%i in ("%SESSIONNAME%") do (
        echo %%i | findstr /r "^[0-9][0-9]*$" >nul && set "SID=%%i"
    )
    if not defined SID (
        for /f "tokens=3" %%i in ('query session %USERNAME% ^| findstr /r /c:" %USERNAME% "') do (
            set "SID=%%i"
        )
    )
)

if not defined SID (
    echo [%date% %time%]  未能获取 SID,用户名=%USERNAME% >>C:\ChromeProfiles\sync_error.log
    goto :eof
)

rem === 2. 复制模板并启动 Chrome ================================
set "template=C:\ChromeProfiles\public_template"
set "profileDir=C:\ChromeProfiles\%USERNAME%_SID%SID%"

if not exist "%profileDir%" (
    xcopy /E /I /Y "%template%" "%profileDir%" >nul
)

start "" "C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="%profileDir%"

用户登录public后,双击该bat。它将依次使用powershell、query两种方法取得当前登录的sessionid,然后拼接成一个文件夹路径C:\ChromeProfiles\public_SID[SID]。之后检查该路径是否存在,如果存在,那么就直接使用;如果不存在,则复制一份public_template,作为自己的配置文件夹。最后,带参数“--user-data-dir”启动Chrome。

由于sessionid并不会无限增加,该方案就完美实现了“既隔离配置文件”又“不会无限占用空间”。

考虑到双击bat文件用户体验不太好,cmd黑乎乎的窗口会闪一下,可以用start_Chrome.vbs来启动bat:

Set oShell = CreateObject("WScript.Shell")
oShell.Run "cmd /c ""C:\public-files\Chrome.bat""", 0, False

注意路径改成实际Chrome.bat所在路径。

为vbs创建一个桌面快捷方式,为快捷方式换上图标——使用体验就很棒了。

四、解决SessionID递增问题

进一步测试发现,如果服务器本身不重启,那么同一个public用户每次登录、注销,都会产生一个新的SessionID,不断递增。这样就会不断复制出新的配置文件夹——最终占满硬盘。

无法通过定期执行某个脚本来解决这个问题,只能对服务器执行完全重启,才能重置SessionID。那么可以让它每天重启,添加一个任务计划,设置为“不管用户是否登录都要运行”,勾选“使用最高权限运行”,然后每天凌晨执行:“shutdown /r /t 60 /f”即可。

但紧接着新的问题来了:Clash Verge Rev虽然可以以服务运行,但重启后它并不会生效,第一次生效还是需要通过UI界面唤起,之后才可以注销运行UI界面时的用户。经过一番研究,最有效的方式还是让服务器重启后自动登录到Administrator用户,并将Clash Verge Rev设置为开机启动。

为了保障安全,再添加第二个任务计划,让Administrator在登录时立即执行:

rundll32.exe user32.dll,LockWorkStation

即可实现自动登录后立即锁定。

五、清理Profiles文件夹并保持template更新

经过前面的操作之后,已经可以较好地工作,但有两个个潜在的隐患:

1、public_template文件夹是固定的,时间久了可能会过期。

2、public用户的活动会不断累积在配置文件夹,并持久存储。

所以还可以进一步优化。

思路是:在服务器重启之前,找到当天最后一个登录的SID文件夹,去掉其中的缓存文件、网站访问记录等,但保留网站的登录信息(cookies),然后用它去替换template,之后删除除了template之外的所有配置文件夹。

这样,既维持了template的更新,也清除了无关文件,第二天又清清爽爽了。

编辑任务计划,让服务器每天凌晨执行以下脚本(bat):

@echo off
setlocal enabledelayedexpansion

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 0. 注销所有 “public” 用户会话(忽略大小写)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
echo === 正在注销 public 用户会话 ===
rem 说明:query session 输出列顺序为 SESSIONNAME USERNAME ID ...
for /f "skip=1 tokens=1,2,3*" %%A in ('query session') do (
    rem %%A=SESSIONNAME  %%B=USERNAME  %%C=ID
    if /I "%%B"=="public" (
        echo    -> 注销会话 ID %%C(用户 public)
        logoff %%C >nul 2>nul
    )
)
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 1. 变量与路径
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set "base_dir=C:\chromeprofiles"
set "template_dir=%base_dir%\public_template"
set "prefix=public_SID"
set "max_sid=-1"
set "max_folder="

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 2. 查找最大的 public_SID[X] 目录
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
for /d %%D in (%base_dir%\%prefix%*) do (
    set "name=%%~nxD"                       rem 仅目录名
    if /I not "!name!"=="public_template" (
        set "num=!name:%prefix%=!"          rem 得到数字
        rem ② 判断是否全数字:直接用 set /a 试算最直白
        set /a dummy=!num!+0 2>nul
        if not errorlevel 1 (
            if !num! gtr !max_sid! (
                set "max_sid=!num!"
                set "max_folder=%%D"
            )
        )
    )
)

echo max_sid = !max_sid!
echo max_folder = "!max_folder!"
if "!max_folder!"=="" (
    echo ERROR: 未找到任何 public_SID[X] 目录。
    exit /b 1
)
echo === 最大 SID 目录: !max_folder! ===
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 3. 清理最大 SID 目录中的缓存和历史记录
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
set "profile=!max_folder!\Default"
echo === 清理缓存与历史记录: !profile! ===
for %%P in (
    "Cache"
    "Code Cache"
    "GPUCache"
    "Session Storage"
    "Sessions"
) do (
    rd /s /q "!profile!\%%~P" 2>nul
)
del /f /q "!profile!\History" "!profile!\History-journal" 2>nul

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 4. 用最大 SID 目录覆盖 public_template
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
echo === 更新 public_template ===
rd /s /q "%template_dir%" 2>nul
mkdir "%template_dir%"
xcopy "!max_folder!\*" "%template_dir%" /E /I /H /K /Y >nul

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: 5. 删除所有其他 public_SID[X] 目录
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
for /d %%D in ("%base_dir%\%prefix%*") do (
    if /I not "%%~nxD"=="public_template" (
        rd /s /q "%%D"
    )
)

echo === 所有操作完成,30 秒后重启服务器 ===
shutdown /r /t 30 /f
endlocal

如此,配置文件夹的体积增加将变得微乎其微,最大限度地减少了人工干预。