VB.NET開發掃描用戶端服務工具
來源:互聯網
上載者:User
用戶端 在大中型公司資訊系統中,對用戶端PC的管理,往往是容易出現問題的環節。因此,很多大公司引入了各種分布式的管理系統,例如防病毒方面的Norton AntiVirus,BlackICE防火牆,微軟的 SMS (System Management Server),等等,這些系統都會在用戶端安裝相應的用戶端軟體,一般都是以服務的形式出現,但是由於種種原因,這些服務會停止運行或者該客戶機根本沒有安裝這些用戶端服務,這樣管理系統就會出現疏漏,有可能造成問題,如因無法防禦病毒而成為病毒源,無法為該用戶端發布軟體,無法管理客戶PC等等。在此,我們提供一個方案,可以定時按照IP地址掃描網路,報告出特定的服務的狀態。
這個方案使用了Microsoft.NET技術,同時也用到了.NET Framework中的ADO.NET ,WMI management,XML。其核心是一個由VB.NET寫的程式以及它的兩個設定檔,設定檔為XML格式,該程式按IP掃描網路,得到每個系統的服務 的狀態,如果IP地址沒有對應系統,則忽略該IP,針對沒有安裝服務或服務停止的系統我們在另一個線程中運行NBTSTAT命令,得到其機器名,使用者名稱,MAC地址域等資訊,以便我們找到機器解決問題。其次為了儲存掃描的結果,我們需要一個很小的資料庫MS-Access或MS-SQL server都可以,本文使用SQL2000 。最後為了呈現出掃描的結果,以便我們採取行動,這裡我們使用網頁的形式把資料庫中的結果展現出來。
1. VB.NET程式
該程式使用兩個XML格式的設定檔,當程式啟動時會讀入這些配置。其中一個檔案定義了需要掃描的網段,包括排除在外的位址區段。另一個檔案定義了串連資料庫的資訊,以及資料表的定義。這兩個檔案的內容如下:
<IPLIST>
<IP LANID="192.168.100." ><EXP L=”1” H=”30”/></IP>
<IP LANID="192.168.101." />
<IP LANID="192.168.102." />
<IP LANID="192.168.103." />
<IP LANID="192.168.104." ><EXP L=”1” H=”40”/></IP>
</IPLIST>
該檔案定義將要掃描5個網段,其中兩個網段有些地址需要排除在外(分配給印表機等裝置),對於192.168.100段,我們排除從1到30,對於192.168.104段我們排除1到40。
<DBINFO>
<SERVER>DBServer</SERVER>
<DATABASE>DB</DATABASE>
<UID>REPORT</UID>
<PWD>REPORT</PWD>
<SERVICE TABLE=”SERVICE”>SERVICE</SERVICE>
</DBINFO>
該檔案定義了串連資料庫所需的資訊
TAG Meaning
<SERVER> SCANSERVICE資料庫的伺服器名
<DATABASE> SCANSERVICE資料庫名
<UID> 用於更新SCANSERVICE資料庫的資料庫使用者名稱
<PWD> 用於更新SCANSERVICE資料庫的資料庫使用者的密碼
<SERVICE> 該TAG的 inner 定義了我們希望掃描的Service的名字,
這裡我們假定希望掃描服務名為SERVICE。
該TAG的屬性定義了資料庫中表名,該表用於儲存掃描結果。
‘首先我們定義一個類,主要用於得到某個IP地址的Service的狀態資訊,並在服務狀態不正常時觸發另一線程得到該系統的詳細資料。
Imports System.ServiceProcess
Imports System.Xml
Imports System.Threading
Public Class GetStatus
Private IServiceName As String ‘服務的名稱
Private IMachineIP As String ‘IP地址
Private ITable As String ‘在DATESET中的表名
‘建構函式
Sub New(ByVal Ip As String, ByVal SvcName As String, ByVal updatetable As String)
IMachineIP = Ip
IServiceName = SvcName
ITable = updatetable
End Sub
‘每個線程所啟動並執行方法,用於得到服務的狀態,如果狀態不正常則觸發另一線程得到該IP的資訊
Sub GetStausF()
Dim ServiceP As New ServiceController() ‘執行個體化一個ServiceController類
ServiceP.MachineName = IMachineIP
ServiceP.ServiceName = IServiceName
Dim myRow As DataRow
Dim status As String
Dim Run As Boolean = False
myRow = ds.Tables(ITable).NewRow
Try
If ServiceP.Status.ToString <> "Running" Then
status = ServiceP.Status.ToString‘如果狀態不是RUNNING則將狀態賦予字串變數
Else
Run = True ‘如果狀態為RUNNING,則不做任何事
End If
Catch er As Exception ‘以下處理取得狀態時候發生的異常
status = Left(er.Message, 35)
If InStr(status, "Service Control Manager") = 0 Then
status = "Not installed or open service failed" ‘沒有安裝該服務
ElseIf InStr(er.Message, "Manager") > 0 Then
status = "Can not detected" ‘服務的狀態不可得
End If
End Try
ServiceP.Close() ‘關閉ServiceController執行個體
‘以下判斷如果狀態不是RUNNING,則記錄該系統,並觸發線程得到它的詳細資料。
If Not Run Then
myRow("msg") = status
myRow("ip") = IMachineIP
SyncLock GetType(AddRow) ‘為保證多線程情況下,對DataSet只有一個寫操作,鎖定AddRow類
Dim AddRowIns As New AddRow(myRow) ‘將IP和狀態通過我們自己寫的AddRow類插入DataSet
End SyncLock
‘觸發另一線程取得機器資訊
Dim HostInfo2 As New HostInfo(IMachineIP)
Dim HostThr2 As New Thread(New ThreadStart(AddressOf HostInfo2.sysInfo))
HostThr2.Start()
SyncLock GetType(HostInfoThreadCounter)
HostInfoThreadCounter.counter += 1 ‘啟動線程數加1
End SyncLock
End If
SyncLock GetType(StoppCounter)
StopThr.AddStop()
End SyncLock
End Sub
End Class
‘該類只有一個方法,就是將停止的線程數減1
Class StoppCounter
Sub AddStop()
ThreadCounterStopped = ThreadCounterStopped + 1
End Sub
End Class
‘此類用於將已有的行插入DataSet
Class AddRow
‘第一個建構函式,以構造好的行為輸入參數
Sub New(ByVal row As DataRow)
Try
ds.Tables(0).Rows.Add(row)
Catch ee As Exception
End Try
End Sub
‘第二個建構函式,以機器名使用者名稱等字串為參數,更新已有的行
Sub New(ByVal IP As String, ByVal user As String, ByVal hostname As String, ByVal Mac As String, ByVal domain As String, ByVal timeout As Char)
Dim RowTimeOut As DataRow
Try
For Each RowTimeOut In ds.Tables(0).Select("IP='" & IP & "'")
RowTimeOut.Item("LastUID") = user
RowTimeOut.Item("Name") = hostname
RowTimeOut.Item("Mac") = Mac
RowTimeOut.Item("Domain") = domain
RowTimeOut.Item("Timeout") = timeout 'Set timeout flag to this item
Exit For 'just run once
Next
Catch er As Exception
End Try
End Sub
End Class
‘由於篇幅限制,這裡省略了根據IP取得機器資訊的類的代碼。
Imports System.Threading ‘用於支援多線程
Imports System.Xml ‘用於分析XML格式的參數檔案
Imports System.Data ‘用於儲存結果到資料庫
Module Module1
Public ds As New DataSet()
Public conn1 As SqlClient.SqlConnection ‘資料庫連接
Public ipf As String ‘IP列表檔案名稱
Public dbf As String ‘資料庫資訊檔
Public ThreadCounterStopped As Integer
Public StopThr As New StoppCounter()
Sub Main() ‘程式主程式
Dim machineIP As String
Dim iplistF As New Xml.XmlDocument()
Dim iplist As Xml.XmlNode
Dim ipitem As Xml.XmlNode
Dim DBinfoF As New Xml.XmlDocument()
Dim DBinfo As Xml.XmlNode
Dim LanID As String
Dim i As Integer
Dim timestart As Integer
Dim ThreadCounterStarted As Integer
ThreadCounterStarted = 0
ThreadCounterStopped = 0
Dim server As String
Dim database As String
Dim uid As String
Dim pwd As String
Dim table As String
Dim connstr, connstr1 As String
Dim ServiceName As String
Dim Purgestr As String
Try
DBinfoF.Load(dbf) ‘讀取資料庫資訊檔
Catch nodb As Exception
MsgBox(nodb.Message & "Wrong DB info file name.")
Exit Sub
End Try
Try
iplistF.Load(ipf) ‘讀取IP列表檔案
Catch noip As Exception
MsgBox(noip.Message & "Wrong IP list file name.")
Exit Sub
End Try
‘分析資料庫資訊檔
DBinfo = DBinfoF.ChildNodes(0)
server = DBinfo.ChildNodes(0).InnerText
database = DBinfo.ChildNodes(1).InnerText
uid = DBinfo.ChildNodes(2).InnerText
pwd = DBinfo.ChildNodes(3).InnerText
ServiceName = DBinfo.ChildNodes(4).InnerText
table = DBinfo.ChildNodes(4).Attributes(0).Value
‘根據分析所得,構造連接字串
connstr1 = "server=" & server & ";database=" & database & ";uid=" & uid & ";password=" & pwd
conn1 = New SqlClient.SqlConnection(connstr1) ‘執行個體化資料庫連接
conn1.Open() ‘開啟資料庫連接
Dim sa As SqlClient.SqlDataAdapter = New SqlClient.SqlDataAdapter("select * from " & table, conn1)
Dim combu As New SqlClient.SqlCommandBuilder(sa)
sa.Fill(ds, table) ‘填充DataSet
ds.Clear() ‘清空舊的資料
Dim IPAddress As String
‘分析IP列表檔案
iplist = iplistF.ChildNodes(0)
Dim Ai As Integer
Dim ipexcepCount As Integer
Dim ipexcep As Xml.XmlNode
For Each ipitem In iplist.ChildNodes
Dim Excep(2, 83) As Integer
LanID = ipitem.Attributes(0).Value‘得到網路ID
For i = 2 To 254 ‘從2到254,根據每個網路ID構造IP地址
Ai = 0
‘以下判斷是為了跳過保留位址區段
If ipitem.HasChildNodes Then
ipexcepCount = ipitem.ChildNodes.Count
ReDim Excep(2, ipexcepCount - 1)
For Each ipexcep In ipitem.ChildNodes
Excep(0, Ai) = CInt(ipexcep.Attributes(0).Value)
Excep(1, Ai) = CInt(ipexcep.Attributes(1).Value)
Ai = Ai + 1
Next
End If
For Ai = 0 To ipexcepCount - 1
If i >= Excep(0, Ai) And i <= Excep(1, Ai) Then
Console.WriteLine("跳過保留地址: " & LanID & i.ToString)
GoTo SkipIP
End If
Next
machineIP = LanID & i.ToString ‘IP地址
‘以下觸發線程以,得到服務狀態
Dim getSt As New GetStatus(machineIP, ServiceName, table)
Dim GetStThread As New Thread(New ThreadStart(AddressOf getSt.GetStausF))
GetStThread.Start()
ThreadCounterStarted = ThreadCounterStarted + 1‘啟動線程數加1
Console.WriteLine("線程" & machineIP & " 啟動。檢測 " & ServiceName)
‘每啟動100個線程,程式主線程停止15秒,避免太多線程造成記憶體溢出
If (ThreadCounterStarted Mod 100) = 0 Then
Console.WriteLine("等待 .......")
Thread.CurrentThread.Sleep(15000)
GC.Collect() 'force garbage collection to aviod outOfMemory when run with long IP list
End If
SkipIP:
Next
Next
Console.WriteLine("Exiting program ...") ‘所有線程都已觸發
Finish:
Thread.CurrentThread.Sleep(5000) ‘以下程式等待所有線程結束
GC.Collect()
If ThreadCounterStopped = ThreadCounterStarted And HostInfoThreadCounter.counter = HostInfoThreadCounter.counterSTOP Then‘如果觸發線程等於結束線程
Dim row As Data.DataRow
For Each row In ds.Tables(table).Rows
row.Item("SysTime") = Now
Next
Purgestr = "delete " & table
Dim com1 As New SqlClient.SqlCommand(Purgestr, conn1)
com1.ExecuteNonQuery() ‘刪除舊記錄
sa.InsertCommand = combu.GetInsertCommand
sa.Update(ds, table) ' 將新記錄寫入資料庫
Else
GoTo Finish ' goto finish and wait another 30 seconds
End If
End Sub
可以利用如下命令在DOS視窗啟動該程式。
Scanservice –i iplist.xml –d dbinfo.xml
2. SCANSERVICE 資料庫
該資料庫儲存儲存程式運行結果,以便用WEB等方式展現出來。以下是建立表的指令碼,包含網域名稱,使用者名稱,機器名,IP以及服務狀態。
CREATE TABLE [dbo].[Service] (
[IP] [varchar] (50) NULL ,
[狀態] [varchar] (50) NULL ,
[使用者名稱] [varchar] (50) NULL ,
[機器名] [varchar] (50) NULL ,
[MAC地址] [varchar] (50) NULL ,
[域] [varchar] (50) NULL ,
[逾時] [varchar] (10) NULL ,
[時間安] [DateTime] (8) NULL ,
)
總結:
以上是一個完整的方法,也是比較簡單明晰的解決方案,如果要求技巧和效能的話,還有一些地方可以做些改進,比如對線程池的使用。另外還有一些方面需要大家自己完成,比如將資料庫中的資訊以WEB的方式展現出來。