前言
????????這是一次提前下班沒有鎖屏進而引發的一次思考后的產物,思考的主要場景是當人離開電腦后,怎么能控制電腦鎖屏,避免屏幕上的聊天記錄被曝光。
? ? ? ? 首先想到通過系統的電源計劃設置閑置超時時間熄屏,這可能是最接近場景的解決方案,然后就考慮通過程序實現。剛開始認為這是在有限能力范圍內最好的解決方案。但在嘗試下面的方案二的時候又發現直接通過計劃任務設置 12:00、18:00 兩個一定會離開位置的自動鎖屏時間也能解決問題。
? ? ? ? 當時有一種執念,根據已知的信息來看,這肯定是能實現的,為什么現在沒成功,必須要找到解決的方法,就這樣一路摸索著最終調試通過。這個功能實現后還可以結合一些場景實現自動化,例如離開公司和回到家兩個場景都可以觸發鎖屏。等等一些類似的場景在需要的時候自動觸發鎖屏。
正文
? ? ? ? 這個想法在腦海里構思了幾天,在今天(2024年5月10日)有時間來實踐了,于是嘗試在已有的會話狀態程序中新增自動鎖屏功能。
? ? ? ? 簡單介紹下這個會話狀態程序,在多次沉迷代碼不能自拔導致錯過點餐最佳時間后。寫的一個提醒程序,判斷每天如果電腦被解鎖時間超過30分鐘,則通過企業微信機器人@是時候點餐了。后面又加入了如果明天是休息日提醒該填報工時了,又又加入如果電腦解鎖時間超過30分鐘并且今天是工作日檢查今天是否有提交代碼等待一些人在公司時的一些提醒。
? ? ? ? 所以這個遠程鎖屏功能自然也就考慮繼續往會話狀態程序上加了。下面是鎖屏功能的大致流程圖,Windows 服務負責執行鎖屏操作,Web 服務負責收集指令,倫理上什么都能做,只需要在Windows 服務中解析指令。
? ? ? ? 接著就是通過搜索引擎實現想法的時候了,搜索關于鎖屏很快就定位到一個清晰的方案一結果,這個看起來是如此的簡單,果斷復制下來加入?Program.cs 中調試,一擊即中看起來核心的鎖屏功能就實現了。那接下來就是把整個流程寫完。流程的過程沒有什么特別的,下面主要講一講實現鎖屏的曲折過程。
方案一
// 引入 LockWorkStation 函數
[DllImport("user32.dll")]
private static extern bool LockWorkStation();
? ? ? ? 在 Windows 服務和 Web 服務代碼寫完調試通過后,于是開始發布準備聯調。如果順利文章到這里就可以結束,可能還需要感嘆下以湊多些正文~~~
? ? ? ? 好在湊正文的事情不用做了,因為方案一的代碼發布后不生效,并且完全沒有異常。在調試時好好的,一切正常,部署后就不行了。就現在這種情況搜索都不好找關鍵字。于是向 ChatGPT 求助「C# 鎖屏操作,在調試的時候有效,但是發布后不行,這個應該怎么解決呢?」對方回復說是可能是權限問題、依賴問題、平臺兼容性、錯誤處理和日志、環境差異。這些問題來大部分不存在的因為環境沒有發生變化,都在同一臺電腦上,唯有第一個權限問題是可能的,但是也并沒有更清晰的方向,畢竟這個問法也是在為難 ChatGPT,然后又接著搜索 Windows 鎖屏。
方案二
C# 代碼
Process.Start("rundll32.exe", "user32.dll,LockWorkStation");CMD 代碼
rundll32.exe user32.dll,LockWorkStation
? ? ? ? 接著找到了方案二通過 CMD 執行鎖屏,并且在 CMD 中執行使用代碼也是能成功的,于是懷著試一試的態度開始改造 Windows 服務程序。代碼也只有一行,其實也沒什么好改的 ~~~
? ? ? ? 很快改完發布,不出意外意外就沒有出現,發布后的程序依然不能實現鎖屏,由于懷疑是聯調中間出了差錯,都已經把邏輯直接放到了 Windows 服務啟動項里面去了,只要服務能正常啟動,鎖屏邏輯就一定被執行。但是現在看起來屏幕并沒有鎖,到底是哪里出了問題,依然沒有頭緒。
? ? ? ? 在繼續搜索C#鎖屏關鍵字時刷到一條信息,大概意思是說由于 Windows 服務屬于后臺任務,并不是當前登錄用戶通過操作啟動的,Windows 出于安全考慮這類場景就不能與用戶交互。
? ? ? ? 這就說得通了,到目前位置方案一和方案二的代碼都沒問題是確定的,問題就是我在調試時是通過 Visual Studio 啟動的程序,此時屬于用戶操作啟動的程序和 Windows 服務不是同一個會話的差別,此時這個場景下的程序還不是 Windows 服務所以能與用戶交互,所以鎖屏動作成功了。
? ? ? ? 有了這個信息,我再回頭和 ChatGPT 說,果然對方其實也是知道這個事情的,只是剛開始我沒有向對方提起我這邊是在 Windows 服務中使用的。現在場景清晰了,并給我5個建議的方向使用計劃任務、使用WCF或命名管道通信、通過COM激活、利用任務調度器Interactive Option,使用Messager API 或 SendSignal utility,果然很專業的樣子,但是我一個都不想選。
? ? ? ? 我想到了另外兩個方向,通過觸發快捷鍵和單獨寫一個 WinFrom 程序。這兩個方法看起來都要比給出的5個方向簡單,因為這兩個方向有熟悉的味道。
? ? ? ? 很快結論就出來了,這段是關于快捷鍵的,SendKeys 很可能也是出于安全考慮,不能觸發 Win 鍵,Ctrl、Shift、Delete 都能觸發唯獨不能觸發 Win 鍵 ~~~ 按理說肯定有辦法的,只是現在還不知道怎么做。好在還有一個備選方案,馬不停蹄繼續。
? ? ? ? 繼續用方案二的 C# 代碼執行鎖屏 WinFrom 操作,代碼也只有一段,可惜就是不成功,原因和最開始發現的一樣,作為 Windows 服務無論做什么操作都是在相同?Session 中狀態下執行的,Windows 服務不能與用戶交互,即使?Windows 服務啟動一個有界面的 WinFrom 程序也不能與用戶交互(其實界面并沒有出現)。就算一直套娃下去依然不能。
? ? ? ? 然后就回去把這個情況給 ChatGPT 反饋了,對方得知這個情況后并沒有嘲笑或指責沒有聽話照做,反而是在現有場景上給出了新的建議,大概是:如果要在 Windows 服務中通過?WinFrom 來執行鎖屏操作也不是不可能,前提是這個 WinFrom 需要提前啟動,可以是用戶啟動甚至是開機自動啟動,只要不是 Windows 服務去啟動的都可以。接著再由 Windows 服務與?WinFrom 通訊告訴 WinFrom 去執行鎖屏這樣就沒問題。
? ? ? ? 方案是有了,并且這應該是能成功的,但是現在電腦上已經有一個 Windows 服務了,如果為了這個功能再跑一個程序,感覺上不是很爽,不希望把程序的復雜度攤開來降低使用體驗。只能重新審視 ChatGPT 最開始提出的5個方向了。剛開始看的時候都沒有仔細思考這些關鍵字,現在回頭看,第一個有熟悉的味道。? ? ? ??
方案二調整
/// <summary>
/// 通過 CMD 執行命令
/// </summary>
/// <param name="applocaltion"></param>
/// <param name="argument"></param>
private static void Exec(string applocaltion, string argument)
{using (var process = new Process()){process.StartInfo.Arguments = argument;process.StartInfo.FileName = applocaltion;process.StartInfo.UseShellExecute = false;process.StartInfo.RedirectStandardInput = true;process.StartInfo.RedirectStandardOutput = true;process.StartInfo.RedirectStandardError = true;process.StartInfo.CreateNoWindow = true;process.Start();process.StandardInput.AutoFlush = true;process.StandardInput.WriteLine("exit");//獲取cmd窗口的輸出信息 //string output = process.StandardOutput.ReadToEnd();process.WaitForExit();process.Close();}
}// 調用方法
Exec("schtasks", $"/create /tn {_name} /tr \"rundll32.exe user32.dll,LockWorkStation\" /sc once /st {DateTime.Now.AddMinutes(1):HH:mm} /f");
? ? ? ?計劃任務是一個系統服務,Windows 服務創建一個計劃任務去執行鎖屏操作這樣就跳出了 Windows 服務不能與用戶交互的限制。
????????直接通過方案二的代碼執行 schtasks 時會拋出異常「System.ComponentModel.Win32Exception:“系統找不到指定的文件。”」調整方案二的代碼后就能正常跑起來。
? ? ? ? 一晃一個多小時過去了,現在的情況比最開始的方案一有進展,但并不多,好消息是計劃任務創建成功了,并且調試的時候也沒問題。壞消息是發布后計劃任務超時了也沒有執行鎖屏。
????????仔細思考懷疑通過 CMD 來創建的計劃任務很有可能還沒有跳出 Session 的場景(其實不是)。于是問 ChatGPT 還有哪些方法可以創建,于是就推薦了?Microsoft.Win32.TaskScheduler NuGet 第三方組件來創建計劃任務,于是抱著有路就要試一試的態度再次調整邏輯開始了方案三。
方案三
引入 NuGet 包:Microsoft.Win32.TaskScheduler;using (var ts = new TaskService())
{// 創建新任務var td = ts.NewTask();td.RegistrationInfo.Description = "定時鎖屏";// 設置觸發器,或其他觸發器,如WeeklyTrigger、MonthlyTrigger等td.Triggers.Add(new TimeTrigger { StartBoundary = DateTime.Now.AddMinutes(1) });td.Actions.Add(new ExecAction("rundll32.exe", "user32.dll,LockWorkStation", null)); // 設置操作// 注冊任務,并且需要指定登錄的系統賬號密碼才能在成功ts.RootFolder.RegisterTaskDefinition(_name, td, TaskCreation.CreateOrUpdate, "administrator", "123456", TaskLogonType.InteractiveTokenOrPassword);
}
????????經過漫長的幾個小時調試,終于在調試方案三的過程中突然鎖屏了。一開始的情況和方案二一樣,也是調試的時候沒問題,發布就沒效果。在不斷向 ChatGPT 逼問下,對方說可能是權限問題,說可以指定賬號密碼試試。在加上賬號密碼過程中調試了N多次,每調試一次都需要等待至少一分鐘。
? ? ? ? 這個過程中才發現原來計劃任務指定的執行時間并不是準時的分鐘,而且每分鐘檢查一次是否執行,而前面在不知道這個前提的情況下,一直守著系統秒跳到00,發現沒執行又是一陣排查調整,可想而知絕大部分的調整都是沒有效果的。直到加上了系統賬號密碼,緊接著分析為什么成功了,通過 Ctrl + Z 回滾大法最終確認是因為加上了下面加租的參數。
ts.RootFolder.RegisterTaskDefinition(_name, td, TaskCreation.CreateOrUpdate, "administrator", "123456", TaskLogonType.InteractiveTokenOrPassword);
????????也就是說因為前面提到 Windows 服務的特殊情況,必須指定一個已經登錄了用戶并且輸入密碼才能在創建的任務中觸發操作。
? ? ? ? 一開始也是不愿意加賬號密碼的,考慮到如果這個 Windows 服務要是裝在多臺電腦上,還需要修改對應的賬號密碼,如果改成配置文件一是感覺不安全,二是現在程序還沒有配置文件 ~~~,但是最終沒有抗住加上就成功了。現在回頭看如果在方案二中加上賬號密碼也能成功。
????????最后再獻上通過 CMD 創建、執行、刪除計劃任務的代碼
# 創建任務:在指定時間執行一次鎖屏,如果任務存在則覆蓋
schtasks /create /tn LockScreen /tr "rundll32.exe user32.dll,LockWorkStation" /sc once /st 09:24 /f# 手動執行任務
schtasks /run /tn LockScreen# 強制刪除任務
schtasks /delete /tn LockScreen /f
參考資料:
一、使用C#創建計劃任務(How to create a Task Scheduler use C# )_使用c#實現計劃任務-CSDN博客
二、Task Scheduler Managed Wrapper