Zabbix之通過Graphid下載圖片——第二版

閱讀本篇之前要先讀上文:https://blog.51cto.com/181647568/2480079

根據上一次的Powershell版本的圖片下載腳本之後,我收到了一些反饋。
1.上一個腳本通過dll實現訪問數據庫,通過sql語句來獲取graphid,這就意味着除了腳本之外還要有一個dll。而且數據庫必須開放訪問權限,這就意味着安全性降低。而且並不是所有的生產環境都可以有機會進數據,或者修改數據庫的。
2.cookie需要手動在瀏覽器裏抓包獲取。這對一些IT來說有難度。
3.itemkey是寫死的,如果要多itemkey的圖片需要運行多次腳本,或者修改腳本代碼。

根據我一開始看的博客中的內容獲得啓發,我打算做一下改進。
參考文檔:https://www.cnblogs.com/dreamer-fish/p/5485556.html
由於生產環境的windows上沒有python,我還不能裝。但是Linux上的python有不能用那個博文中的代碼,缺少一些模塊。所以這次我打算用vbs去實現代碼。powershell的cookie,session之類的對象太複雜了。

我沒有在zabbix查看graph的時候通過http抓包看到graphid產生的過程。我發現zabbix有一套獨立的webapi,通過jsonrpc格式數據進行通信,但是在網頁正常訪問的時候是不用這套api的。
官方文檔:https://www.zabbix.com/documentation/3.4/zh/manual/api

api的查詢方法和sql是一樣的。通過zabbix_agent_host查到hostid,根據hostid和itemkey查到itemid,再通過Itemid查到graphid。然後下載圖片。這個jsonrpc的過程不需要用到cookie。而下圖片的過程需要cookie。
唯一的不同是jsonrpc webapi需要取到一個authcode的過程,需要用到用戶名和密碼來獲取,接下來的過程都需要用到這個authcode。

爲了獲取cookie,可能需要模擬一次登錄的過程來取到cookie。登錄界面index.php中通過post用戶名和密碼可以獲取到cookie。這樣我們在代碼中只需要填入用戶名和密碼,而不是cookie,更加的友善了。

以下是代碼:

On Error Resume Next

Class VbsJson
    'Author: Demon
    'Date: 2012/5/3
    'Website: http://demon.tw
    Private Whitespace, NumberRegex, StringChunk
    Private b, f, r, n, t

    Private Sub Class_Initialize
        Whitespace = " " & vbTab & vbCr & vbLf
        b = ChrW(8)
        f = vbFormFeed
        r = vbCr
        n = vbLf
        t = vbTab

        Set NumberRegex = New RegExp
        NumberRegex.Pattern = "(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?"
        NumberRegex.Global = False
        NumberRegex.MultiLine = True
        NumberRegex.IgnoreCase = True

        Set StringChunk = New RegExp
        StringChunk.Pattern = "([\s\S]*?)([""\\\x00-\x1f])"
        StringChunk.Global = False
        StringChunk.MultiLine = True
        StringChunk.IgnoreCase = True
    End Sub

    'Return a JSON string representation of a VBScript data structure
    'Supports the following objects and types
    '+-------------------+---------------+
    '| VBScript          | JSON          |
    '+===================+===============+
    '| Dictionary        | object        |
    '+-------------------+---------------+
    '| Array             | array         |
    '+-------------------+---------------+
    '| String            | string        |
    '+-------------------+---------------+
    '| Number            | number        |
    '+-------------------+---------------+
    '| True              | true          |
    '+-------------------+---------------+
    '| False             | false         |
    '+-------------------+---------------+
    '| Null              | null          |
    '+-------------------+---------------+
    Public Function Encode(ByRef obj)
        Dim buf, i, c, g
        Set buf = CreateObject("Scripting.Dictionary")
        Select Case VarType(obj)
            Case vbNull
                buf.Add buf.Count, "null"
            Case vbBoolean
                If obj Then
                    buf.Add buf.Count, "true"
                Else
                    buf.Add buf.Count, "false"
                End If
            Case vbInteger, vbLong, vbSingle, vbDouble
                buf.Add buf.Count, obj
            Case vbString
                buf.Add buf.Count, """"
                For i = 1 To Len(obj)
                    c = Mid(obj, i, 1)
                    Select Case c
                        Case """" buf.Add buf.Count, "\"""
                        Case "\"  buf.Add buf.Count, "\\"
                        Case "/"  buf.Add buf.Count, "/"
                        Case b    buf.Add buf.Count, "\b"
                        Case f    buf.Add buf.Count, "\f"
                        Case r    buf.Add buf.Count, "\r"
                        Case n    buf.Add buf.Count, "\n"
                        Case t    buf.Add buf.Count, "\t"
                        Case Else
                            If AscW(c) >= 0 And AscW(c) <= 31 Then
                                c = Right("0" & Hex(AscW(c)), 2)
                                buf.Add buf.Count, "\u00" & c
                            Else
                                buf.Add buf.Count, c
                            End If
                    End Select
                Next
                buf.Add buf.Count, """"
            Case vbArray + vbVariant
                g = True
                buf.Add buf.Count, "["
                For Each i In obj
                    If g Then g = False Else buf.Add buf.Count, ","
                    buf.Add buf.Count, Encode(i)
                Next
                buf.Add buf.Count, "]"
            Case vbObject
                If TypeName(obj) = "Dictionary" Then
                    g = True
                    buf.Add buf.Count, "{"
                    For Each i In obj
                        If g Then g = False Else buf.Add buf.Count, ","
                        buf.Add buf.Count, """" & i & """" & ":" & Encode(obj(i))
                    Next
                    buf.Add buf.Count, "}"
                Else
                    Err.Raise 8732,,"None dictionary object"
                End If
            Case Else
                buf.Add buf.Count, """" & CStr(obj) & """"
        End Select
        Encode = Join(buf.Items, "")
    End Function

    'Return the VBScript representation of ``str(``
    'Performs the following translations in decoding
    '+---------------+-------------------+
    '| JSON          | VBScript          |
    '+===============+===================+
    '| object        | Dictionary        |
    '+---------------+-------------------+
    '| array         | Array             |
    '+---------------+-------------------+
    '| string        | String            |
    '+---------------+-------------------+
    '| number        | Double            |
    '+---------------+-------------------+
    '| true          | True              |
    '+---------------+-------------------+
    '| false         | False             |
    '+---------------+-------------------+
    '| null          | Null              |
    '+---------------+-------------------+
    Public Function Decode(ByRef str)
        Dim idx
        idx = SkipWhitespace(str, 1)

        If Mid(str, idx, 1) = "{" Then
            Set Decode = ScanOnce(str, 1)
        Else
            Decode = ScanOnce(str, 1)
        End If
    End Function

    Private Function ScanOnce(ByRef str, ByRef idx)
        Dim c, ms

        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "{" Then
            idx = idx + 1
            Set ScanOnce = ParseObject(str, idx)
            Exit Function
        ElseIf c = "[" Then
            idx = idx + 1
            ScanOnce = ParseArray(str, idx)
            Exit Function
        ElseIf c = """" Then
            idx = idx + 1
            ScanOnce = ParseString(str, idx)
            Exit Function
        ElseIf c = "n" And StrComp("null", Mid(str, idx, 4)) = 0 Then
            idx = idx + 4
            ScanOnce = Null
            Exit Function
        ElseIf c = "t" And StrComp("true", Mid(str, idx, 4)) = 0 Then
            idx = idx + 4
            ScanOnce = True
            Exit Function
        ElseIf c = "f" And StrComp("false", Mid(str, idx, 5)) = 0 Then
            idx = idx + 5
            ScanOnce = False
            Exit Function
        End If

        Set ms = NumberRegex.Execute(Mid(str, idx))
        If ms.Count = 1 Then
            idx = idx + ms(0).Length
            ScanOnce = CDbl(ms(0))
            Exit Function
        End If

        Err.Raise 8732,,"No JSON object could be ScanOnced"
    End Function

    Private Function ParseObject(ByRef str, ByRef idx)
        Dim c, key, value
        Set ParseObject = CreateObject("Scripting.Dictionary")
        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "}" Then
            Exit Function
        ElseIf c <> """" Then
            Err.Raise 8732,,"Expecting property name"
        End If

        idx = idx + 1

        Do
            key = ParseString(str, idx)

            idx = SkipWhitespace(str, idx)
            If Mid(str, idx, 1) <> ":" Then
                Err.Raise 8732,,"Expecting : delimiter"
            End If

            idx = SkipWhitespace(str, idx + 1)
            If Mid(str, idx, 1) = "{" Then
                Set value = ScanOnce(str, idx)
            Else
                value = ScanOnce(str, idx)
            End If
            ParseObject.Add key, value

            idx = SkipWhitespace(str, idx)
            c = Mid(str, idx, 1)
            If c = "}" Then
                Exit Do
            ElseIf c <> "," Then
                Err.Raise 8732,,"Expecting , delimiter"
            End If

            idx = SkipWhitespace(str, idx + 1)
            c = Mid(str, idx, 1)
            If c <> """" Then
                Err.Raise 8732,,"Expecting property name"
            End If

            idx = idx + 1
        Loop

        idx = idx + 1
    End Function

    Private Function ParseArray(ByRef str, ByRef idx)
        Dim c, values, value
        Set values = CreateObject("Scripting.Dictionary")
        idx = SkipWhitespace(str, idx)
        c = Mid(str, idx, 1)

        If c = "]" Then
            ParseArray = values.Items
            Exit Function
        End If

        Do
            idx = SkipWhitespace(str, idx)
            If Mid(str, idx, 1) = "{" Then
                Set value = ScanOnce(str, idx)
            Else
                value = ScanOnce(str, idx)
            End If
            values.Add values.Count, value

            idx = SkipWhitespace(str, idx)
            c = Mid(str, idx, 1)
            If c = "]" Then
                Exit Do
            ElseIf c <> "," Then
                Err.Raise 8732,,"Expecting , delimiter"
            End If

            idx = idx + 1
        Loop

        idx = idx + 1
        ParseArray = values.Items
    End Function

    Private Function ParseString(ByRef str, ByRef idx)
        Dim chunks, content, terminator, ms, esc, char
        Set chunks = CreateObject("Scripting.Dictionary")

        Do
            Set ms = StringChunk.Execute(Mid(str, idx))
            If ms.Count = 0 Then
                Err.Raise 8732,,"Unterminated string starting"
            End If

            content = ms(0).Submatches(0)
            terminator = ms(0).Submatches(1)
            If Len(content) > 0 Then
                chunks.Add chunks.Count, content
            End If

            idx = idx + ms(0).Length

            If terminator = """" Then
                Exit Do
            ElseIf terminator <> "\" Then
                Err.Raise 8732,,"Invalid control character"
            End If

            esc = Mid(str, idx, 1)

            If esc <> "u" Then
                Select Case esc
                    Case """" char = """"
                    Case "\"  char = "\"
                    Case "/"  char = "/"
                    Case "b"  char = b
                    Case "f"  char = f
                    Case "n"  char = n
                    Case "r"  char = r
                    Case "t"  char = t
                    Case Else Err.Raise 8732,,"Invalid escape"
                End Select
                idx = idx + 1
            Else
                char = ChrW("&H" & Mid(str, idx + 1, 4))
                idx = idx + 5
            End If

            chunks.Add chunks.Count, char
        Loop

        ParseString = Join(chunks.Items, "")
    End Function

    Private Function SkipWhitespace(ByRef str, ByVal idx)
        Do While idx <= Len(str) And _
            InStr(Whitespace, Mid(str, idx, 1)) > 0
            idx = idx + 1
        Loop
        SkipWhitespace = idx
    End Function

End Class

Set wshnamed=wscript.arguments.named
strGraphID = wshnamed.item("graphid")
strPicSavePath = wshnamed.item("PicSavePath")
strCookies = wshnamed.item("Cookies")

Set fso = CreateObject("Scripting.FileSystemObject")

zabbix_url = "192.1.31.66"
zabbix_index = "http://" & zabbix_url & "/zabbix/index.php"
zabbix_webapi= "http://" & zabbix_url & "/zabbix/api_jsonrpc.php"
zabbix_username = "Admin"
zabbix_password = "zabbix"
Zabbix_cookie = GetZabbixCookie(zabbix_index,zabbix_username,zabbix_password)

If(Zabbix_cookie = "")Then
    Wscript.Echo "Could not get Zabbix cookies, make sure your username and password is correct."
End If

Function GetAuthToken(url,username,password)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1") 
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc" 
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"", ""method"": ""user.login"", ""params"":{""user"": """&username&""",""password"": """&password&"""}, ""id"": 0}"

    Set json = New VbsJson
    GetAuthToken = json.Decode(Winhttp.ResponseText)("result")
End Function

Function GetDaySecond(Day)
    GetDaySecond = Day * 24 * 3600
End Function

Function GetGraphid(url,AuthCode,itemid)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""graphitem.get"",""params"": {""output"": ""extend"",""expandData"": 1,""itemids"": """&itemid&"""},""auth"": """&AuthCode&""",""id"": 1}"

    Set json = New VbsJson
    GetGraphid = json.Decode(WinHttp.ResponseText)("result")(0)("graphid")
End Function

Function GetHostid(url,AuthCode,zabbix_agent_hostname)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""host.get"",""params"": {""output"": ""extend"",""filter"": {""host"": """&zabbix_agent_hostname&"""}},""auth"": """&AuthCode&""",""id"": 1}"

    Set json = New VbsJson
    GetHostid = json.Decode(Winhttp.ResponseText)("result")(0)("hostid")

End Function

Function GetItemid(url,AuthCode,hostid,zabbix_item_key)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", url
    Winhttp.SetRequestHeader "Content-Type", "application/json-rpc"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send "{""jsonrpc"": ""2.0"",""method"": ""item.get"",""params"": {""output"": ""extend"",""hostids"": """ &hostid & """,""search"":{""key_"": """ & zabbix_item_key & """},""sortfield"": ""name""},""auth"": """&AuthCode&""",""id"": 1}"

    GetItemid = GetMid(Winhttp.ResponseText,"{""itemid"":""",""",""",1)
End Function

Function GetMid(strText, strFormer, strLater,intStartLocation)
    Dim FormerLocation
    Dim LaterLocation
    Dim PFormerLocation
    Dim PLaterLocation

    FormerLocation = InStr(intStartLocation, strText, strFormer)

    If (FormerLocation <> 0) Then
        FormerLocation = FormerLocation + Len(strFormer)
        LaterLocation = InStr(FormerLocation, strText, strLater)
        If (LaterLocation <> 0) Then
            GetMid = Mid(strText, FormerLocation, LaterLocation - FormerLocation)
            Exit Function
        End If
    End If

    GetMid = ""
End Function

Function GetZabbixCookie(zabbix_index,username,password)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1")
    Winhttp.Open "POST", zabbix_index
    Winhttp.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    Winhttp.Send "name=" & username & "&password=" & password & "&autologin=1&enter=Sign+in"
    GetZabbixCookie = "zbx_sessionid=" & GetMid(winhttp.GetAllResponseHeaders,"zbx_sessionid=",";",1) & ";"
End Function

Sub DownloadZabbixPic(url,strPath,cookie)
    Set Winhttp = CreateObject("WinHttp.WinHttpRequest.5.1") 
    Winhttp.Open "GET", url
    Winhttp.SetRequestHeader "Content-Type", "application/x-www-form-urlencoded"
    If(cookie <> "")then
        Winhttp.SetRequestHeader "Cookie",cookie
    End If
    Winhttp.Send

    Set sGet = CreateObject("ADODB.Stream")
    sGet.Mode = 3
    sGet.Type = 1
    sGet.Open()
    sGet.Write(Winhttp.ResponseBody)
    sGet.SaveToFile strPath
End Sub

AuthCode = GetAuthToken(zabbix_webapi,zabbix_username,zabbix_password)
If AuthCode = "" Then
    Wscript.Echo "Could not get AuthCode."
    Wscript.Quit
End If

CurrentFolder = fso.getfolder(".")
CSV_Path = CurrentFolder&"\list.csv"

If (fso.fileExists(CSV_Path)=0) Then
    Wscript.Echo "Could not find " & CSV_Path & "."
    Wscript.Quit
End If

set csv_file = fso.opentextfile(CSV_Path)
csv_text = csv_file.readall
csv_file.close

PicSaveDir = CurrentFolder&"\"&replace(date(),"/","")

If (fso.folderExists(PicSaveDir)=0) Then
    fso.createfolder(PicSaveDir)
End If

CSV_ReadLine = split(csv_text,vbCrlf)

for i = 1 to ubound(CSV_ReadLine) step 1
    CSV_ReadCol = split(CSV_ReadLine(i),"!")
    Zabbix_agent_host = CSV_ReadCol(0)
    ItemKey = CSV_ReadCol(1)

    PicSaveItemDir = PicSaveDir & "\" & Left(ItemKey,4)

    If (fso.folderExists(PicSaveItemDir)=0) Then
        fso.createfolder(PicSaveItemDir)
    End if

    PicSavePath = PicSaveItemDir & "\" & Zabbix_agent_host & ".png"

    Hostid = GetHostid(zabbix_webapi,AuthCode,Zabbix_agent_host)
    If (Hostid = "") Then
        Wscript.echo "Hostid is empty. Current host is: " & Zabbix_agent_host
    Else
        ItemID = GetItemid(zabbix_webapi,AuthCode,Hostid,ItemKey)

        If (Itemid = "") Then
            Wscript.echo "Itemid is empty. Current host is: " & Zabbix_agent_host & ". Current item is: "&ItemKey
        Else
            Graphid = GetGraphid(zabbix_webapi,AuthCode,itemid)
            If (graphid = "") Then
                Wscript.echo "Graphid is empty. Current host is: " & Zabbix_agent_host
            Else
                If (fso.fileExists(PicSavePath)) Then
                    Wscript.echo "PNG alreadly exist. " & "Current host is: " & Zabbix_agent_host & ". Current item is: "&ItemKey
                Else
                    DownloadZabbixPic "http://" & zabbix_url & "/zabbix/chart2.php?graphid=" & Graphid & "&period=" & GetDaySecond(30),PicSavePath,Zabbix_cookie
                    Wscript.Echo Zabbix_agent_host & " " & ItemKey &" successfully save as " & PicSavePath
                End if
            End If
        End If
    End If
Next

我來介紹一下此腳本的使用方法。首先開始的第一段設置了這個腳本會在運行出錯時,不會退出程序轉而繼續運行。
然後是一個json的類,用於在vbs中分析json,不過這個json類有點問題,只要json一複雜,就會分析出錯。所以如果json很長我會使用另外一個命令去獲取json的值。

這個腳本的正常運行還需要一個csv的列表文件來修改我們需要檢查的host主機的名字。而且這次我添加了item的項,決定檢查host的什麼item的續表。其中csv默認的分隔符爲“,”,但是我們要改成“!”。因爲Itemkey中可能包含逗號,這樣子腳本會誤把itemkey的值給拆分了。如果同一個主機你需要兩種不同的item的圖表,你需要寫兩行。第一行默認是註釋,所以是無視掉的。csv文件可以在excel裏生成並且修改。也可以在任何的文本編輯器中進行編輯。

這個csv的寫法如下:

zabbixAgentHost!itemkey
Zabbix server!system.cpu.utils[,avg]
Zabbix server!vm.memory.useage

這個腳本中變動的部分包括,zabbix的IP地址(對應變量:zabbix_url)、用戶名(zabbix_username)、密碼(zabbix_password)。還有圖表生成php的參數。
圖片會默認生成在vbs所在的路徑處,先生成一個由今天日期產生的文件夾,再以item名的前4位創建子文件夾,然後圖片會放在對應的文件夾中。圖片的明明規則是zabbix_agent_hostname的值加.png後綴名。如果圖片已經存在,會在運行的時候提示文件已存在,但是不會覆蓋已存在的圖片。

以下是腳本中function的解釋:
GetAuthToken(url,username,password)
通過用戶名密碼獲取webapi authtoken

GetGraphid(url,AuthCode,itemid)
通過itemid獲取graphid

GetHostid(url,AuthCode,zabbix_agent_hostname)
通過authcode和zabbix_agent_hostname獲取hostid

GetItemid(url,AuthCode,hostid,zabbix_item_key)
通過hostid和itemkey獲取itemid

GetMid(strText, strFormer, strLater,intStartLocation)
改編自精易模塊的“文本_取中間文本”主要是取出兩個字符串中間的結果,用於json不能被正常解析的時候通過文本的形式獲取json的值。

GetDaySecond(Day)
計算N天的秒數,這個值用於後期給chart2.php生成表格的時候,提供一個參數的值。

GetZabbixCookie(zabbix_index,username,password)
通過zabbix的用戶名和密碼來獲取cookie。cookie是成功下載到正確的圖片的必要條件!

DownloadZabbixPic(url,strPath,cookie)
事實上這個函數並不僅僅能夠獲取圖片,它是將網頁返回的內容以二進制的形式保存爲一個文件。其中還能使用到cookie。

腳本會讀取list.csv文件中的信息(一定要同目錄下有一個list.csv文件)然後將它們轉換成圖片。
注意的是圖片生成的參數,你可以搜索腳本中的DownloadZabbixPic來看到zabbix生成圖表的參數。主要有四個參數比較重要,stime代表起始時間用法是(yyyyMMDDhhmmss)不填會默認爲過去對應時長到現在當前的數據,period代表時長單位是秒可以用getdaysecond來計算出秒數,默認是30天的數據,如果想改可以自己修改,不填這個數據也是默認30天數據。還有兩個值是weight和height,這兩個值分別控制圖片的長和寬。不填也可以。

請在cmd裏使用cscript去運行這個vbs,否則報錯和提示信息會以信息框的形式出現。你會被不停的彈框煩死。你可以將cscript的命令重定向到一個log裏,作爲這個腳本的日誌文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章