筆者有一陣子接 soho 時,鑑於手邊硬體不夠強大,所以直接跑網咖做開發 ( 聽說在網咖寫程式是很奇怪的一件事 ),然後每次跑網咖的時候都要去 download  四、五樣開發軟體與工具,再安裝,後來覺得很浪費時間,於是自己弄了一份自動安裝包出來。

 

SendMessage OR PostMessage

 

自動安裝包的概念是直接拿官網所釋放的安裝檔,主要用 FindWindow、PostMessage  這些 API 進行自動安裝,安裝速度比一般用人工安裝快些,但以這種機制進行會有些缺點出現,關鍵在於 SendMessage 與 PostMessage 之問題。

SendMessage 本身會一直待在 message queue 裡面,等被拿走之後才又繼續跑下個指令;PostMessage 是將訊息放到 queue 裡面就直接閃人不管。一開始在設計這部份程式時用的是 SendMessage,但後來容易發生卡死的情況,於是改用 PostMessage;而 PostMessage 又沒辦法確定到底是不是被正常接收、處理掉,個人認為算是兩難的局面。

由於不想在這個小工具上花費太長的時間,不然就失去當時開發的目的 ( 開發時間大於人工操作時間不就不划算了嗎 ) ,故最後全用 PostMessage 外,還在「適當」的時候加上 Sleep 等待動作,但 Sleep 個人認為是較不智的做法,因該延遲多久,這變數實在是太大的了。

 

Sample 

 

這裡 Sample 以 Code::Blocks 10.05 ( windows ) with Mingw 為例,但不會放上 main ( 主因是懶得截取專案部份程式碼 )。

xWnd.h / xWnd.cpp 之設計模式,主要仿一些屬 script language ,如 AutoHotkey、AutoIt、按鍵精靈的設計概念,所以沒打算包成 class,也由於是短期開發,所以 xWnd.h / xWnd.cpp 還有很多可擴充的地方 ( 像是 SetText,建議用 SendMessage / PostMessage with WM_TEXT 做,用 SetWindowText 效果沒很好 )。

這個 sample 跳過一個部份 : Toolbar,事實上要對 tool bar 下訊息也行 ( 這方面網路上現有資源似乎較少 ),有機會開發時再放上。 

 

Code Snippet
  1.  
  2. /**
  3. * @file xWnd.h
  4. * @brief declare some functions about AutoSetup
  5. * @author EdisonX < Edison.Shih >
  6. * @date 20120307
  7.     */
  8. #ifndef X_WND_H__
  9. #define X_WND_H__
  10.  
  11. #include <windows.h>
  12. HWND WaitWnd(char* name, int times=100, DWORD td=50, char* Class=NULL);
  13. HWND WaitWndEx(HWND hmain, char* name, int times=100, DWORD td=50, char* Class=NULL);
  14. HWND WaitWndEx(HWND hmain, DWORD ID, int times=100, DWORD td=50);
  15.  
  16. void Click(HWND hwnd, DWORD td=50);
  17. void SetCheck(HWND hwnd, DWORD ID, BOOL Checked, DWORD td=50);
  18.  
  19. void ArrowUp(HWND hwnd, DWORD times=1, DWORD td=50);
  20. void ArrowDown(HWND hwnd, DWORD times=1, DWORD td=50);
  21. void SendEnter(HWND hwnd, DWORD times=1, DWORD td=50);
  22.  
  23. void SetCheck(HWND hwnd, BOOL Checked);
  24. #endif

 

Code Snippet
  1.  
  2. /**
  3. * @file xWnd.cpp
  4. * @brief Implement some functions about AutoSetup
  5. * @author EdisonX < Edison.Shih >
  6. * @date 20120307
  7.     */
  8.  
  9. #include "xWnd.h"
  10. #include <iostream>
  11. using namespace std;
  12.  
  13. /////////////////////////////////////////////////////
  14. HWND WaitWnd(char* name, int times,
  15.              DWORD td, char* Class)
  16. {
  17.     HWND hwnd = FindWindow(Class, name);
  18.     for(int i=0; i<times; ++i){
  19.         hwnd = FindWindow(Class, name);
  20.         if(hwnd!=NULL) return hwnd;
  21.         Sleep(td);
  22.     }
  23.     cout << "can't find " << name << " window\n";
  24.     return NULL;
  25. }
  26.  
  27. /////////////////////////////////////////////////////
  28. HWND WaitWndEx(HWND hmain, char* name, int times,
  29.                DWORD td, char* Class)
  30. {
  31.     if(hmain==NULL) {
  32.         cout << " hmain is NULL < from WaitWndEx >\n";
  33.         return NULL;
  34.     }
  35.     HWND hsub = FindWindowEx(hmain,NULL, Class, name);
  36.     for(int i=0; i<times; ++i){
  37.         hsub = FindWindowEx(hmain,NULL, Class, name);
  38.         if(hsub!=NULL) return hsub;
  39.         Sleep(td);
  40.     }
  41.     cout << "can't find " << name << " window\n";
  42.     return NULL;
  43. }
  44.  
  45.  
  46. /////////////////////////////////////////////////////
  47. HWND WaitWndEx(HWND hmain, DWORD ID, int times, DWORD td)
  48. {
  49.     if(hmain==NULL) {
  50.         cout << " hmain is NULL < from WaitWndEx >\n";
  51.         return NULL;
  52.     }
  53.     HWND hsub = GetDlgItem(hmain, ID);
  54.     for(int i=0; i<times; ++i){
  55.         hsub = GetDlgItem(hmain, ID);
  56.         if(hsub!=NULL) return hsub;
  57.         Sleep(td);
  58.     }
  59.     cout << "can't find ID : " <<ID << " \n";
  60.     return NULL;
  61. }
  62. /////////////////////////////////////////////////////
  63. void Click(HWND hwnd, DWORD td)
  64. {
  65.     if(hwnd==NULL) {
  66.         cout << " hwnd is NULL < from Click >\n";
  67.         return;
  68.     }
  69.     Sleep(td);    PostMessage(hwnd, WM_LBUTTONDOWN, NULL, NULL);
  70.     Sleep(td);    PostMessage(hwnd, WM_LBUTTONUP, NULL, NULL);
  71.     Sleep(td);
  72. }
  73. /////////////////////////////////////////////////////
  74. void SetCheck(HWND hwnd, DWORD ID, BOOL Checked, DWORD td)
  75. {
  76.     if(hwnd==NULL) {
  77.         cout << " hwnd is NULL < from SetChect > \n";
  78.         return ;
  79.     }
  80.     CheckDlgButton(hwnd, ID,Checked? BST_CHECKED :BST_UNCHECKED);
  81.     // HWND hsub = GetDlgItem(hwnd, ID);
  82.     // PostMessage(hsub, BM_SETCHECK,Checked? BST_CHECKED :BST_UNCHECKED, 0);
  83.     Sleep(td);
  84. }
  85. /////////////////////////////////////////////////////
  86. void ArrowUp(HWND hwnd, DWORD times, DWORD td)
  87. {
  88.     if(hwnd==NULL) {
  89.         cout << " hwnd is NULL < from ArrowUp >\n";
  90.         return;
  91.     }
  92.     for(DWORD i=0; i<times; ++i){
  93.         Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_UP, NULL);
  94.         Sleep(td); PostMessage(hwnd, WM_KEYUP  , VK_UP, NULL);
  95.     }
  96.     Sleep(td);
  97. }
  98.  
  99. /////////////////////////////////////////////////////
  100. void ArrowDown(HWND hwnd, DWORD times,DWORD td)
  101. {
  102.     if(hwnd==NULL) {
  103.         cout << " hwnd is NULL < from ArrowDown >\n";
  104.         return;
  105.     }
  106.     for(DWORD i=0; i<times; ++i){
  107.         Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_DOWN, NULL);
  108.         Sleep(td); PostMessage(hwnd, WM_KEYUP  , VK_DOWN, NULL);
  109.     }
  110.     Sleep(td);
  111. }
  112. /////////////////////////////////////////////////////
  113. void SendEnter(HWND hwnd,DWORD times, DWORD td)
  114. {
  115.     if(hwnd==NULL) {
  116.         cout << " hwnd is NULL < from SendEnter >\n";
  117.         return;
  118.     }
  119.     for(DWORD i=0; i<times; ++i){
  120.         Sleep(td); PostMessage(hwnd, WM_KEYDOWN, VK_RETURN, NULL);
  121.         Sleep(td); PostMessage(hwnd, WM_KEYUP  , VK_RETURN, NULL);
  122.     }
  123.     Sleep(td);
  124. }

 

Code Snippet
  1. /**
  2. * @file AppSetup.h
  3. * @brief some application software auto setup
  4. * @author EdisonX < Edison.Shih >
  5. * @date 20120307
  6. */
  7.  
  8. #ifndef APP_SETUP_H__
  9. #define APP_SETUP_H__
  10.  
  11. void setup_cb1005mingw();
  12. void setup_devcpp4992();
  13. void setup_pcman2007();
  14. void setup_vc2010express();
  15. void setup_pspad();
  16. void setup_7z();
  17. void setup_XNResource();
  18. void setup_VerySleepy();
  19. void setup_SourceMonitor();
  20.  
  21. #endif

 

Code Snippet
  1. /**
  2. * @file setup_cb1005mingw.cpp
  3. * @brief auto setup code::blocks 10.05 with mingw
  4. * @author EdisonX < Edison.Shih >
  5.     */
  6.  
  7. #include "AppSetup.h"
  8. #include "xWnd.h"
  9.  
  10. void setup_cb1005mingw()
  11. {
  12.     HWND hwnd, hsub, hsub2;
  13.     WinExec("codeblocks-10.05mingw-setup.exe", SW_SHOW);
  14.     Sleep(2000);
  15.  
  16.     // [1] "CodeBlocks Setup" (0) , "&Next >" (1)
  17.     hwnd = WaitWnd("CodeBlocks Setup");
  18.     hsub = WaitWndEx(hwnd, 1);
  19.     Click(hsub);
  20.  
  21.     // [2] "CodeBlocks Setup " (0), I &Agree (1)
  22.     hwnd = WaitWnd("CodeBlocks Setup ");
  23.     hsub = WaitWndEx (hwnd, 1);
  24.     Click(hsub);
  25.  
  26.     // [3] "CodeBlocks Setup " (0), < ""  "#32770" > , Custom (1017) up*4
  27.     // puts("[3]");
  28.     hwnd = WaitWnd("CodeBlocks Setup ");
  29.     hsub = WaitWndEx(hwnd, "", 100, 50, "#32770");
  30.     hsub2 = WaitWndEx(hsub, 1017);
  31.     ArrowUp(hsub2, 4);
  32.  
  33.     // [4] "CodeBlocks Setup" (0), "&Next >" (1)
  34.     hwnd = WaitWnd("CodeBlocks Setup ");
  35.     hsub = WaitWndEx(hwnd, 1);
  36.     Click(hsub);
  37.  
  38.     // [5] "CodeBlocks Setup " (0), "&Install" (1)
  39.     hwnd = WaitWnd("CodeBlocks Setup ");
  40.     hsub = WaitWndEx(hwnd, 1);
  41.     Click(hsub);
  42.  
  43.     // [6] msgbox, "CodeBlocks Setup" (0), 是(&Y)  (6)  否(&N)  (7)
  44.     hwnd = WaitWnd("CodeBlocks Setup", 500, 1000);
  45.     hsub =WaitWndEx(hwnd, 7);
  46.     Click(hsub);
  47.  
  48.     // [7] "CodeBlocks Setup " (0), "&Next >" (1)
  49.     hwnd = WaitWnd("CodeBlocks Setup ");
  50.     hsub = WaitWndEx(hwnd, 1);
  51.     Click(hsub);
  52.  
  53.     // [8] "CodeBlocks Setup " (0), "&Finish" (1)
  54.  
  55.     hwnd = WaitWnd("CodeBlocks Setup ");
  56.     hsub = WaitWndEx(hwnd, 1);
  57.     Click(hsub);
  58. }

 

Win Spy Tools and APIs

 

原理


這類型 tool 原理大多分兩種,一種是用 WindowFromPoint 去取得 hwnd,再用 GetParent 取得母視窗相關資訊,這也是大多採取的方式。

另一種是較少見 ( 其實筆者沒見過基於這概念的軟體 ) 是用 EnumWindowsEnumChildWindows 去列舉所有視窗與其訊息,較少見的原因是它較難和看到的子控制項目做為結合。個人認為它有存在的必要,原因是極少數 ( 可能會發生於科技廠 release 給 clinet 的 tool ) 應用程式,有些不想讓 client 看到的功能、子控制,故意 create 在 Dialog 可視範圍之外,沒用 EnumChildWindows 根本抓不到。

 

軟體


WInSpy

筆者用的是早期所下載的 WInSpy ( 圖示是一隻蝴蝶,作者為 Robert Kuster ),它很小型,但它被大多網站判定為高危險的病毒程式。到現在筆者還是較習慣用它,原因是筆者要求截取的資訊必須含有 handle、ID、text、class,且還必須含 parent window 訊息,雖然找不到編好的執行檔,但幸運的是,還是找得到粗略的 source ,同時作者對 Win Spy 留有一些 紀錄 (有講 Inject 技術 ),有興趣可前往查閱,欲下載的話點進去後可選  Download WinSpy - 20 Kb (demo application) ,註明,這系列文章曾在 codeproject 上發表過。

 

VS

VS 本身也有,以2010 為例,在「開始」->「Microsoft Visual Studio 2010」-> 「Visual Studio Tools」-> Spy++,Alt + F3 就是我們要的功能,但在 2010 裡面它沒辦法顯示 parent 訊息,故筆者認為較不好用 ( 沒記錯的話,早期 VC6 附的 Spy++ 可顯示 parent 訊息 )。

 

AutoIt Window Info

這是 autoit 這套 script language 額外附的 tools ,有裝 AutoIt 之使用者開啟 IDE (SciTE) 後按下 Ctrl + F6 便可使用,但不知道有沒有提供單獨下載。筆者稱讚它的原因是,它是少數 Win Spy tool 可以抓到 tool bar command ID 的工具  ( 雖然對應沒做得很好 )。

  

封裝

 

Auto Setup 寫完後,拿到其他電腦就至少需要兩個檔案,一個是真正的 setup.exe ,一個是 hook.exe。怎解決這問題?

問題敘述大概是這樣,尋問了另一位常寫自動安裝的網友才解決。答案是:把它們全都壓縮在一起 ( 搞了半天是軟體問題 )。

選取兩個要壓縮的執行檔,WinRar 下選擇

 

1. 一般設定 -> 建立自我解壓縮。

2. 進階設定-> 自解檔選項。

3. 切到「安裝」,解壓縮後執行輸入「hook.exe」。

4. 切到「模式」,暫存模式 勾選「解封裝至暫存資料夾」,安靜模式 勾選「全部隱藏」。

5. 切到「文字與圖示」,我選了一份 icon。

6. 切到「更新」,更新模式選「解壓縮並取代檔案」,覆寫模式選「覆寫所有檔案」。

 

重新壓好之 exe ,丟到 client 端執行,自動解壓後就會自動執行 hook.exe。當然 Winrar 不是免費軟體,另一免費方案是用 7z ,但 7z 純用 command 要完成這動作比 Winrar 困難許多 , 所以有開發一些 7z 自動解壓工具,筆者這裡沒再深入,有興趣可自行研究。

edisonx 發表在 痞客邦 PIXNET 留言(2) 人氣()


留言列表 (2)

發表留言
  • novus
  • 我沒有做過這方面的嘗試,只是有時候會覺得你解決問題的思路挺特別的...

    很多安裝軟體,如 msi、InstallShield、NSIS等等,都可以從命令列給定安裝參數,然後用 quiet 模式執行,就不需要對話方塊了。這是大概是正常人會嘗試的方向。

    不過透過 msi 安裝,最大的速度魔王應該還是Windows Installer,花的時間會是單純解壓、複製檔案的好幾倍,原因是Windows Installer會建立一連串的系統還原點並且頻繁使用 registry 儲存資料。這些額外的動作對網咖電腦顯然沒有任何意義

    要快的話,我覺得可能的方向是盡不要透過安裝軟體,事實上有很多東西是可以直接複製過去就可以用,但這點要慢慢嘗試。

    如果是 Linux 的話雖然很繁瑣,但是還有終極武器chroot可以用...很可惜 Windows沒有類似的東西
  • 疑!這麼講才發現我豬頭了。
    不少安裝軟體 (setup.exe) 可以用 7z 解壓縮,檔案複製過去後,大概就剩環境變數與登錄檔,對沒有 cmd.exe 的環境可能要先把 xcopy.exe registry.exe 類似的執行檔找出來。
    最後比較納悶的是, InstallShield、NSIS 類似軟體應是用來制作安裝選單,這部份有辦法再針對執行檔做 cmd line 安裝動作嗎?應是當初 coder 設計是否有考量沒錯吧?

    最後謝謝您的指教,收穫良多。

    edisonx 於 2012/07/01 13:29 回覆

  • novus
  • 我可能沒講清楚,用 NSIS 或 InstallShield 製作出來的安裝檔Setup.exe,都可以接受某些命令列參數,並且提供若干安裝模式(例如對話方塊或命令列)。例如 InstallShield 產生的 setup.exe 可以先在 GUI 模式下錄製使用者安裝選擇,然後之後用 silent mode 執行。如果底層使用 msi 封裝,InstallShield 還可以轉參數給 msiexec。

    反正你查查各家的 quiet/silent mode 就知道怎麼用。應該不難理解,這些自動化功能對軟體測試人員屬於非常基本的需求,如果沒有的話才真的很奇怪。

    不過在怎麼自動化,遇到蝸牛級的Windows Installer仍然沒轍,Windows Installer花太多時間在做一些與安裝軟體無關的工作,對於隨用即丟環境完全沒有意義

    真的要效率的話,我總覺得可以用某種虛擬機制,把所有的軟體事先安裝在虛擬環境上,複製過去就可以直接使用,連 registry 都不用操心。在 Linux 有 chroot 這個幾乎沒有 overhead 的虛擬環境,Widows 我就不知道有什麼方法了
  • 了解了,謝謝您的回覆,我再查一下相關資料,感謝 :)

    edisonx 於 2012/07/01 15:01 回覆

您尚未登入,將以訪客身份留言。亦可以上方服務帳號登入留言

請輸入暱稱 ( 最多顯示 6 個中文字元 )

請輸入標題 ( 最多顯示 9 個中文字元 )

請輸入內容 ( 最多 140 個中文字元 )

請輸入左方認證碼:

看不懂,換張圖

請輸入驗證碼