2026年5月20日

 windows 10, 如何使用 batch , vbs 將桌面上的視窗移至特定位置?
這些腳本語言缺乏直接控制 Windows GUI 視窗 API(如 MoveWindow)的能力。

powershell 腳本過於複雜,有其它方法嗎?

如果您覺得 PowerShell 的寫法太複雜,希望有更直覺、程式碼更短的方法,最標準的解決方案是使用第三方小工具
我們可以使用 Windows 社群中最著名的開源命令列視窗控制工具:CmdowNirCmd。下載後,您只需要在 Batch (.bat) 裡面寫一行指令就能精準移動視窗。
以下是這兩種最簡單的方法:

方法一:使用 Cmdow(最推薦,語法最直覺)

cmdow 是一個專門用來管理 Windows 視窗的免費命令列工具(僅約 30KB)。
  1. 下載:前往官方或 GitHub 下載 cmdow.exe
  2. 編寫 Batch:將 cmdow.exe 跟您的 .bat 檔放在同一個資料夾,寫下這行指令:
@echo off
:: 語法:cmdow "視窗標題" /MOV X座標 Y座標 寬度 高度
cmdow "未命名 - 記事本" /MOV 100 100 800 600
  • 優點:指令極短,直接指定「視窗標題」和「位置大小」即可。

方法二:使用 NirCmd(功能最強大的萬用工具)

NirCmd 是老牌系統微調工具,它同樣支援一行指令移動視窗。
  1. 下載:前往 NirSoft 官網下載 nircmd.exe
  2. 編寫 Batch:將 nircmd.exe.bat 放在一起,寫入以下指令:
@echo off
:: 語法:nircmd win setsize title "視窗標題" X座標 Y座標 寬度 高度
nircmd win setsize title "未命名 - 記事本" 100 100 800 600
  • 延伸玩法:如果您想把視窗「置中」,它甚至支援 nircmd win center title "視窗標題"

方法三:如果您堅持「不下載任何檔案」的純 VBScript 法

如果您因為公司資安規定不能下載任何 exe 執行檔,也不想用複雜的 PowerShell,那纯 VBScript 唯一的簡單做法是透過模擬鍵盤,將視窗直接最大化或最小化(無法指定精確的 X, Y 座標,但程式碼只有 4 行):
建立 maximize.vbs
Set WshShell = CreateObject("WScript.Shell")
WshShell.AppActivate "記事本"      ' 填入視窗標題
WScript.Sleep 200                 ' 等待視窗聚焦
WshShell.SendKeys "%{SPACE}x"     ' 模擬按下 Alt+Space,再按 X (最大化)
(如果要最小化,請將最後一行的 x 改為 n)

如果您想試試看第三方工具的方法,可以告訴我:
  • 您是否允許在電腦下載/放置 cmdow.exenircmd.exe 這些綠色免安裝小工具?
  • 您的目標視窗標題是否固定?(例如每次打開都叫同一個名字?)
我可以提供您直接打包好的檔案下載路徑與完整寫法。
不要執行檔。給我一個 vc6 可以執行的 C 程式

如果您不想下載外部執行檔,使用 VC6(Visual C++ 6.0)編寫一個簡單的 C 語言程式,直接呼叫 Windows API 絕對是最精準且程式碼最乾淨的方法。
VC6 雖然古老,但它內建的 windows.h 完全支援 FindWindowMoveWindow 這兩個標準 API。

C 語言原始碼

請在 VC6 中建立一個 Win32 Console Application(主控台應用程式),並將以下程式碼貼入您的 .c 檔案中:
#include <windows.h>
#include <stdio.h>

int main() {
    // 1. 設定您要尋找的視窗標題(必須完全相同,例如:未命名 - 記事本)
    const char* windowTitle = "未命名 - 記事本"; 
    
    // 2. 設定目標位置與大小
    int x = 100;      // 畫面左上角 X 座標
    int y = 100;      // 畫面左上角 Y 座標
    int width = 800;  // 視窗寬度
    int height = 600; // 視窗高度

    // 3. 尋找視窗控制代碼 (HWND)
    HWND hWnd = FindWindow(NULL, windowTitle);

    if (hWnd == NULL) {
        printf("錯誤:找不到標題為 [%s] 的視窗。\n", windowTitle);
        return 1;
    }

    // 4. 移動並調整視窗大小 (最後一個參數 TRUE 代表移動後重新繪製視窗)
    if (MoveWindow(hWnd, x, y, width, height, TRUE)) {
        printf("成功:已將視窗移至 (%d, %d),大小改為 %dx%d。\n", x, y, width, height);
    } else {
        printf("錯誤:無法移動視窗。\n");
    }

    return 0;
}

在 VC6 中的編譯與使用步驟

  1. 打開 VC6,點選功能表的 File -> New
  2. 切換到 Projects 標籤,選擇 Win32 Console Application
  3. 輸入專案名稱(例如 MoveWin),點選 OK,接著選擇 An empty project 後點 Finish
  4. 再次點選 File -> New,切換到 Files 標籤,選擇 C++ Source File(檔名輸入 main.c,副檔名用 .c 即可)。
  5. 將上面的程式碼貼入,修改 windowTitle 為您目前桌面上正開啟的視窗名稱。
  6. 按下鍵盤 F7(Build)進行編譯,再按下 Ctrl + F5(Execute)執行。

如果您的目標視窗標題會變動,請告訴我:
  • 該視窗的標題是否包含動態字串?(例如瀏覽器分頁名稱會一直變)
  • 您是否需要改用處理程序名稱(如 notepad.exe)來抓取視窗?
我可以幫您修改程式碼,改用 EnumWindows API 來進行模糊搜尋。
如何找出桌面上所有視窗的 title?
在 C 語言中,要找出桌面上所有可見視窗的標題,不能只用 FindWindow,必須使用 EnumWindows API 來遍歷(列舉)系統中的所有頂層視窗。
以下是可以在 VC6(Visual C++ 6.0)中直接編譯並執行的 C 程式碼。

C 語言原始碼

請在 VC6 的 .c 檔案中貼入以下程式碼:
#include <windows.h>
#include <stdio.h>

// 這是回呼函式(Callback Function),每找到一個視窗,Windows 就會自動呼叫它一次
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) {
    char windowTitle[512];

    // 1. 檢查視窗是否可見(過濾掉系統後台隱藏的無形視窗)
    if (IsWindowVisible(hWnd)) {
        
        // 2. 取得視窗標題,存入 windowTitle 陣列中
        // GetWindowText 返回值大於 0 代表成功取得標題(過濾掉沒有標題的視窗)
        if (GetWindowText(hWnd, windowTitle, sizeof(windowTitle)) > 0) {
            
            // 3. 排除 Windows 10 常見的空白標題或非實體視窗
            if (strcmp(windowTitle, "Program Manager") != 0 && strcmp(windowTitle, "Start") != 0) {
                printf("HWND: 0x%08X | 標題: %s\n", (unsigned int)hWnd, windowTitle);
            }
        }
    }
    return TRUE; // 返回 TRUE 代表繼續尋找下一個視窗;返回 FALSE 則停止列舉
}

int main() {
    printf("--- 開始列舉桌面所有可見視窗 ---\n\n");

    // 呼叫 API,傳入回呼函式的名稱
    EnumWindows(EnumWindowsProc, 0);

    printf("\n--- 列舉結束 ---\n");
    
    // 讓 Console 視窗暫停,方便觀看結果
    system("pause"); 
    return 0;
}

程式碼關鍵解析(為什麼這樣寫?)

  1. EnumWindows:這個 API 會從畫面的最上層開始,把每一個視窗的「控制代碼(HWND)」輪流丟給 EnumWindowsProc 處理。
  2. IsWindowVisible:Windows 系統後台有無數個看不見的隱藏視窗(例如事件監聽、系統服務)。加上此判斷,可以確保只抓到你肉眼在桌面上看得到的視窗
  3. GetWindowText:用來把視窗的 HWND 轉換成人類看得懂的 String(標題字串)。

如果您觀察輸出結果,可以告訴我:
  • 您的目標視窗是否成功出現在列表上
  • 您接下來是否想把這個功能與上一步結合,寫成「輸入部分關鍵字,就自動移動該視窗」的自動化程式?
承上,如何知道 各 windows 本身的程式名稱?
要從視窗控制代碼(HWND)取得該視窗所屬的程式名稱(例如 notepad.exe,在 Windows API 中需要透過以下三個步驟:
  1. 使用 GetWindowThreadProcessId 透過 HWND 取得該視窗的行程識別碼(Process ID, PID)
  2. 使用 OpenProcess 透過 PID 開啟該行程並取得權限控制代碼(hProcess)。
  3. 使用 GetModuleFileNameEx 取得該行程的完整執行檔路徑,再從中切出檔名。 [1, 2]
由於 VC6(Visual C++ 6.0)非常古老,直接使用 psapi.h 容易遇到 SDK 版本不相容或缺少 psapi.lib 的連結錯誤。為了確保完全不依賴專案設定、不需要手動加入內含庫(Library),最安全且相容性最高的方法是使用 LoadLibrary 動態載入 psapi.dll。 [3, 4]

C 語言原始碼

請將以下程式碼直接覆蓋到您 VC6 的 .c 檔案中:
#include <windows.h>
#include <stdio.h>

// 定義 GetModuleFileNameEx 的函式指標型態,方便動態載入
typedef DWORD (WINAPI *pfnGetModuleFileNameExA)(HANDLE hProcess, HMODULE hModule, LPSTR lpFilename, DWORD nSize);

// 全域變數,用來存放動態載入的 API 函式指標
pfnGetModuleFileNameExA myGetModuleFileNameEx = NULL;

// 回呼函式:每找到一個視窗就執行一次
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) {
    char windowTitle[256];
    char processPath[MAX_PATH];
    char* exeName = NULL;
    DWORD processId = 0;
    HANDLE hProcess = NULL;

    // 1. 只抓取畫面上看得見的頂層視窗
    if (IsWindowVisible(hWnd)) {
        
        // 2. 取得視窗標題,若沒有標題則跳過
        if (GetWindowText(hWnd, windowTitle, sizeof(windowTitle)) > 0) {
            
            // 排除系統內建的特殊虛擬視窗
            if (strcmp(windowTitle, "Program Manager") == 0 || strcmp(windowTitle, "Start") == 0) {
                return TRUE;
            }

            // 3. 關鍵步驟:透過 HWND 取得該視窗的 Process ID (PID)
            GetWindowThreadProcessId(hWnd, &processId);

            // 4. 透過 PID 開啟行程,取得讀取資訊的權限
            hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
            
            if (hProcess != NULL) {
                // 5. 呼叫動態載入的 API 取得程式的完整路徑
                if (myGetModuleFileNameEx && myGetModuleFileNameEx(hProcess, NULL, processPath, sizeof(processPath)) > 0) {
                    
                    // 從完整路徑(如 C:\Windows\notepad.exe)中,切出最後的檔名
                    exeName = strrchr(processPath, '\\');
                    if (exeName != NULL) {
                        exeName++; // 移除斜線,只留 "notepad.exe"
                    } else {
                        exeName = processPath;
                    }

                    // 6. 印出結果:程式名稱、PID、以及視窗標題
                    printf("[%s] (PID: %d) -> 標題: %s\n", exeName, processId, windowTitle);
                }
                CloseHandle(hProcess); // 記得關閉控制代碼以釋放資源
            }
        }
    }
    return TRUE; // 繼續列舉下一個視窗
}

int main() {
    // 動態載入 Windows 系統內建的 psapi.dll,避免 VC6 編譯時缺少 lib 報錯
    HMODULE hPsapi = LoadLibrary("psapi.dll");
    if (hPsapi == NULL) {
        printf("錯誤:無法載入 psapi.dll\n");
        return 1;
    }

    // 從 dll 中抓取 GetModuleFileNameExA 函式的位址
    myGetModuleFileNameEx = (pfnGetModuleFileNameExA)GetProcAddress(hPsapi, "GetModuleFileNameExA");
    if (myGetModuleFileNameEx == NULL) {
        printf("錯誤:找不到 GetModuleFileNameExA 函式\n");
        FreeLibrary(hPsapi);
        return 1;
    }

    printf("--- 開始列舉桌面視窗與對應程式 ---\n\n");

    // 開始遍歷桌面所有視窗
    EnumWindows(EnumWindowsProc, 0);

    printf("\n--- 列舉結束 ---\n");

    // 釋放載入的 DLL
    FreeLibrary(hPsapi);
    
    system("pause");
    return 0;
}

程式執行效果範例

執行後,您的 Console 視窗會呈現類似以下的精準對應表:
--- 開始列舉桌面視窗與對應程式 ---

[notepad.exe] (PID: 14320) -> 標題: 未命名 - 記事本
[chrome.exe] (PID: 8844) -> 標題: Google Chrome
[explorer.exe] (PID: 3412) -> 標題: 下載
[Discord.exe] (PID: 12056) -> 標題: Discord

--- 列舉結束 ---

為什麼這段程式可以在 VC6 完美執行?

傳統在 VC6 呼叫 GetModuleFileNameEx 時,必須在專案設定的 Link 頁面手動輸入 psapi.lib,否則編譯會噴出 LNK2001: unresolved external symbol 的錯誤。本程式採用 Runtime 動態載入(LoadLibrary + GetProcAddress,徹底繞過了這個老舊編譯器的痛點,不需要修改任何 VC6 的專案設定即可直接編譯通過。 [3]

現在您已經能同時掌握「視窗標題」與「程式名稱(如 notepad.exe)」了,請問:
  • 您是否需要將這個邏輯結合,改成「只要發現 chrome.exe 開啟,就自動把它移到特定位置」的自動化工具?
  • 還是您有遇到特定視窗(例如多螢幕環境下)名稱抓不到的問題?


沒有留言: