Zabbix Download Pictures via Graphid - 2nd Edition

Keywords: Windows JSON Zabbix PHP Database

Read the above before reading this article: https://blog.51cto.com/181647568/2480079

After downloading the script from the last Powershell version of the picture, I received some feedback.
1. The last script accesses the database through dll and gets graphid through sql statement, which means there is a dll in addition to the script.And the database must have open access, which means less security.And not all production environments have the opportunity to enter data or modify databases.
2.Cookies need to be captured manually in the browser.This is difficult for some IT.
3. The itemkey is written to death. If you want to have more images of the itemkey, you need to run the script several times or modify the script code.

I'm going to make some improvements based on what I saw in the blog I started with.
Reference documents: https://www.cnblogs.com/dreamer-fish/p/5485556.html
Since python is not available on windows in the production environment, I cannot install it yet.But python on Linux has code that can't be used in that blog post, missing some modules.So this time I'm going to use vbs to implement the code.Objects such as powershell cookies, sessions are too complex.

I didn't see the process of graphid generation through http grabbing when zabbix was viewing the graph.I found that zabbix has a separate web api for communicating with data in jsonrpc format, but this api is not used when web pages are normally accessed.
Official documents: https://www.zabbix.com/documentation/3.4/zh/manual/api

The query method of the api is the same as that of sql.The hostid is found by zabbix_agent_host, the itemid by hostid and itemkey, and the graphid by Itemid.Then download the picture.This jsonrpc process does not require cookies.The process for the next image requires a cookie.
The only difference is that the jsonrpc webapi needs to fetch an authcode, which is obtained by using a username and password, which is needed in the next process.

In order to obtain a cookie, you may need to simulate a login process to retrieve the cookie.Cookies are available through the post username and password in the login interface index.php.This makes it more friendly for us to just fill in the code with a user name and password, not a cookie.

Here is the code:

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

Let me show you how to use this script.The first section at the beginning sets up that the script will continue running without exiting the program if something goes wrong.
Then there is a JSON class, which is used to analyze JSON in vbs, but this JSON class is a bit problematic, as long as the JSON is complex, the analysis will be wrong.So if JSON is very long, I'll use another command to get the value of json.

The normal operation of this script also requires a list file of CSVS to modify the name of the hosts we need to check.And this time I added an item for the item to determine what item to check for the continuation table of the host.The default separator for csv is',', but we'll change it to'!'.Because Itemkey may contain commas, the subscript will mistakenly split the value of itemkey.If you need charts for two different items for the same host, you need to write two lines.The first line is a comment by default, so it's ignored.csv files can be generated and modified in excel.It can also be edited in any text editor.

This csv is written as follows:

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

The changes in this script include the IP address (corresponding variable: zabbix_url), user name (zabbix_username), and password (zabbix_password).There are also parameters for graph generation php.
Pictures will be generated by default at the path where vbs is located. Mr. will create a folder generated by today's date. Subfolders will be created in the first four bits of the item name, and the pictures will be placed in the corresponding folder.The clear rule for pictures is the value of zabbix_agent_hostname plus the.png suffix name.If the picture already exists, the file will be prompted to exist when running, but the existing picture will not be overwritten.

The following is an explanation of the function in the script:
GetAuthToken(url,username,password)
Obtain webapi authtoken with username password

GetGraphid(url,AuthCode,itemid)
Getting graphid from itemid

GetHostid(url,AuthCode,zabbix_agent_hostname)
Get hostid from authcode and zabbix_agent_hostname

GetItemid(url,AuthCode,hostid,zabbix_item_key)
Get itemid from hostid and itemkey

GetMid(strText, strFormer, strLater,intStartLocation)
Text_Retrieve Intermediate Text adapted from the refined module is mainly the result of taking out the middle of two strings to get the value of json in the form of text when json cannot be parsed properly.

GetDaySecond(Day)
Calculates the number of seconds in N days, which is used to provide a parameter value later in the table generation for chart2.php.

GetZabbixCookie(zabbix_index,username,password)
Get the cookie by zabbix's username and password.Cookies are essential for successful download to the correct picture!

DownloadZabbixPic(url,strPath,cookie)
In fact, this function does not just take pictures, it saves the content returned from a web page as a binary file.Cookies can also be used.

The script reads the information in the list.csv file (be sure to have a list.csv file in the same directory) and converts it to a picture.
Notice the parameters for the graph generation, and you can search Download ZabbixPic in the script to see the parameters for the graph generated by zabbix.There are four main parameters which are more important. The stime represents the start time usage (yyyyMMDDhhmmss) does not fill in the data which will default to the corresponding time in the past and now. The period represents the time unit which can be used to calculate seconds with getdaysecond s. The default is 30-day data. If you want to modify it yourself, it is also the default 30-day data.The other two values are weight and height, which control the length and width of the picture, respectively.Not filling in is also possible.

Please use cscript in cmd to run this vbs, otherwise the error and prompt information will appear as information box.You'll get bored with the endless bouncing boxes.You can redirect cscript commands to a log as log files for this script.

Posted by Protato on Wed, 25 Mar 2020 09:42:18 -0700