【问题标题】:Excel VBA listing all unique values from a column, and all corresponding values to each unique value from another columnExcel VBA列出一列中的所有唯一值,以及另一列中每个唯一值的所有对应值
【发布时间】:2021-10-17 00:58:20
【问题描述】:

我是 VBA/宏的新手,不知道技术术语或执行许多功能的最佳方法。但是,我能够创建一个宏,它可以部分完成我需要的部分,但我似乎无法弄清楚其余部分。

情况:我正在尝试以某种方式解析漏洞扫描结果,以便我可以正确获取相关信息,以便以简洁明了的方式将其提供给我的团队。目前,我将 vuln 结果放入一个 excel 文件中,该文件包含每个 vuln、每个主机的所有信息(因此 1 个 vuln 显示在 6 个主机上,产生 6 行)。我当前的宏能够提取信息,以便我可以将其视为每个 vuln 1 行(由唯一的 PluginID 指示),所有主机连接到由换行符分隔的 1 个单元格中。当前的宏很好地完成了这一点,但是 vuln 结果中有另一列非常重要 - “漏洞证明”。在某些 vuln 上,“漏洞证明”对于所有具有该 vuln 的主机都是相同的,而在某些情况下则不同。 我需要能够列出与证明相关的所有主机的所有唯一“漏洞证明”,以便获得“这些主机有这个证明,这些主机有这个证明等”的简明列表。

这里有一些示例源数据:

PluginID Description Host Vuln Proof
Plugin123 CVE-Plugin123 Host1 Version 1.2.3 detected
Plugin123 CVE-Plugin123 Host2 Version 1.2.3 detected
Plugin123 CVE-Plugin123 Host3 Version 4.5.6 detected
Plugin456 Plugin456-2021 Vuln Host1 Version 7.8.9 detected
Plugin456 Plugin456-2021 Vuln Host2 Version 10.11.12 detected
Plugin456 Plugin456-2021 Vuln Host3 Version 10.11.12 detected

目前,当我运行我的宏(如下)时,我得到以下输出:

PluginID Description Host Vuln Proof
Plugin123 CVE-Plugin123 Host1, Host2, Host3 Version 1.2.3 detected, Version 1.2.3 detected, Version 4.5.6 detected
Plugin456 Plugin456-2021 Vuln Host1, Host2, Host3 Version 7.8.9 detected, Version 10.11.12 detected, Version 10.11.12 detected

虽然实现了每个漏洞审查 1 行的目标,但如果/当证明很长,和/或有很多很多主机报告漏洞时,这并不能提供一个很好的方法来审查漏洞证明.

我希望如何接收输出:

PluginID Description Host Vuln Proof
Plugin123 CVE-Plugin123 Host1, Host2, Host3 Host1, Host 2: Version 1.2.3 detected
Host 3: Version 4.5.6 detected
Plugin456 Plugin456-2021 Vuln Host1, Host2, Host3 Host 1: Version 7.8.9 detected
Host 2, Host3: Version 10.11.12 detected

我当前的宏是这样的:

Sub CombineDupRows()
    Dim cCR As cCombinedRows
    Dim colCR As Collection
    Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
    Dim vSrc As Variant, vRes() As Variant
    Dim S As String
    Dim I As Long, J As Long
    
Sheets("Results").Activate
    
Set wsSrc = Worksheets("Source")
Set wsRes = Worksheets("Results")
    Set rRes = wsRes.Cells(1, 1)
    
With wsSrc
    vSrc = .Range("a1", .Cells(.Rows.Count, "A").End(xlUp)).Resize(columnsize:=4)
End With
Application.ScreenUpdating = False
'collect source data
Set colCR = New Collection
On Error Resume Next
For I = 1 To UBound(vSrc)
    Set cCR = New cCombinedRows
    With cCR
        For J = 1 To 2
            .Key(J) = CStr(vSrc(I, J))
        Next J
        
        .Phrase(0) = vSrc(I, 3)
        .ProofPhrase(0) = vSrc(I, 4)
        
        'The key will be the concatenation of columns 1-2
        S = Join(.Keys, Chr(1))
        
        'if key is duplicate, add phrase to existing collection item
        On Error Resume Next
            colCR.Add cCR, S
            Select Case Err.Number
                Case 457  'duplicate key
                    Err.Clear
                    colCR(S).Phrase(UBound(colCR(S).Phrases) + 1) = .Phrase(0)
                    colCR(S).ProofPhrase(UBound(colCR(S).ProofPhrases) + 1) = .ProofPhrase(0)
                Case Is <> 0  'some other error.  Stop for debugging
                    Debug.Print Err.Number, Err.Description, Err.Source
                    Stop
                End Select
        On Error GoTo 0
    End With
Next I

'Create results array
ReDim vRes(1 To colCR.Count, 1 To 4)
For I = 1 To colCR.Count
    With colCR(I)
        For J = 1 To 2
            vRes(I, J) = colCR(I).Key(J)
        Next J
        vRes(I, J) = Join(.Phrases, " " & Chr(13) & Chr(10))
        vRes(I, 4) = Join(.ProofPhrases, " " & Chr(13) & Chr(10))
    End With
Next I

Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
With rRes
    .Value = vRes
End With

作为其中的一部分,我还必须创建一个类模块,如下所示:

Option Explicit
Private pKeys() As String
Private pKey As String
Private pPhrases() As String
Private pPhrase As String
Private ProofpPhrases() As String
Private ProofpPhrase As String

Private Sub Class_Initialize()
    ReDim pKeys(0)
    ReDim pPhrases(0)
    ReDim ProofpPhrases(0)
End Sub


Public Property Get Keys() As String()
    Keys = pKeys
End Property

Public Property Get Key(index As Long) As String
    Key = pKeys(index)
End Property

Public Property Let Key(index As Long, strValue As String)
    If index > UBound(pKeys) Then ReDim Preserve pKeys(index)
    pKeys(index) = strValue
End Property

Public Property Get Phrases() As String()
    Phrases = pPhrases
End Property

Public Property Get Phrase(index As Long) As String
    Phrase = pPhrases(index)
End Property

Public Property Let Phrase(index As Long, strValue As String)
    If index > UBound(pPhrases) Then ReDim Preserve pPhrases(index)
    pPhrases(index) = strValue
End Property

Public Property Get ProofPhrases() As String()
    ProofPhrases = ProofpPhrases
End Property

Public Property Get ProofPhrase(index As Long) As String
    ProofPhrase = ProofpPhrases(index)
End Property

Public Property Let ProofPhrase(index As Long, strValue As String)
    If index > UBound(ProofpPhrases) Then ReDim Preserve ProofpPhrases(index)
    ProofpPhrases(index) = strValue
End Property

我尝试再次为主机添加一些字符串类型,它们可用于在结果的证明列中提供某种连接,但到目前为止我还没有成功(即使它甚至不完全是我正在寻找的)。 我花了很多时间修修补补并试图让它发挥作用,最后我寻求帮助。我可以在宏中添加什么以使其正常工作以按照我正在寻找的方式获得结果?

【问题讨论】:

    标签: excel vba sorting


    【解决方案1】:

    我一直在尝试学习 Power Query(在 Windows Excel 2010+ 和 Office 365 中可用),因此我将介绍这个六步过程作为替代方法。

    使用 Power Query

    • 选择数据表中的某个单元格
    • Data =&gt; Get&amp;Transform =&gt; from Table/Range
    • 当 PQ 编辑器打开时:Home =&gt; Advanced Editor
    • 记下第 2 行中的表 Name
    • 粘贴下面的 M 代码代替您看到的内容
    • 将第 2 行中的表名称更改回最初生成的名称。
    • 阅读 cmets 并探索 Applied Steps 以了解算法

    M 码

    let
    
    //Change Name in next line to actual name in your workbook
        Source = Excel.CurrentWorkbook(){[Name="Table22"]}[Content],
    
    //Set data types
        #"Changed Type" = Table.TransformColumnTypes(Source,{
            {"PluginID", type text}, 
            {"Description", type text}, 
            {"Host", type text}, 
            {"Vuln Proof", type text}}),
    
    //Group by PluginID
        #"Grouped Rows" = Table.Group(#"Changed Type", {"PluginID"}, {
    
    //If there is more than on associated Description, not sure what you want to do
    //  so we only return the first entry
            {"Description", each [Description]{0}},
    
    // Aggregate the Hosts
            {"Host", each Text.Combine([Host], ", ")},
    
    // Combine Host:Vuln Proofs by grouping each subgroup by the Vuln Proof
    // and extracting the associated Hosts
            {"Vuln Proof", (t)=> Table.Group(t, "Vuln Proof",{
                {"Hosts", (x)=> Text.Combine(x[Host],", ")}})}}),
    
    // Add custom column to create a list of the vuln proof and hosts from each subtable
        #"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", 
            each List.Transform(List.Zip({[Vuln Proof][Hosts],[Vuln Proof][Vuln Proof]}),
                                    each Text.Combine(_,":"))),
    
    //Then concatenate the list of hostes with the vuln proof
        #"Extracted Values" = Table.TransformColumns(#"Added Custom", 
            {"Custom", each Text.Combine(List.Transform(_, Text.From), "#(lf)"), type text}),
    
    //remove unneeded column        
        #"Removed Columns" = Table.RemoveColumns(#"Extracted Values",{"Vuln Proof"})
    in
        #"Removed Columns"
    

    【讨论】:

    • 谢谢,这真的很有帮助。 vuln 结果实际上有大约 12 个我匹配的字段(主要是插件 ID),因此如果前 12 个字段相同,则它们合并为 1(如果插件 ID 仍然匹配,它们应该始终相同)。如果我想在 12 上分组,我会怎么做?我已经开始玩这个了,我得到一个错误,说''' Expression.Error: 14 arguments were passed to a function which expects between 3 and 5.'''
    • @Cbohn 不确定您的意思。如果 12 个不同的 vuln 结果 都在同一列中,则无需更改任何内容。
    • 谢谢,我的意思是有 12 个不同的列应该充当“键”(我认为那是 table.group 函数的正确术语。我想如果我只是添加列还需要充当用逗号分隔“PluginID”的位置的“键”然后它起作用了。所以基本上我替换了:#“Grouped Rows”= Table.Group(#“Changed Type”,{“PluginID”} , { With #"Grouped Rows" = Table.Group(#"Changed Type", {"PluginID","Description" etc. etc}, { 成功了!感谢您的帮助!
    • @Cbohn 很高兴您能够使其适应您的数据。
    【解决方案2】:

    这就是我的解决方法 - 抱歉没有时间尝试理解/调整您的代码...

    Sub Combine()
        Const SEP As String = "~~~"
        Dim r As Long, dictHosts As Object, dictProofs As Object, kp, k, host, proof
        Dim data, dict As Object, c As Range, s As String
        
        Set dictHosts = CreateObject("scripting.dictionary")
        Set dictProofs = CreateObject("scripting.dictionary")
    
        'read all data to array for processing
        data = ActiveSheet.Range("A2:D" & ActiveSheet.Cells(Rows.Count, "A").End(xlUp).Row).Value
        
        'process and combine
        For r = 1 To UBound(data, 1)
            k = data(r, 1) & SEP & data(r, 2)
            host = data(r, 3)
            proof = data(r, 4)
            'tracking vuln vs hosts
            If Not dictHosts.exists(k) Then dictHosts.Add k, New Collection 'new vuln
            dictHosts(k).Add host                              'add host for this vuln
            
            'tracking vuln vs host<>proof
            If Not dictProofs.exists(k) Then dictProofs.Add k, CreateObject("scripting.dictionary")
            Set dict = dictProofs(k)
            If Not dict.exists(proof) Then dict.Add proof, New Collection
            dict(proof).Add host
        Next r
        
        'output results
        Set c = ActiveSheet.Range("G1")
        For Each k In dictHosts
            c.Value = Split(k, SEP)(0)               'plugin ID
            c.Offset(0, 1).Value = Split(k, SEP)(1)  'description
            c.Offset(0, 2).Value = Join(ColToArray(dictHosts(k)), ", ") 'list of hosts
            s = ""
            Set dict = dictProofs(k)
            'build the proof: hosts string
            For Each kp In dict
                s = s & IIf(s <> "", vbLf, "") & kp & ": " & Join(ColToArray(dict(kp)), ",")
            Next kp
            c.Offset(0, 3).Value = s
            
            Set c = c.Offset(1, 0)
        Next k
    End Sub
    
    'Utility function: get an array from a Collection
    Function ColToArray(col As Collection)
        Dim v, arr, i
        ReDim arr(0 To col.Count - 1)
        For i = 1 To col.Count
            arr(i - 1) = col(i)
        Next i
        ColToArray = arr
    End Function
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多