程關(guān)機(jī):Windows局域網(wǎng)多機(jī)自動(dòng)關(guān)機(jī)腳本實(shí)戰(zhàn))
簡(jiǎn)介這份資源面向需要在Windows環(huán)境下管理多臺(tái)設(shè)備的IT運(yùn)維人員與Python學(xué)習(xí)者提供一套自動(dòng)遠(yuǎn)程批量關(guān)機(jī)的腳本方案解決逐臺(tái)手動(dòng)關(guān)機(jī)效率低、易遺漏的問題。壓縮包共3個(gè)文件包含1個(gè)py腳本與2個(gè)txt說明文檔整體僅923B輕量易取腳本可直接參考或改造使用。內(nèi)容圍繞WMI遠(yuǎn)程管理展開涉及通過win32com.client建立遠(yuǎn)程連接、調(diào)用Win32_ComputerSystem的Shutdown方法執(zhí)行軟關(guān)機(jī)或強(qiáng)制關(guān)機(jī)并以IP列表遍歷實(shí)現(xiàn)批量下發(fā)命令同時(shí)提示管理員權(quán)限、目標(biāo)機(jī)遠(yuǎn)程管理策略配置以及用戶名密碼的安全存放方式。已有897人學(xué)習(xí)下載適合希望快速掌握遠(yuǎn)程批量關(guān)機(jī)思路、并在此基礎(chǔ)上擴(kuò)展重試與超時(shí)等健壯性處理的讀者參考借鑒。1. 機(jī)房巡檢收尾那點(diǎn)事為什么我最終選了 Python 批量遠(yuǎn)程關(guān)機(jī)凌晨一點(diǎn)半實(shí)驗(yàn)室的燈還亮著三十多臺(tái) Windows 工作站跑完最后一輪渲染任務(wù)風(fēng)扇聲像一群不肯落地的直升機(jī)。手動(dòng)一臺(tái)臺(tái)點(diǎn)“開始→關(guān)機(jī)→確定”點(diǎn)到最后手都麻了還漏了兩臺(tái)第二天被導(dǎo)師問“昨晚那幾臺(tái)怎么還開著”。這就是我最初折騰批量遠(yuǎn)程關(guān)機(jī)的真實(shí)動(dòng)機(jī)——不是炫技是實(shí)在受不了重復(fù)勞動(dòng)。這份資源要解決的核心問題很具體在 Windows 環(huán)境下用 Python 腳本對(duì)局域網(wǎng)內(nèi)多臺(tái) Windows 主機(jī)發(fā)起自動(dòng)、批量的遠(yuǎn)程關(guān)機(jī)。它適合誰適合手里管著幾臺(tái)到幾十臺(tái) Windows 機(jī)器、又不想逐臺(tái)登錄操作的人——實(shí)驗(yàn)室管理員、測(cè)試機(jī)維護(hù)者、小型機(jī)房值守人員。關(guān)鍵詞里的“自動(dòng)”“遠(yuǎn)程關(guān)機(jī)”“批量關(guān)機(jī)”“windows”“python”五個(gè)詞基本就是它的全部能力邊界。下面我按“先跑通一臺(tái)再擴(kuò)到一批最后處理翻車”的順序拆開講。2. 遠(yuǎn)程關(guān)機(jī)的底層通道WMI、RPC 與 shutdown 命令怎么選2.1 三條常見技術(shù)路線對(duì)比Windows 上讓另一臺(tái)機(jī)器關(guān)機(jī)繞不開三個(gè)東西shutdown命令、WMIWindows Management Instrumentation、以及基于 RPC 的遠(yuǎn)程調(diào)用。它們不是并列關(guān)系而是層層包裹——shutdown /m底層走的就是 RPCWMI 也能調(diào)Win32_OperatingSystem的Shutdown方法。路線調(diào)用方式依賴服務(wù)適用場(chǎng)景主要限制shutdown 命令shutdown /s /m \\目標(biāo)IPRPC、Remote Registry快速腳本、臨時(shí)操作報(bào)錯(cuò)信息模糊權(quán)限要求高WMIwmic /node:IP或 Pythonwmi庫WMI 服務(wù)、DCOM需要讀取狀態(tài)再關(guān)機(jī)配置復(fù)雜防火墻端口多PsExec 類工具遠(yuǎn)程執(zhí)行進(jìn)程Admin$ 共享、SMB需要執(zhí)行任意命令需額外工具易被殺軟攔截我一般優(yōu)先用shutdown命令因?yàn)樗钶p不需要額外裝庫Python 里一個(gè)subprocess就能調(diào)。但它的坑也最多后面避坑章節(jié)會(huì)細(xì)說。2.2 為什么用 Python 而不是批處理批處理也能寫for循環(huán)調(diào)shutdown但一旦要處理“哪些機(jī)器在線”“失敗重試”“記錄日志”“并發(fā)控制”bat 就力不從心了。Python 的優(yōu)勢(shì)在于可以用concurrent.futures做并發(fā)用subprocess拿返回碼用logging落日志用ipaddress生成網(wǎng)段。這些在幾十臺(tái)機(jī)器的場(chǎng)景下不是錦上添花是剛需——串行關(guān)機(jī)等一臺(tái)超時(shí) 30 秒三十臺(tái)就是十五分鐘。2.3 前置條件目標(biāo)機(jī)器必須打開什么遠(yuǎn)程關(guān)機(jī)不是發(fā)個(gè)包就完事目標(biāo) Windows 主機(jī)必須滿足幾個(gè)條件缺一個(gè)就報(bào)“拒絕訪問”Remote Registry 服務(wù)默認(rèn)手動(dòng)啟動(dòng)遠(yuǎn)程 RPC 調(diào)用需要它。設(shè)為自動(dòng)并啟動(dòng)。防火墻規(guī)則入站放行“Windows Management Instrumentation (WMI)”和“Remote Service Management”相關(guān)規(guī)則或者臨時(shí)關(guān)閉域內(nèi)防火墻測(cè)試。賬號(hào)權(quán)限發(fā)起端使用的賬號(hào)必須是目標(biāo)機(jī)的管理員組成員且目標(biāo)機(jī)不能是空密碼賬號(hào)Windows 默認(rèn)禁止空密碼遠(yuǎn)程登錄。網(wǎng)絡(luò)發(fā)現(xiàn)同一網(wǎng)段或路由可達(dá)能 ping 通是基本前提。這些條件在域環(huán)境里通常由組策略統(tǒng)一配好但在工作組環(huán)境比如實(shí)驗(yàn)室臨時(shí)搭的機(jī)器里得一臺(tái)臺(tái)確認(rèn)。我習(xí)慣先拿一臺(tái)當(dāng)“小白鼠”跑通了再批量。import subprocess def shutdown_one(ip, timeout10): 對(duì)單臺(tái) Windows 主機(jī)發(fā)起遠(yuǎn)程關(guān)機(jī) :param ip: 目標(biāo)主機(jī) IP :param timeout: 子進(jìn)程超時(shí)秒數(shù)防止卡死 :return: (ip, 是否成功, 返回信息) cmd fshutdown /s /t 0 /m \\\\{ip} /f try: # capture_output 捕獲 stdout/stderrtext 讓輸出為字符串 result subprocess.run( cmd, shellTrue, capture_outputTrue, textTrue, timeouttimeout ) if result.returncode 0: return ip, True, 關(guān)機(jī)指令已發(fā)送 else: return ip, False, result.stderr.strip() or result.stdout.strip() except subprocess.TimeoutExpired: return ip, False, 超時(shí)目標(biāo)無響應(yīng)這段代碼是整套腳本的最小單元。shutdown /s表示關(guān)機(jī)/t 0表示立即執(zhí)行延遲 0 秒/m \\IP指定遠(yuǎn)程目標(biāo)/f強(qiáng)制關(guān)閉正在運(yùn)行的程序——不加/f時(shí)如果有未保存的文檔目標(biāo)機(jī)會(huì)彈窗等待遠(yuǎn)程場(chǎng)景下沒人點(diǎn)確認(rèn)就會(huì)一直掛著。timeout參數(shù)是血淚經(jīng)驗(yàn)網(wǎng)絡(luò)不通時(shí)shutdown命令可能卡很久不設(shè)超時(shí)整個(gè)批量任務(wù)會(huì)被一臺(tái)機(jī)器拖死。提示/t 0和/f組合會(huì)直接丟棄未保存內(nèi)容生產(chǎn)環(huán)境慎用測(cè)試機(jī)隨意。3. 從單臺(tái)到批量IP 列表、并發(fā)與日志三件套3.1 IP 來源文件讀取還是網(wǎng)段掃描批量關(guān)機(jī)的第一步是拿到目標(biāo)列表。常見做法有兩種一是維護(hù)一個(gè)hosts.txt每行一個(gè) IP二是給一個(gè)網(wǎng)段先 ping 掃描再關(guān)機(jī)。前者可控后者省事但容易誤傷。我一般用文件方式因?yàn)椤澳男C(jī)器該關(guān)”是個(gè)業(yè)務(wù)判斷不該交給腳本猜。文件格式就一行一個(gè) IP允許#開頭注釋# 渲染組 192.168.1.101 192.168.1.102 # 測(cè)試組 192.168.1.201讀取時(shí)過濾空行和注釋順便去重def load_targets(path): 從文本文件加載 IP 列表忽略空行和 # 注釋 targets [] with open(path, r, encodingutf-8) as f: for line in f: line line.strip() if not line or line.startswith(#): continue targets.append(line) # 去重但保持順序 return list(dict.fromkeys(targets))dict.fromkeys是去重的常用技巧比set好的地方是保留原始順序日志讀起來符合直覺。3.2 并發(fā)控制線程池不是越多越好三十臺(tái)機(jī)器串行關(guān)機(jī)每臺(tái)哪怕只花 2 秒也要一分鐘。用線程池并發(fā)能把時(shí)間壓到幾秒。但線程數(shù)不是越大越好——shutdown命令本身會(huì)創(chuàng)建進(jìn)程線程太多反而搶資源而且目標(biāo)機(jī)可能扛不住同時(shí)涌入的連接。from concurrent.futures import ThreadPoolExecutor, as_completed def shutdown_batch(targets, max_workers8): 并發(fā)批量關(guān)機(jī)max_workers 控制同時(shí)發(fā)起的關(guān)機(jī)數(shù) results [] with ThreadPoolExecutor(max_workersmax_workers) as executor: future_map { executor.submit(shutdown_one, ip): ip for ip in targets } for future in as_completed(future_map): ip future_map[future] try: results.append(future.result()) except Exception as e: results.append((ip, False, f異常: {e})) return resultsmax_workers8是我在幾十臺(tái)規(guī)模下比較穩(wěn)的值。機(jī)器少可以調(diào)到 4上百臺(tái)可以到 16但再高收益遞減還容易觸發(fā)目標(biāo)機(jī)的連接限制。as_completed保證誰先返回先處理誰不會(huì)因?yàn)槟撑_(tái)慢而阻塞日志輸出。3.3 日志與結(jié)果核對(duì)別關(guān)完就完事批量操作最怕“以為都關(guān)了其實(shí)有幾臺(tái)沒關(guān)”。所以每次執(zhí)行都要落日志記錄時(shí)間、IP、成功與否、失敗原因。日志用標(biāo)準(zhǔn)logging模塊同時(shí)輸出到控制臺(tái)和文件import logging def setup_logger(log_fileshutdown.log): logger logging.getLogger(batch_shutdown) logger.setLevel(logging.INFO) fmt logging.Formatter(%(asctime)s [%(levelname)s] %(message)s) # 文件處理器追加模式 fh logging.FileHandler(log_file, encodingutf-8) fh.setFormatter(fmt) # 控制臺(tái)處理器 ch logging.StreamHandler() ch.setFormatter(fmt) logger.addHandler(fh) logger.addHandler(ch) return logger執(zhí)行完打印一個(gè)匯總表成功多少、失敗多少、失敗的是哪些 IP。失敗列表可以直接復(fù)制出來排查后重跑——腳本要支持“只對(duì)失敗的重試”而不是每次全量重來。注意日志文件別放在會(huì)被關(guān)機(jī)影響的機(jī)器上發(fā)起端自己別被關(guān)進(jìn)去。4. 避坑與排查遠(yuǎn)程關(guān)機(jī)最常見的五類翻車4.1 現(xiàn)象提示“拒絕訪問(5)”原因發(fā)起端賬號(hào)不是目標(biāo)機(jī)管理員或目標(biāo)機(jī)禁用了空密碼遠(yuǎn)程登錄或 Remote Registry 服務(wù)沒啟動(dòng)。解決確認(rèn)賬號(hào)在目標(biāo)機(jī)Administrators組用net use \\IP\IPC$ /user:管理員賬號(hào)先建立憑據(jù)會(huì)話再關(guān)機(jī)檢查組策略“賬戶使用空密碼的本地賬戶只允許進(jìn)行控制臺(tái)登錄”是否被啟用。4.2 現(xiàn)象命令返回成功但機(jī)器沒關(guān)原因shutdown返回 0 只代表指令送達(dá)不代表目標(biāo)機(jī)執(zhí)行了。目標(biāo)機(jī)可能因?yàn)橛写4嫖臋n、有程序阻止關(guān)機(jī)、或電源策略攔截。解決加/f強(qiáng)制關(guān)閉如果仍不關(guān)去目標(biāo)機(jī)事件查看器看“系統(tǒng)”日志里User32或Kernel-Power的記錄確認(rèn)目標(biāo)機(jī)沒有處于“演示模式”或接了不允許關(guān)機(jī)的策略。4.3 現(xiàn)象部分機(jī)器超時(shí)腳本卡住原因網(wǎng)絡(luò)不通或目標(biāo)機(jī)防火墻攔截shutdown命令在等 RPC 響應(yīng)。解決subprocess.run必須帶timeout批量前先并發(fā) ping 一輪把不在線的剔掉減少無效等待。4.4 現(xiàn)象關(guān)機(jī)后無法再次遠(yuǎn)程喚醒原因這是關(guān)機(jī)不是重啟Wake-on-LAN 需要網(wǎng)卡和主板支持且關(guān)機(jī)后網(wǎng)卡仍供電。解決如果后續(xù)要遠(yuǎn)程開機(jī)關(guān)機(jī)前確認(rèn)目標(biāo)機(jī) BIOS 和網(wǎng)卡驅(qū)動(dòng)開啟了 WOL否則只能現(xiàn)場(chǎng)開機(jī)。這個(gè)坑我在實(shí)驗(yàn)室踩過關(guān)得爽第二天全員現(xiàn)場(chǎng)按電源鍵。4.5 現(xiàn)象殺軟或安全軟件攔截 shutdown 調(diào)用原因部分終端防護(hù)會(huì)把遠(yuǎn)程shutdown當(dāng)成可疑行為。解決在目標(biāo)機(jī)安全軟件里把發(fā)起端 IP 或shutdown.exe加入白名單或者改用 WMI 方式但 WMI 同樣可能被攔。沒有銀彈只能按環(huán)境調(diào)。5. 進(jìn)階把關(guān)機(jī)做成可調(diào)度、可回滾的例行任務(wù)5.1 用計(jì)劃任務(wù)做定時(shí)批量關(guān)機(jī)腳本跑通后下一步是讓它自動(dòng)在固定時(shí)間執(zhí)行。Windows 任務(wù)計(jì)劃程序里建一個(gè)基本任務(wù)觸發(fā)器設(shè)為每天 23:30操作設(shè)為python.exe D:\scripts\batch_shutdown.py起始目錄填腳本所在目錄。注意“不管用戶是否登錄都要運(yùn)行”要勾上否則鎖屏后不執(zhí)行。5.2 加一個(gè)“預(yù)演模式”再真關(guān)直接批量關(guān)機(jī)風(fēng)險(xiǎn)高我后來給腳本加了一個(gè)--dry-run參數(shù)只 ping 和打印將要關(guān)機(jī)的列表不真正調(diào)用shutdown。確認(rèn)列表無誤后再去掉參數(shù)執(zhí)行。這個(gè)習(xí)慣救過我好幾次——有一次 IP 文件里混進(jìn)了一臺(tái)正在跑重要任務(wù)的機(jī)器預(yù)演時(shí)看到了及時(shí)刪掉。import argparse def parse_args(): parser argparse.ArgumentParser(descriptionWindows 批量遠(yuǎn)程關(guān)機(jī)) parser.add_argument(--file, defaulthosts.txt, helpIP 列表文件) parser.add_argument(--workers, typeint, default8, help并發(fā)數(shù)) parser.add_argument(--dry-run, actionstore_true, help只預(yù)演不關(guān)機(jī)) return parser.parse_args()argparse讓腳本從“改代碼調(diào)參數(shù)”變成“命令行傳參”配合計(jì)劃任務(wù)和手動(dòng)執(zhí)行都方便。--dry-run是布爾開關(guān)不加就是真關(guān)加了就只打印。5.3 驗(yàn)證關(guān)機(jī)結(jié)果ping 輪詢確認(rèn)指令發(fā)出去不等于關(guān)掉了。執(zhí)行完等 30 秒再對(duì)目標(biāo)列表做一輪 ping還在線的就是沒關(guān)成功的單獨(dú)列出來。這個(gè)“發(fā)指令→等待→驗(yàn)證”的閉環(huán)比只看shutdown返回碼可靠得多。import subprocess def check_offline(ip): ping 一次返回 True 表示已離線關(guān)機(jī)成功 # -n 1 發(fā)一個(gè)包-w 1000 超時(shí) 1 秒 result subprocess.run( fping -n 1 -w 1000 {ip}, shellTrue, capture_outputTrue, textTrue ) return result.returncode ! 0ping返回非 0 通常意味著目標(biāo)不可達(dá)結(jié)合關(guān)機(jī)場(chǎng)景可以判定為已關(guān)機(jī)。但要注意有些機(jī)器關(guān)機(jī)后網(wǎng)卡仍響應(yīng) ARPping 可能還通這時(shí)以shutdown返回碼和事件日志為準(zhǔn)。從那以后我每次批量關(guān)機(jī)前都強(qiáng)制走一遍--dry-run確認(rèn)列表、確認(rèn)時(shí)間、確認(rèn)沒有漏網(wǎng)的重要機(jī)器再真刀真槍執(zhí)行。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取