關鍵字:
C#,API,FindWindow,FindWindowEx,SendMessage,進程,注冊表
設計初衷:
公司為了便於網絡管理,使用了IEEE 802.1X的網絡訪問控制,這樣每次開機需要輸入兩次登錄密碼,於是我就研究了一下用C#來幫我輸入第二此登錄的密碼
設計思想:
主要是通過調用Windows API中的一些方法,主要使用的也就是FindWindow,FindWindowEx和SendMessage這三個函數,循環遍歷當前的所有窗口,找到目標窗口和進程以後把保存在特定位置的用戶名密碼以及域信息自動填入輸入框中,然後再觸發一下button事件,最後程序本身退出。
環境:
在Windows 2000中文版 + sp4,VS.net 2003中文版下開發
在Windows 2000中文版下測試通過
程序截圖:
具體設計這個Form的代碼就略過不詳細說了
為了使用Win32 API,需要先引入下面這個命名空間:
using System.Runtime.InteropServices;
另外還需要用到進程和注冊表,所以還需要引入下面的兩個命名空間:
using System.Threading;
using Microsoft.Win32;
下面的代碼是用來添加對API的引用:
Dll Import#region Dll Import
[DllImport("User32.dll",EntryPoint="FindWindow")]
private static extern IntPtr FindWindow(string lpClassName,
string lpWindowName);
[DllImport("user32.dll",EntryPoint="FindWindowEx")]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("User32.dll",EntryPoint="SendMessage")]
private static extern int SendMessage(IntPtr hWnd,
int Msg, IntPtr wParam, string lParam);
#endregion
主要用到的就是這三個方法,具體的我這裡就不詳細介紹了,請參考MSDN。
需要用到的一些參數:
const int WM_GETTEXT = 0x000D;
const int WM_SETTEXT = 0x000C;
const int WM_CLICK = 0x00F5;
從名稱上應該就可以了解這些參數具體的含義是什麼了,而且這些參數都可以通過VS附帶的工具Spy ++查到。
下面是整個程序的核心部分,查找窗體並對它進行操作:
SearchWindow#region SearchWindow
private int SearchWindow()
{
int retval = 0; //增加一個返回值用來判斷操作是否成功
//下面的這些參數都可以用Spy++查到
string lpszParentClass = "#32770"; //整個窗口的類名
string lpszParentWindow = "本地連接"; //窗口標題
string lpszClass = "Edit"; //需要查找的子窗口的類名,也就是輸入框
string lpszClass_Submit = "Button"; //需要查找的Button的類名
string lpszName_Submit = "確定"; //需要查找的Button的標題
string text = "";
IntPtr ParenthWnd = new IntPtr(0);
IntPtr EdithWnd = new IntPtr(0);
//查到窗體,得到整個窗體
ParenthWnd = FindWindow(lpszParentClass,lpszParentWindow);
//判斷這個窗體是否有效
if (!ParenthWnd.Equals(IntPtr.Zero))
{
//得到User Name這個子窗體,並設置其內容
EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
if (!EdithWnd.Equals(IntPtr.Zero))
{
text = this.tbUserName.Text.Trim();
//調用SendMessage方法設置其內容
SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)0, text);
retval ++;
}
//得到Password這個子窗體,並設置其內容
EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
if (!EdithWnd.Equals(IntPtr.Zero))
{
text = this.tbPassword.Text.Trim();
SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)0, text);
retval ++;
}
//得到Domain這個子窗體,並設置其內容
EdithWnd = FindWindowEx(ParenthWnd,EdithWnd,lpszClass,"");
if (!EdithWnd.Equals(IntPtr.Zero))
{
text = this.tbDomain.Text.Trim();
SendMessage(EdithWnd, WM_SETTEXT, (IntPtr)0, text);
retval ++;
}
//得到Button這個子窗體,並觸發它的Click事件
EdithWnd = FindWindowEx(ParenthWnd,
EdithWnd,lpszClass_Submit,lpszName_Submit);
if (!EdithWnd.Equals(IntPtr.Zero))
{
SendMessage(EdithWnd,WM_CLICK,(IntPtr)0,"0");
retval ++;
}
}
return retval;
}
#endregion
這裡有一點需要說明的是,當一個窗體下面有幾個類名相同的子窗體時,也就是說如果有三個輸入框,這三個輸入框的類名都是Edit,查找結果是依次從上往下的,最開始我不知道該怎麼辦才能分出具體的每個不同的輸入框,後來只能這樣一個一個來查找來試一下,沒想到居然是對的。(有別的辦法麼?)
上面的這段代碼也只適用於中文版的操作系統,因為不同的操作系統下同一個窗體的名稱都是不一樣的,我這裡也沒有英文版的系統,所以也沒辦法進行測試。
為了免去每次都讓用戶手動輸入的煩惱,我需要把這些信息都保存到一個特定的文件裡面去,當用戶在第一次運行這個程序的時候,只需要輸入一次,點下Save,先把這些信息保存到一個文件中,然後再把程序本身加載到系統啟動項裡去,這樣下次開機的時候程序就可以自啟動,然後從文件中讀取信息完成以下的操作。
選擇存放文件的路徑:
private string UserPro =
System.Environment.GetEnvironmentVariable("USERPROFILE");
private string PATH = System.Environment.GetEnvironmentVariable("USERPROFILE") + @"Local SettingsAutoLog.ini";
當用戶點下Save按鈕所觸發的事件:
Button Submit Click#region Button Submit Click
private void btSubmit_Click(object sender, System.EventArgs e)
{
SaveData();
}
private void SaveData()
{
try
{
//Save Data
FileInfo obj = new FileInfo(PATH);
if(obj.Exists)
obj.Delete();
FileStream ofile = new FileStream(PATH,FileMode.Create);
//Hidden the file
File.SetAttributes(PATH,FileAttributes.Hidden);
StreamWriter sw = new StreamWriter(ofile);
//把用戶名密碼和域信息寫入文件
sw.WriteLine(this.tbUserName.Text);
sw.WriteLine(this.tbPassword.Text);
sw.WriteLine(this.tbDomain.Text);
sw.Flush();
sw.Close();
ofile.Close();
//把當前文件拷貝到指定位置,然後再添加到注冊表的啟動項裡
string opath = Application.StartupPath + @"Login.exe";
string tpath = UserPro + @"Local SettingsLogin.exe";
if(File.Exists(tpath))
File.Delete(tpath);
File.Copy(opath,tpath);
RegistryKey hklm = Registry.CurrentUser;
RegistryKey run =
hklm.CreateSubKey(@"SOFTWAREMicrosoftWindowsCurrentVersionRun");
run.SetValue("AutoLogin",tpath);
//最後程序退出
MessageBox.Show("OK","Information",
MessageBoxButtons.OK,MessageBoxIcon.Information);
Application.Exit();
}
catch(Exception ex)
{
MessageBox.Show(ex.ToString(),"Error",
MessageBoxButtons.OK,MessageBoxIcon.Error);
}
}
#endregion
這樣的話,程序就可以從文件中讀取已經存放好的信息來進行驗證了。最後要做的就是,需要單獨開一個進程來循環執行上面的SearchWindow這個方法,直到找到符合條件的窗口並成功驗證為止,並且這個進程需要隨程序的啟動而啟動。
我們可以在構造函數中添加一個名為LoadData的方法,然後在這個方法中進行具體的讀文件信息和啟動進程的操作。
當然,先定義好這個進程:
private Thread thread;
然後是LoadData這個方法:
Load#region Load
private void LoadData()
{
//Load Data
FileStream ofile = new FileStream(PATH,FileMode.OpenOrCreate);
StreamReader sr = new StreamReader(ofile);
this.tbUserName.Text = sr.ReadLine();
this.tbPassword.Text = sr.ReadLine();