Malicious Office (OLE) — malware analysis report

Static analysis result for SHA-256 1c8451931e7e259e…

MALICIOUS

Office (OLE)

843.5 KB Created: 2020-09-21 17:45:52 Authoring application: AddinUpdater First seen: 2021-09-14
MD5: 480df7323f98c30e5115067e89ac59b8 SHA-1: 76296078213f20991873c554242417c1acbc66ae SHA-256: 1c8451931e7e259e80d4096f849acbd4dbc7d950cb1249f2ea8ca1094465da30
720 Risk Score

Malware Insights

MITRE ATT&CK
T1059.005 Visual Basic T1566.001 Spearphishing Attachment T1203 Exploitation for Client Execution

The sample is a malicious Excel document containing obfuscated VBA macros designed to execute a payload. The Workbook_Open event triggers the execution of a staged loader that uses CreateObject and Shell() calls, indicative of downloading and executing a second-stage payload. The presence of URLDownloadToFile API calls and embedded URLs like 'http://ExcelVBA.ru/php2/updates.php' strongly suggests the macro's intent is to download and run additional malicious content. The document body presents a deceptive interface for language selection and EULA agreement to lure the user into enabling macros.

Heuristics 18

  • ClamAV: Xls.Malware.Powmet-6922919-0 critical CLAMAV_DETECTION
    ClamAV detected this file as malware: Xls.Malware.Powmet-6922919-0
  • Reference to URLDownloadToFile API critical SC_STR_URLDOWNLOAD
    Reference to URLDownloadToFile API
  • VBA macros detected medium 11 related findings OLE_VBA_MACROS
    Document contains VBA macro code
  • Shell() call in VBA critical OLE_VBA_SHELL
    Shell() call in VBA
    Matched line in script
            URL$ = Replace(URL$, "%hid%", HID)
            CreateObject("WScript.Shell").Run URL$
        End If
  • WScript.Shell usage critical OLE_VBA_WSCRIPT
    WScript.Shell usage
    Matched line in script
            URL$ = Replace(URL$, "%hid%", HID)
            CreateObject("WScript.Shell").Run URL$
        End If
  • URLDownloadToFile in VBA critical OLE_VBA_DOWNLOAD
    URLDownloadToFile in VBA
    Matched line in script
    #If VBA7 Then        '  Office 2010-2013
        Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
                (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
  • VBA ActiveX event runs worksheet-decoded XLM formulas critical OLE_VBA_ACTIVEX_XLM_CELL_STAGER
    VBA code attached to an ActiveX/UserForm event reconstructs formula text from worksheet constants using Split/Replace/Mid or character shifting, then executes it through ExecuteExcel4Macro or Run. This is a high-confidence malware stager that hides XLM formula execution in sheet cells; it is not a document-parser CVE.
    Matched line in script
        On Error Resume Next
        MSG_StopMacro$ = Run(TWN & "tt", "PI_MSG_StopMacro")
        If MSG_StopMacro$ = "" Then MSG_StopMacro$ = "Do you really want to stop the macro?"
  • Obfuscated auto-exec VBA loader critical OLE_VBA_OBFUSCATED_AUTOEXEC_LOADER
    Auto-exec VBA reconstructs strings with a heavy custom decoder (numeric char-array, repeated hex-string decode, or junk-token Replace removal) and feeds them to a COM-instantiation or execution sink. This obfuscated-loader shape keeps CreateObject/Shell/URL indicators out of the macro source.
    Matched line in script
    Function ClipboardText()
        With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
            .GetFromClipboard
  • VBA ActiveX event launches decoded Excel4 macro critical OLE_VBA_ACTIVEX_XLM_STAGER
    The compiled VBA p-code (identifier table) references an auto-firing ActiveX/control event together with ExecuteExcel4Macro, while the decompressed source does not — the VBA-stomping shape of the ActiveX-event XLM stager. The control event bridges into XLM formula execution to call Win32 / drop payloads, hidden from source-level scanners.
  • CreateObject call high OLE_VBA_CREATEOBJ
    CreateObject call
    Matched line in script
        filename$ = Environ("TEMP") & "\macro_log.txt"
        With CreateObject("scripting.filesystemobject").CreateTextFile(filename, True)
            .Write Mid(LogString, 3): .Close
  • GetObject call high OLE_VBA_GETOBJ
    GetObject call
    Matched line in script
    Function ClipboardText()
        With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
            .GetFromClipboard
  • VBA instantiates a COM class by raw CLSID high OLE_VBA_GETOBJECT_CLSID_EVASION
    VBA uses GetObject("new:{CLSID}") to instantiate a COM class by raw CLSID rather than a CreateObject ProgID — an uncommon bypass of name-based macro detection.
    Matched line in script
    Function ClipboardText()
        With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
            .GetFromClipboard
  • Workbook_Open macro low OLE_VBA_WBOPEN
    Workbook_Open macro
    Matched line in script
    Private Sub Workbook_Open()
        On Error Resume Next: Dim FirstRun As Boolean
  • Environ() call (env variable access) low OLE_VBA_ENVIRON
    Environ() call (env variable access)
    Matched line in script
            Next: res$ = res$ & buf$
            tmp_file$ = Environ("tmp") & "\file_MainPicture_" & PROJECT_NAME$: Kill tmp_file$
            ff& = FreeFile: Open tmp_file$ For Binary Access Write As #ff
  • Reference to CreateProcess API high SC_STR_CREATEPROCESS
    Reference to CreateProcess API
  • Reference to Windows Script Host high SC_STR_WSCRIPT
    Reference to Windows Script Host
  • Suspicious extracted artifact info EXTRACTED_FILE_STATIC_TRIAGE
    One or more files extracted from inside this sample matched static suspicious-content checks such as script obfuscation, encoded payload blobs, packed data, or execution/download terms.
  • Embedded URL info EMBEDDED_URL
    One or more URLs were extracted from the document. The URL itself is not a detection — see the per-URL labels for which channel (macro, JS, link annotation, document body, ...) reached each URL.
    URL https://ExcelVBA.ru/ Referenced by macro
    • https://ExcelVBA.ru/programmes/Lookup/CopyRowsA@�Referenced by macro
    • http://ExcelVBA.ru/php2/updates.php���Referenced by macro
    • http://ExcelVBA.ru/php2/updates.phpReferenced by macro
    • https://ExcelVBA.ru/�Referenced by macro
    • http://Excel-Automation.com/Referenced by macro
    • http://www.wordarticles.com/Shorts/RibbonVBA/RibbonVBADemo.phpReferenced by macro
    • http://ExcelVBA.ru/Referenced by macro
    • http://excelvba.ru/Referenced by macro
    • https://excelvba.ru/programmes/LookupReferenced by macro
    • https://ExcelVBA.ru/programmes/Lookup/CopyRowsReferenced by macro

Extracted artifacts 1

Files carved from inside the sample during analysis.

FilenameKindSourceSize
macros.bas vba-macro oletools.olevba.extract_macros (decoded VBA source) 632573 bytes
SHA-256: 991c3e57d9f02f9e50803d6da36039619f6bfd3f76bcf3ce84232027c25a8928
Detection
ClamAV: No threats found
Obfuscation or payload: likely
Carved artifact contains 10 long base64-like blob(s).
Preview script
First 1,000 lines of the extracted script
Attribute VB_Name = "ThisWB"
Attribute VB_Base = "0{00020819-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
'---------------------------------------------------------------------------------------
' Author        : Igor Vakhnenko                   Date: 25.12.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    DeleteProgramCommandBar
    
    Application.OnKey "^%a"
End Sub

Private Sub Workbook_Open()
    On Error Resume Next: Dim FirstRun As Boolean
    FirstRun = SETT.IsFirstRun
    If FirstRun Then ShowFirstRunForm
    If SetupCancelled Then
        Application.DisplayAlerts = False
        If TrueDeveloper Then MsgBox "Setup Cancelled", vbInformation Else ThisWorkbook.Close False
        Application.DisplayAlerts = True
        Exit Sub
    End If
    Enable_AccessVBOM_Macro_DataConnections        ' disables notifications
    SaveSetting PROJECT_NAME$, "Setup", "AddinPath", ThisWorkbook.FullName
    If FirstRun Then If IsObject(F_Greeting) Then F_Greeting.Show
    CreateProgramCommandBar 0
    
    If HideDeveloperInfo Then Application.OnKey "^%a", "ShowMainForm"
End Sub


Attribute VB_Name = "shtr"
Attribute VB_Base = "0{00020820-0000-0000-C000-000000000046}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = True
'---------------------------------------------------------------------------------------
' Author        : Igor Vakhnenko                   Date: 08.01.2016
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Const PREFIX$ = "MENU"


Function NewTranslateID() As String
    On Error Resume Next
    Dim ra As Range, coll As New Collection
    Set ra = shtr.Range(shtr.Range("a" & TRANSLATE_SHEET_FIRST_ROW), shtr.Range("A" & shtr.Rows.Count).End(xlUp))
    arr = ra.Value
    For i = LBound(arr) To UBound(arr)
        coll.Add arr(i, 1), CStr(arr(i, 1))
    Next i

    For i = 1 To 1000
        Err.Clear: id$ = PREFIX$ & "_" & Format(i, "0000")
        coll.Add id$, id$
        If Err = 0 Then NewTranslateID = id$: Exit Function
    Next
    MsgBox "Can't create ID$", vbExclamation, "Function NewTranslateID()"
End Function

Function ClipboardText()
    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
        .GetFromClipboard
        ClipboardText = .GetText
    End With
End Function

Sub SetClipboardText(ByVal txt$)
    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
        .SetText txt$
        .PutInClipboard
    End With
End Sub

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
    On Error Resume Next
    If Target.Column = 1 And Target.Cells.Count = 1 Then
        If Target <> "" Then Cancel = True: SetClipboardText "tt(""" & Target & """) "
    End If
End Sub


Attribute VB_Name = "mod_CommonFunctions"
'---------------------------------------------------------------------------------------
' Module        : mod_CommonFunctions                    Version:
' Author        : Igor Vakhnenko                   Date: 20.12.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------
Option Compare Text: Option Private Module

Function ColumnsStringToRangeAddress(ByVal txt$) As String
    ' gets string like "A-F,8" and returns range "$A:$F,$H:$H"
    On Error Resume Next
    Dim ra As Range, col As Variant
    With ThisWorkbook.Worksheets(1)
        For Each col In ParseColumnsStringEx(txt)
            If ra Is Nothing Then Set ra = .Cells(Val(col)) Else Set ra = Union(ra, .Cells(Val(col)))
        Next
    End With
    ColumnsStringToRangeAddress = ra.EntireColumn.Address
End Function

Function ParseColumnsStringEx(ByVal txt$, Optional ByRef norm1$, Optional ByRef norm2$) As Variant
    ' Принимает в качестве параметра строку типа "A-C;8,,11-9, Е-К; 4,21,"
    ' Возвращает одномерный (горизонтальный) массив в формате Array(1,2,3,8,11,10,9,5,6,7,8,9,10,11,4,21)
    ' (пустые значения удаляются; диапазоны типа 9-15 и 17-13 раскрываются,
    '  буквенные диапазоны заменяются на числовые, русские буквы заменяются латинскими)

    On Error Resume Next
    ' устраняем возможные ошибки пользовательского ввода
    Const enARR$ = "ABCEHKMOPTX", ruARR$ = "АВСЕНКМОРТХ"
    Const cc& = 2560        ' ограничение на максимальный номер столбца
    Dim i&, arr, n&, tmpArr, spl, j&, cn&
    For i = 1 To Len(enARR$): txt = Replace(txt, Mid(ruARR$, i, 1), Mid(enARR$, i, 1)): Next i
    txt = Replace(txt, " ", ""): txt = Replace(txt, ";", ",")
    txt = Replace(txt, ":", "-"): txt = Replace(txt, ".", ","): txt = UCase(txt)
    For i = 1 To Len(txt)
        If Not Mid(txt, i, 1) Like "[A-Z0-9,-]" Then Mid(txt, i, 1) = ","
    Next i
    While InStr(1, txt, ",,"): txt = Replace(txt, ",,", ","): Wend
    While InStr(1, txt, "--"): txt = Replace(txt, "--", "-"): Wend
    txt = Replace(txt, ",-", ","): txt = Replace(txt, "-,", ",")
    If Left(txt, 1) = "-" Or Left(txt, 1) = "," Then txt = Mid(txt, 2)
    If Right(txt, 1) = "-" Or Right(txt, 1) = "," Then txt = Left(txt, Len(txt) - 1)
    norm1$ = Replace(txt$, ",", ", ")        ' возвращаем «нормализованную» строку для подстановки в поле

    arr = Split(txt$, ","): ReDim tmpArr(0 To 0)
    For i = LBound(arr) To UBound(arr)
        spl = Split(arr(i), "-")
        For j = LBound(spl) To UBound(spl)
            cn& = 0: cn& = ColumnNameToColumnNumber(spl(j)): If cn& Then spl(j) = cn&
            If Not spl(j) Like String(Len(spl(j)), "#") Then spl(j) = ""
        Next j
        If Val(spl(0)) > cc& Then spl(0) = "": spl(UBound(spl)) = ""
        If Val(spl(UBound(spl))) > cc& Then spl(UBound(spl)) = cc&
        If UBound(spl) > 1 Then arr(i) = spl(0) & "-" & spl(UBound(spl)) Else arr(i) = Join(spl, "-")
        If UBound(spl) = 1 Then If spl(0) = spl(1) Then arr(i) = spl(0)
        If UBound(spl) = 1 Then If spl(0) = "" Then arr(i) = spl(1)
    Next i
    norm2$ = Join(arr, ","): norm2$ = Replace(norm2$, ",-", ","): norm2$ = Replace(norm2$, "-,", ",")
    While InStr(1, norm2$, ",,"): norm2$ = Replace(norm2$, ",,", ","): Wend
    If Left(norm2$, 1) = "," Then norm2$ = Mid(norm2$, 2)
    If Right(norm2$, 1) = "," Then norm2$ = Left(norm2$, Len(norm2$) - 1)

    For i = LBound(arr) To UBound(arr)
        Select Case True
            Case arr(i) = "", Val(arr(i)) < 0
            Case IsNumeric(arr(i))
                tmpArr(UBound(tmpArr)) = arr(i): ReDim Preserve tmpArr(0 To UBound(tmpArr) + 1)
            Case arr(i) Like "*#-#*"
                spl = Split(arr(i), "-")
                If UBound(spl) = 1 Then
                    If IsNumeric(spl(0)) And IsNumeric(spl(1)) Then
                        If spl(0) <= cc& Then
                            If spl(1) > cc& Then spl(1) = cc&
                            For j = Val(spl(0)) To Val(spl(1)) Step IIf(Val(spl(0)) > Val(spl(1)), -1, 1)
                                tmpArr(UBound(tmpArr)) = j: ReDim Preserve tmpArr(0 To UBound(tmpArr) + 1)
                            Next j
                        End If
                    End If
                End If
        End Select
    Next i
    If UBound(tmpArr) Then
        ReDim Preserve tmpArr(0 To UBound(tmpArr) - 1)
        ParseColumnsStringEx = tmpArr
    End If
End Function

Function GetFilePathEx(Optional ByVal FileType$ = "", Optional ByVal DialogTitle$, _
                       Optional ByVal FilterDescription$ = "Excel files", Optional ByVal FilterExtention$ = "*.*") As String
    On Error Resume Next
    InitialPath$ = ThisWorkbook.Path & "\"
    If DialogTitle$ = "" Then DialogTitle$ = tt("SelectFileDialogCaption")
    With Application.FileDialog(msoFileDialogOpen)
        .ButtonName = tt("SelectButtonCaption"): .Title = DialogTitle$
        .InitialFileName = SETT.GetText("GetFilePathEx_" & FileType, InitialPath)
        .Filters.Clear: .Filters.Add FilterDescription, FilterExtention
        If .Show <> -1 Then Exit Function
        GetFilePathEx = .SelectedItems(1)
        Folder$ = Left(.SelectedItems(1), InStrRev(.SelectedItems(1), "\"))
        SETT.SetText "GetFilePathEx_" & FileType, Folder$
    End With
End Function

Function PickNewColor(Optional ByVal i_OldColor As Double = xlNone) As Double
    ' shows pick color dialog and returns selected color (RGB format)
    On Error Resume Next:
    PickNewColor = i_OldColor
    Const BGColor As Long = 13160660, ColorIndexLast As Long = 32
    Dim myOrgColor As Double, myNewColor As Double, WB As Workbook
    Dim myRGB_R As Integer, myRGB_G As Integer, myRGB_B As Integer
    If ActiveWorkbook Is Nothing Then Application.ScreenUpdating = False: Set WB = Workbooks.Add
    myOrgColor = ActiveWorkbook.Colors(ColorIndexLast)        'save original palette color

    i_Color = IIf(i_OldColor = xlNone, BGColor, i_OldColor): myRGB_R = i_Color Mod 256
    i_Color = i_Color \ 256: myRGB_G = i_Color Mod 256
    i_Color = i_Color \ 256: myRGB_B = i_Color Mod 256
    ActiveWorkbook.ResetColors        'AppActivate Application.Name
    If Application.Dialogs(xlDialogEditColor).Show(ColorIndexLast, myRGB_R, myRGB_G, myRGB_B) Then
        PickNewColor = ActiveWorkbook.Colors(ColorIndexLast)
        ThisWorkbook.Colors(ColorIndexLast) = myOrgColor
    End If
    If Not WB Is Nothing Then WB.Close False: Application.ScreenUpdating = True
End Function

Function GetKeyName(ByVal KeyCode As Integer, ByVal Shift As Integer) As String
    Select Case KeyCode
        Case 112 To 135: Button$ = "F" & (KeyCode - 111)
        Case 32: Button$ = "SpaceBar"
        Case 8: Button$ = "BackSpace"
        Case 9: Button$ = "Tab"
        Case 13: Button$ = "Enter"
        Case 16: Button$ = ""        '"Shift"
        Case 17: Button$ = ""        '"Ctrl"
        Case 18: Button$ = ""        '"Alt"
        Case 20: Button$ = "CapsLock"
        Case 27: Button$ = "Esc"
        Case 33: Button$ = "PageUp"
        Case 34: Button$ = "PageDown"
        Case 35: Button$ = "End"
        Case 36: Button$ = "Home"
        Case 37: Button$ = "Left Arrow"
        Case 38: Button$ = "Up Arrow"
        Case 39: Button$ = "Right Arrow"
        Case 40: Button$ = "Down Arrow"
        Case 44: Button$ = "PrintScreen"
        Case 45: Button$ = "Insert"
        Case 46: Button$ = "Delete"
        Case vbKeyNumlock: Button$ = "Numlock"
        Case 145: Button$ = "ScrollLock"

        Case 91: Button$ = "Win(Left)"
        Case 92: Button$ = "Win(Right)"
        Case 96 To 105: Button$ = "Numpad (" & KeyCode - 96 & ")"
        Case vbKeyMultiply: Button$ = "Numpad (*)"
        Case vbKeyAdd: Button$ = "Numpad (+)"
        Case vbKeySubtract: Button$ = "Numpad (-)"
        Case vbKeyDecimal: Button$ = "Numpad (,)"
        Case vbKeyDivide: Button$ = "Numpad (/)"

        Case 166: Button$ = "Browser Back"
        Case 167: Button$ = "Browser Forward"
        Case 168: Button$ = "Browser Refresh"
        Case 169: Button$ = "Browser Stop"
        Case 170: Button$ = "Browser Search"
        Case 171: Button$ = "Browser Favorites"
        Case 172: Button$ = "Browser Home"
        Case 173: Button$ = "Volume Mute"
        Case 174: Button$ = "Volume Down"
        Case 175: Button$ = "Volume Up"
        Case 176: Button$ = "Next Track"
        Case 177: Button$ = "Previous Track"
        Case 178: Button$ = "Stop Media"
        Case 179: Button$ = "Play/Pause"
        Case 180: Button$ = "Start Mail"
        Case 181: Button$ = "Select Media"
        Case 182: Button$ = "Start App 1"
        Case 183: Button$ = "Start App 2"

        Case 48 To 57, 65 To 90: Button$ = Chr(KeyCode)
        Case Else: Button$ = "{button " & KeyCode & "}"
    End Select

    If Len(Button$) Then
        If (Shift And 1) Then GetKeyName = GetKeyName & "Shift + "
        If (Shift And 2) Then GetKeyName = GetKeyName & "Ctrl + "
        If (Shift And 4) Then GetKeyName = GetKeyName & "Alt + "
    End If
    GetKeyName = GetKeyName & Button$
End Function

Function Chars(ByVal txt As String) As Variant
    On Error Resume Next: ReDim arr(0 To Len(txt) - 1)
    For i = LBound(arr) To UBound(arr): arr(i) = Mid(txt, i + 1, 1): Next i
    If Err Then Chars = Array() Else Chars = arr
End Function

Function SafeText(ByVal txt As String) As String
    For i = 1 To Len(txt)
        SafeText = SafeText & IIf(i = 1, "", "-") & AscW(Mid(txt, i, 1))
    Next i
End Function

Function RestoreText(ByVal txt As String) As String
    On Error Resume Next: arr = Split(txt, "-")
    For i = LBound(arr) To UBound(arr): arr(i) = ChrW(Val(arr(i))): Next i
    RestoreText = Join(arr, "")
End Function

Function ColumnNameByColumnNumber(ByVal col As Long) As String
    resA1 = Application.ConvertFormula("=r1c" & col, xlR1C1, xlA1)
    ColumnNameByColumnNumber = col & " «" & Split(resA1, "$")(1) & "»"
End Function

Function ColumnNameToColumnNumber(ByVal txt$) As Long
    On Error Resume Next    ' преобразует имя столбца в номер.   в случае ошибки возвращает 0
    ColumnNameToColumnNumber = Split(Application.ConvertFormula(txt$ & "1", xlA1, xlR1C1, True), "C")(1)
End Function


Attribute VB_Name = "mod_Main"
'---------------------------------------------------------------------------------------
' Module        : mod_Main                    Version:
' Author        : Igor Vakhnenko                   Date: 16.10.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Compare Text: Option Private Module ' : Option Explicit
Public Const PROJECT_NAME$ = "Lookup", PROJECT_YEAR& = 2013

Private Sub CreateCommandBar(): CreateProgramCommandBar 0: End Sub
    
Sub CreateProgramCommandBar(Optional ByVal RefreshOnly As Boolean = True)
    On Error Resume Next
    SaveDefaultSettings
    
    Dim AddinMenu As CommandBar, coll As Collection, i&, MainMacroButton As Object
    Application.ScreenUpdating = False
    If Not RefreshOnly Then Run DeleteOldCommandBar
    Set AddinMenu = GetCommandBar(PROJECT_NAME, True Or RefreshOnly)
    
    Set MainMacroButton = Add_Control(AddinMenu, ct_BUTTON, 501, "LookupData", tt("MENU_001"), msoButtonIconAndCaption, True)
    AddSettingsSwitcher AddinMenu, ct_DROPDOWN, , "LookupData", MainMacroButton
    
    Add3Buttons AddinMenu
    If Not RefreshOnly Then
        RunWithDelay "ActivateAddinsTab"
        AddUpdateButton AddinMenu
        RunWithDelay "ActivateAddinsTab"
    End If
    If Developer Then
        Add_Control(AddinMenu, ct_BUTTON, 0, "0", "", msoButtonIconAndCaption, False).Enabled = 0
        Add_Control AddinMenu, ct_BUTTON, , "ShowFirstRunForm", "Show FirstRun Form", msoButtonIconAndCaption, True
        Add_Control AddinMenu, ct_BUTTON, , "ToggleIsAddin", "Show/Hide TR Sheet", msoButtonIconAndCaption, True
    End If
    Application.ScreenUpdating = True
End Sub

Sub ToggleIsAddin()
    On Error Resume Next
    ThisWorkbook.IsAddin = Not ThisWorkbook.IsAddin
End Sub

Function HideDeveloperInfo() As Boolean
    On Error Resume Next
    HideDeveloperInfo = ThisWorkbook.Name Like "*--*"
End Function

Sub SaveDefaultSettings()
    On Error Resume Next
    With SETT
        .LoadAllSettings
        '---------------------------------------------------------------
        .AddDefaultValue "OptionButton_SF_ActiveWorkbook", True
        .AddDefaultValue "OptionButton_SF_ActiveSheet", True
        .AddDefaultValue "ComboBox_SF_SheetIndex", 1
        .AddDefaultValue "CheckBox_IgnoreCase", True
        .AddDefaultValue "CheckBox_IgnoredCharsEnabled", True
        .AddDefaultValue "TextBox_IgnoredChars", "{TAB}{SPACE}{160}{CR}{LF}", , True
        
        .AddDefaultValue "ComboBox_SF_Found_Color_Interior", vbGreen
        .AddDefaultValue "ComboBox_SF_Found_Color_Font", xlNone
        .AddDefaultValue "TextBox_SF_Found_ColumnsList", "A-F"
        .AddDefaultValue "ComboBox_SF_NotFound_Color_Interior", 13408767        'vbRed
        .AddDefaultValue "ComboBox_SF_NotFound_Color_Font", xlNone
        .AddDefaultValue "TextBox_SF_NotFound_ColumnsList", "A-B"
        
        .AddDefaultValue "ComboBox_SF_FirstRow", 2
        .AddDefaultValue "ComboBox_SF_LastRowColumn", "auto"
        
        '---------------------------------------------------------------
        .AddDefaultValue "OptionButton_DF_ActiveWorkbook", True
        .AddDefaultValue "OptionButton_DF_ActiveSheet", True
        .AddDefaultValue "ComboBox_DF_SheetIndex", 1
        
        .AddDefaultValue "ComboBox_DF_Found_Color_Interior", 15849925        ' blue
        .AddDefaultValue "ComboBox_DF_Found_Color_Font", xlNone
        .AddDefaultValue "TextBox_DF_Found_ColumnsList", "A-F"
        .AddDefaultValue "ComboBox_DF_NotFound_Color_Interior", 10092543        'yellow
        .AddDefaultValue "ComboBox_DF_NotFound_Color_Font", xlNone
        .AddDefaultValue "TextBox_DF_NotFound_ColumnsList", "A-B"
        
        .AddDefaultValue "ComboBox_DF_FirstRow", 2
        .AddDefaultValue "ComboBox_DF_LastRowColumn", "auto"
        
        '---------------------------------------------------------------
        .AddDefaultValue "TextBox_SF_CompareColumnsList", "2"
        .AddDefaultValue "TextBox_DF_CompareColumnsList", "3"
        .AddDefaultValue "TextBox_SF_CopyColumnsList", "8, 5-3, K-M, R, S"
        .AddDefaultValue "TextBox_DF_CopyColumnsList", "H-N, P, R"
        
        .AddDefaultValue "CheckBox_CopyNewRows", False
        '---------------------------------------------------------------
        .AddDefaultValue "TextBox_CopyRows_SF_ColumnsList", "0,3-5,0,0,2,1"
        .AddDefaultValue "CheckBox_CopyRows_SF_CheckColumnEnabled", False
        .AddDefaultValue "TextBox_CopyRows_SF_CheckColumnMask", "?*"
        .AddDefaultValue "ComboBox_CopyRows_DF_LastRowColumn", 1, True
        .AddDefaultValue "ComboBox_CopyRows_SF_CheckColumnNumber", 1, True
        
        .AddDefaultValue "ComboBox_CopyRows_DF_Color_Interior", 10092441        ' light green
        .AddDefaultValue "ComboBox_CopyRows_DF_Color_Font", xlNone
        .AddDefaultValue "TextBox_CopyRows_DF_ColouringColumnsList", "A-F"
        
        .AddDefaultValue "CheckBox_CopyRows_DF_Border", True
    End With
End Sub


Function GetFile_MainPicture() As String
    ' создаёт во временной папке файл, возвращает путь к созданному файлу
    On Error Resume Next: Dim F_TXT$, buf$, tmp_file$: Const BufLen& = 5000
    F_TXT$ = F_TXT$ & "FFD8FFE000104A46494600010101012C012C0000FFDB0043000504040504030505040506060506080E0908070708110C0D0A0E141115141311131316181F1B16171E1713131B251C1E2021232323151A26292622291F222322FFDB00430106060608070810090910221613162222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222FFC00011080029005003012200021101031101FFC4001A000003010101010000000000000000000004060700050801FFC40037100001040004040405020309000000000001020304110005122106132231074151711423246181153233428234354352627291A1B1FFC400190100020301000000000000000000000000040500020301FFC4002B11000103030301060700000000000000000100020304112112133141053371C1D1F03234516181B1E1FFDA000C03010002110311003F00BCE799D670C66B25197BCB212F389D0EBC50000AA1A6BCBF1819ACFB373A39F25F492F212A4B6F5D03762EFBF6AFCE04E278A6566F280806754877A5D25213D47F6F49B0702C760B6DB23E11C674C86CE94926BBEF75DC5EC3EFDB05B5A2C3096BDEEB9CA74566531B8115C0EBC80B8CDAD4B90FE93A8A16492350D8909AF657A61278DB"
    F_TXT$ = F_TXT$ & "8A38832ACE16DE5599262B20F509CF14EFCB411A2CEE2C9B24F6230D4EB01FCAE26A8EB7DB30DAD464D8BA42CD51AD81A23DD5E9789D788D1512788D6972046989424DA27F4947CB6F6412074F91FE9FCA9AC716B0106D9F557AF7B9B134836CF91547F0BF39CD7388D3D59D4A4BEF364005B5EB40EB58D89DEE80047A8C51B129F0752A4C5CD438CB4CAB5D9432BD68FE239BDFF98F73F7F4ED8AB5E34A624C409F795AD1B8BA06927EBFB283CD5C5B3934D71B2A4AD0C2D4929EE0849AAC4A8E7F9F87D412F5B009A5FC490A03EE2EBFEF152CE7FB8331EFFD99CEDDFF0069C451705467ADEFD212AEA27E23590AEFDC8D1FF3BF960D88037BA952E208B15D0CDB8973A622BA634E790F2592A429C73E58572C9B5EF611A85DFA038E6F09717F12CFE2C851F31CD23BF116E84A9319ED4A279880411BF4D120FB8ED7581B881B232F923937AA3D687C7CB590D11A5760740ED7B6D7EF8E6704C511B8EB2E4B7974288D07C52A33BA9765D458D3E86859DB703B6C30AEADEE6D4D81C612A74AFDE0351E9D7FA9C38A6326566F28182ECED321D1A41D3A3A8EC0E8360FBE04623252863E91C6B4C86D40293A8A6AFA81D1DD37B7AD9C17C52DA1ECE256B6644AD321D14C2820A3A8ED7B5DFBE048CC37A59210EB25121B58E6EE456AEAB03C81"
    F_TXT$ = F_TXT$ & "EDBDDE1D37808B7F25333F113320E5F709C9A830DB4971D1A6BA17B01A45EF5F851F4C4FFC458EB91C40E06B2F19969429250F2F945B05A6C148E91608B07D87ADE1FE4B08970B2F2A8EE4FF00A36C172F4247CB58AA02FCEBFABED89EF8931933B8817F40EE6A116290E0694D7CB6B6BF3EDEBFCBF90A2B7E01E3EAAFDA1DD37C7C8A7AF0943C2166E90C222BC0F437AB98916E3A4289005955D9F3B2463E4283C6E97A6D393D2F17EDE5AA4B41B717A53D4D0524D3754001554455D93BC1A472E06648F855C4DC2BE1D6A0A29B71C37636DFBFDAF157C4823D70B72473C1FBAB52C42481B9239E0DBA948F96B3C42CE4F9F1E22796A64C4F91ADC6D6A0AD2BD47A123CB4F7BED89DAE0A4E60B77F497892A27E212A001DFBD68F3C5B73A35906626EBE99CDFD3A4E222B61AF8F5ABE0A6EA2A2798958D0ADFBE9FBFB61853B748B2B4EDD200BA173E6008329298D6A763E9E5A93497A9A23428E814076BDB638078219758E35CBCB994A21053E01752F87147E63742B4D8040DCFF00A123D307E7CC24E5B29286F99CF63498E7FC621A2347ED355B26EBFF0031C9E038898BC750143277E05BC0739C7C38156EA0E9AB277ABFC1F524AAADF9A1F84A9FDF0F109E38A52A566F27509CB01F76843B4A93D4763D62EFC8E0069B25318812FA65"
    F_TXT$ = F_TXT$ & "36AB77F70235751EADC0F3F718B9E361B096DD139753026F75319684498F9725D53F21422B40AD9572D29210BD8EFDF7A3FEE1841F12A2AE6710A94A893B334A410170145251F2DAD8907AAEBD7BA7B1F2F46636049A2DD16BD97678379A1B7B594AFC1E0E081989721C8876ABE4C84F5D971C2544F9EABBF638AA636363B147B6D0D57862DA6060E881CE2CE459804F7F8772B6BFE5388A29A273073A334D5A89B493CA56FE435F6FC6DBF6C5E7CF1B1BB1FA54922DC232BCF39D4753D0E7212CBEEF398D0A689FE2D34404036749F2BDF7C05C0D0571B8CA0286593E302F8A7A539CCD8BA8240DCD5E907BED58F49E31ED81A5804B26E1286342D2FD57F617FFD9"
    For i = 1 To Len(F_TXT$) / 2
        buf$ = buf$ & Chr(Val("&H" & Mid(F_TXT$, 2 * i - 1, 2)))
        If Len(buf$) > BufLen& Then res$ = res$ & buf$: buf$ = "": DoEvents
        Next: res$ = res$ & buf$
        tmp_file$ = Environ("tmp") & "\file_MainPicture_" & PROJECT_NAME$: Kill tmp_file$
        ff& = FreeFile: Open tmp_file$ For Binary Access Write As #ff
        Put #ff, , res$
        Close #ff
        If FileLen(tmp_file$) = Len(F_TXT$) / 2 Then GetFile_MainPicture = tmp_file$
    End Function
    
    
    
    

Attribute VB_Name = "mod_Functions"
'---------------------------------------------------------------------------------------
' Module        : mod_Functions                    Version:
' Author        : Igor Vakhnenko                   Date: 20.12.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Compare Text: Option Private Module

Function OpenWorkbooksList() As Variant
    On Error Resume Next
    Dim WB As Workbook, coll As New Collection, Item, i&
    For Each WB In Application.Workbooks
        If WB.Windows(1).Visible Then
            If WB.Path <> "" Then coll.Add WB.FullName
        End If
    Next
    ReDim arr(1 To coll.Count, 1 To 2)
    For Each Item In coll
        i = i + 1
        arr(i, 1) = Item
        arr(i, 2) = Dir(Item, vbNormal)
    Next
    OpenWorkbooksList = arr
End Function

Function ColoringEnable(ByVal FileType$) As Boolean
    On Error Resume Next        ' FileType$ = "SF" or "DF"
    ColoringEnable = SETT.GetBoolean("CheckBox_" & FileType$ & "_Found_Color_Interior") Or _
                     SETT.GetBoolean("CheckBox_" & FileType$ & "_NotFound_Color_Interior") Or _
                     SETT.GetBoolean("CheckBox_" & FileType$ & "_Found_Color_Font") Or _
                     SETT.GetBoolean("CheckBox_" & FileType$ & "_NotFound_Color_Font")
End Function

Function SpecialCells_TypeConstants(ByRef ra As Range) As Range
    ' возвращает диапазон, содержащий все заполненные ячейки диапазона ra
    On Error Resume Next: en& = Err.Number
    If ra.Worksheet.ProtectContents Then        ' если лист защищён
        Dim cell As Range
        ' перебираем все ячейки в диапазоне
        For Each cell In Intersect(ra, ra.Worksheet.UsedRange).Cells
            If Trim(cell.Value) <> "" Then        ' если ячейка непустая
                ' то добавляем её в результат
                If SpecialCells_TypeConstants Is Nothing Then
                    Set SpecialCells_TypeConstants = cell
                Else
                    Set SpecialCells_TypeConstants = Union(SpecialCells_TypeConstants, cell)
                End If
            End If
        Next cell

    Else        ' если защита листа не установлена - используем штатные средства Excel
        Set SpecialCells_TypeConstants = ra.SpecialCells(xlCellTypeConstants)
    End If
    If en& = 0 Then Err.Clear
End Function

Function SpecialCells_VisibleRows(ByRef ra As Range) As Range
    On Error Resume Next: en& = Err.Number
    If ra.Worksheet.ProtectContents Then
        Dim ro As Range
        For Each ro In Intersect(ra, ra.Worksheet.UsedRange.EntireRow).Rows
            If ro.EntireRow.Hidden = False Then
                If SpecialCells_VisibleRows Is Nothing Then
                    Set SpecialCells_VisibleRows = ro
                Else
                    Set SpecialCells_VisibleRows = Union(SpecialCells_VisibleRows, ro)
                End If
            End If
        Next ro
    Else
        Set SpecialCells_VisibleRows = ra.SpecialCells(xlCellTypeVisible)
    End If
    If en& = 0 Then Err.Clear
End Function

Function ParseString(ByVal txt As String) As Variant
    ' получает в качестве параметра текстовую строку для проверки
    ' возвращает двумерный массив размером N * 4 (где N - длина текстовой строки)
    On Error Resume Next
    n = Len(txt): ReDim arr(1 To n, 1 To 4)
    For i = LBound(arr) To UBound(arr)
        arr(i, 1) = i
        l$ = Mid(txt, i, 1)
        arr(i, 2) = l$
        arr(i, 3) = Asc(l$)
        arr(i, 4) = AscW(l$)
    Next i
    ' arr(1, 1) = "#": arr(1, 2) = "Char": arr(1, 3) = "Asc": arr(1, 4) = "AscW"
    ParseString = arr
End Function

Sub ParseActiveCell()
    On Error Resume Next
    If ActiveSheet Is Nothing Then Exit Sub
    With New Fchar
        .Show
        .TextBox_txt = ActiveCell.Value
    End With
End Sub

Attribute VB_Name = "F_Progress"
Attribute VB_Base = "0{C0028012-1FB2-468D-800A-CB779C14B204}{5B3626FF-D120-4DC8-B066-A094FDA70A17}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
'---------------------------------------------------------------------------------------
' Module        : F_Progress                    Version: 2
' Author        : Igor Vakhnenko                   Date: 21.07.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Public Indicator As ProgressIndicator, ButtonMacro$
Dim MSG_StopMacro$, MSG_StopMacroTitle$

Private Sub UserForm_Initialize()
    On Error Resume Next
    MSG_StopMacro$ = Run(TWN & "tt", "PI_MSG_StopMacro")
    If MSG_StopMacro$ = "" Then MSG_StopMacro$ = "Do you really want to stop the macro?"
    MSG_StopMacroTitle$ = Run(TWN & "tt", "PI_MSG_StopMacroTitle")
    If MSG_StopMacroTitle$ = "" Then MSG_StopMacroTitle$ = "Processing is not complete yet"
    Err.Clear
End Sub

Private Sub CommandButton_RunMacro_Click()
    On Error Resume Next
    If Len(ButtonMacro$) Then Run TWN & ButtonMacro$
End Sub

Private Sub CommandButton_stop_Click()
    On Error Resume Next
    If StopMacro Then        ' macro finished
        If IsObject(F_Greeting) Then
            If F_Greeting.Visible Then
                Unload Me
                Exit Sub
            End If
        Else
            End
        End If
    Else        ' macro is running
        If MsgBox(MSG_StopMacro$, vbQuestion + vbDefaultButton2 + vbYesNo, MSG_StopMacroTitle$) = vbYes Then
            StopMacro = True
        End If
    End If
End Sub

Private Sub SpinButton_log_Change()
    On Error Resume Next
    n = Me.SpinButton_log.Value
    Me.Height = IIf(n = 0, 82, 92 + n * 40)
    Me.TextBox_Log.Height = 40 * n
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    On Error Resume Next
    If Not Indicator Is Nothing Then Indicator.QueryClose
End Sub

Attribute VB_Name = "ProgressIndicator"
Attribute VB_Base = "0{FCFB3D2A-A0FA-1068-A738-08002B3371B5}"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Attribute VB_TemplateDerived = False
Attribute VB_Customizable = False
'---------------------------------------------------------------------------------------
' Module        : ProgressIndicator                    Version: 2
' Author        : Igor Vakhnenko                   Date: 21.07.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Explicit

Public FP As New F_Progress
Public SubActionIndex As Single, SubActionsCount As Single, SubActionsStep&
Private FPVisible As Boolean, FPStartTime As Date, Position&
Private PrS&, PrE&, Percent As Double, LogString$

Public Parent As ProgressIndicator, Children As New Collection
Public ShowPercents As Boolean, ShowTime As Boolean, ShowTimeInLog As Boolean

Dim MSG_EstimatedTime$, BTN_Stop$

Function AddChildIndicator(ByVal Caption$, Optional ByVal FPPosition& = 1) As ProgressIndicator
    On Error Resume Next
    Set AddChildIndicator = New ProgressIndicator
    Set AddChildIndicator.Parent = Me
    AddChildIndicator.Show Caption, FPPosition
    Children.Add AddChildIndicator
End Function

Private Sub Class_Initialize()
    Set FP = New F_Progress: ShowPercents = True: FPVisible = True
    PrS = 0: PrE = 100: Set_ProgressBar 0: FP.PrBar.Caption = ""
    FPStartTime = Now: ShowTime = True: ShowPercents = True: SubActionsStep = 1
    Set FP.Indicator = Me

    On Error Resume Next
    MSG_EstimatedTime$ = Run(TWN & "tt", "PI_MSG_EstimatedTime")
    If MSG_EstimatedTime$ = "" Then MSG_EstimatedTime$ = "Estimated time left"
    BTN_Stop$ = Run(TWN & "tt", "PI_BTN_Cancel")
    If BTN_Stop$ = "" Then BTN_Stop$ = "Cancel"
    FP.CommandButton_stop.Caption = BTN_Stop$
    Err.Clear
End Sub

Sub Show(ByVal Caption$, Optional ByVal FPPosition& = 0, Optional LogSize& = 0)
    On Error Resume Next
    SetProgressFormCaption Caption: On Error Resume Next:
    FP.PrBar.Width = ProgressBar_Default_Width
    Position = FPPosition
    FP.Tag = Caption: FP.Show:
    If Position <> 0 Then Move Position
    FP.Repaint: DoEvents
    FP.SpinButton_log.Value = LogSize
    FP.SpinButton_log.Visible = LogSize > 0
    SetLogSize LogSize
End Sub

Sub Hide()
    Unload FP
    FPVisible = False
End Sub

Sub Repaint()
    FP.Repaint: DoEvents
    If Application.ScreenUpdating = False Then Application.ScreenUpdating = True: Application.ScreenUpdating = False
End Sub

Sub Move(ByVal Position&)
    If Abs(Position) > 3 Then Exit Sub
    If Not Me.Parent Is Nothing Then
        FP.Top = Me.Parent.FP.Top + Me.Parent.FP.Height + 10 + (FP.Height + 3) * (Position - 1)
    Else
        FP.Top = FP.Top + (FP.Height + 3) * Position
    End If
End Sub

Public Property Get Visible(): Visible = FPVisible: End Property

Public Property Let Line1(ByVal NewValue$): FP.L1.Caption = ProcessLabel(NewValue): Me.FP.Repaint: DoEvents: End Property
Public Property Let Line2(ByVal NewValue$): FP.L2.Caption = ProcessLabel(NewValue): Me.FP.Repaint: DoEvents: End Property
Public Property Let Line3(ByVal NewValue$): FP.L3.Caption = ProcessLabel(NewValue): Me.FP.Repaint: DoEvents: End Property
Public Property Let Caption(ByVal NewValue$): SetProgressFormCaption NewValue: End Property

Private Sub SetProgressFormCaption(Optional ByVal Caption$ = "")
    Dim txt$, dt$
    If Len(Caption) > 0 Then FP.Tag = Caption
    txt = Trim(FP.Tag): If ShowPercents Then txt = Fix(Percent) & " %   " & txt
    dt = Format(Now - FPStartTime, "HH:NN:SS")
    If ShowTime Then txt = "( " & dt & " )    " & txt
    FP.Caption = txt
End Sub

Private Function TimeToFinish$()
    On Error Resume Next: Dim dt As Single
    If Percent < 15 Then Exit Function
    dt = (Now - FPStartTime) * (100 - Percent) / Percent
    TimeToFinish$ = Format(dt, "H:NN:SS")
    TimeToFinish$ = MSG_EstimatedTime$ & ": " & TimeToFinish$
End Function

Sub SetFocus()
    FP.Show 0: If Position <> 0 Then Move Position
End Sub

Private Sub UpdateLabels(Optional ByVal L1_txt$, Optional ByVal L2_txt$, Optional ByVal L3_txt$)
    If Len(L1_txt$) > 0 Then FP.L1.Caption = ProcessLabel(L1_txt$)
    If Len(L1_txt$) + Len(L2_txt$) > 0 Then FP.L2.Caption = ProcessLabel(L2_txt$)
    If Len(L1_txt$) + Len(L2_txt$) + Len(L3_txt$) > 0 Then FP.L3.Caption = ProcessLabel(L3_txt$)
End Sub

Private Function ProcessLabel$(ByVal txt$)
    If txt Like "*$index*" Then txt = Replace(txt, "$index", SubActionIndex)
    If txt Like "*$count*" Then txt = Replace(txt, "$count", SubActionsCount)
    If txt Like "*$time*" Then txt = Replace(txt, "$time", TimeToFinish)
    ProcessLabel = txt
End Function

Sub SubAction(Optional ByVal L1_txt$ = "", Optional ByVal L2_txt$ = "", Optional ByVal L3_txt$ = "")
    On Error Resume Next
    If SubActionsCount = 0 Then SubActionsCount = 1
    SubActionIndex = SubActionIndex + 1 * SubActionsStep
    If SubActionIndex > SubActionsCount Then SubActionIndex = SubActionsCount
    Percent = PrS + (PrE - PrS) * ((SubActionIndex - 1) / SubActionsCount)
    UpdateLabels L1_txt$, L2_txt$, L3_txt$
    Set_ProgressBar Percent: DoEvents
End Sub

Sub StartNewAction(Optional ByVal Pr_Start& = 0, Optional ByVal Pr_End& = 100, _
                   Optional ByVal L1_txt$ = "", Optional ByVal L2_txt$ = " ", Optional ByVal L3_txt$ = " ", _
                   Optional ByVal Actions_Count& = 0)
    On Error Resume Next
    PrS = Pr_Start: PrE = Pr_End: SubActionIndex = 0: SubActionsCount = Actions_Count
    UpdateLabels L1_txt$, L2_txt$, L3_txt$
    Set_ProgressBar PrS
End Sub

Sub UpdateFromChild(ByVal ChildPercent As Double)
    If SubActionsCount = 0 Then
        Percent = PrS + (PrE - PrS) * (ChildPercent / 100)
    Else
        Percent = PrS + (PrE - PrS) / SubActionsCount * (SubActionIndex - 1) + (PrE - PrS) / SubActionsCount * (ChildPercent / 100)
    End If
    Set_ProgressBar Percent
End Sub

Private Sub Set_ProgressBar(ByVal NewPercent As Double)
    On Error Resume Next: Percent = NewPercent
    If NewPercent > 100 Then Percent = 100
    If NewPercent < 0 Then Percent = 0
    FP.PrBar.Width = Int(Percent * ProgressBar_Default_Width / 100)
    SetProgressFormCaption
    FP.Repaint
    If Not Parent Is Nothing Then Parent.UpdateFromChild Percent
End Sub

Private Sub Class_Terminate()
    On Error Resume Next
    Unload FP: FPVisible = False
    Set FP = Nothing: Set Children = Nothing: Set Parent = Nothing
End Sub

Private Function ProgressBar_Default_Width() As Double
    ProgressBar_Default_Width = FP.Width - 18
End Function

Function CancelButton() As MSForms.CommandButton
    Set CancelButton = FP.CommandButton_stop
End Function

Sub SetLogSize(ByVal n&)
    On Error Resume Next
    If n < 0 Then n = 0
    If n > 5 Then n = 5
    FP.SpinButton_log.Value = n
    FP.SpinButton_log.Visible = n > 0
End Sub

Sub Log(ByVal txt$, Optional ByVal MaxLen& = 0)
    On Error Resume Next: Dim currtime$, newtext$
    If ShowTimeInLog Then currtime$ = Time & vbTab
    LogString = LogString & vbNewLine & currtime$ & txt
    newtext$ = Mid(LogString, 3): If MaxLen& Then newtext$ = Right(newtext$, MaxLen&)
    FP.TextBox_Log.Text = newtext$
    If FP.SpinButton_log.Value = 0 Then FP.SpinButton_log.Value = 2: FP.SpinButton_log.Visible = True
    FP.CommandButton_stop.SetFocus: FP.TextBox_Log.SetFocus
End Sub

Sub ClearLog()
    LogString = "": FP.TextBox_Log.Text = ""
End Sub

Sub ShowLog()
    On Error Resume Next: Dim filename$
    filename$ = Environ("TEMP") & "\macro_log.txt"
    With CreateObject("scripting.filesystemobject").CreateTextFile(filename, True)
        .Write Mid(LogString, 3): .Close
    End With
    FollowHyperlink """" & filename$ & """"
End Sub

Sub AddButton(ByVal Caption$, ByVal Macro$)
    Const dd& = 18
    If FP.SpinButton_log = 0 Then FP.SpinButton_log = 1
    With Me.FP.CommandButton_RunMacro
        .Caption = Caption$
        .Visible = True
        .Top = FP.Height - .Height - dd - 20
        .Left = FP.Width - .Width - dd - 15
    End With
    FP.ButtonMacro = Macro$
End Sub

Function MacroButton() As MSForms.CommandButton
    Set MacroButton = FP.CommandButton_RunMacro
End Function

Sub QueryClose()
    On Error Resume Next
    Dim pi As ProgressIndicator
    For Each pi In Children
        pi.QueryClose
        pi.Hide
    Next pi
End Sub



Attribute VB_Name = "FWF"
'---------------------------------------------------------------------------------------
' Module        : FWF                    Version: 2
' Author        : Igor Vakhnenko                   Date: 25.12.2015
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/        info@excelvba.ru      Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Private Module: Option Compare Text: Option Explicit

#If VBA7 Then        '  Office 2010-2013
    Declare PtrSafe Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
            (ByVal pCaller As LongPtr, ByVal szURL As String, ByVal szFileName As String, _
             ByVal dwReserved As LongPtr, ByVal lpfnCB As LongPtr) As LongPtr
#Else        '  Office 2003-2007
    Declare Function URLDownloadToFile Lib "urlmon" Alias "URLDownloadToFileA" _
                                       (ByVal pCaller As Long, ByVal szURL As String, ByVal szFileName As String, _
                                        ByVal dwReserved As Long, ByVal lpfnCB As Long) As Long
#End If

Function DownLoadFileFromURL(ByVal URL$, ByVal LocalPath$, Optional ByVal DisableCache As Boolean = False) As Boolean
    On Error Resume Next: Dim shortFilename$
    If (LocalPath$ = "") Or (URL$ = "") Then Exit Function
    If Not LocalPath$ Like "*\*" Then LocalPath$ = Environ("TEMP") & "\" & LocalPath$
    Kill LocalPath$
    shortFilename$ = Mid(LocalPath$, InStrRev(LocalPath$, "\") + 1)
    If shortFilename$ <> Replace_symbols(shortFilename$) Then
        Debug.Print "Wrong symbols in filename: " & shortFilename$
        Exit Function
    End If
    If DisableCache Then Randomize: URL$ = URL$ & "?HID=" & HID & "&rnd=" & Left(Rnd(Now) * 1E+15, 10)
    DownLoadFileFromURL = URLDownloadToFile(0, URL$, LocalPath$, 0, 0) = 0
End Function

Function GetFileExtension(ByVal filename$) As String
    On Error Resume Next: filename$ = Replace(filename$, "/", "\")
    filename$ = Split(filename$, "\")(UBound(Split(filename$, "\")))
    If filename$ Like "*.*" Then GetFileExtension = Split(filename$, ".")(UBound(Split(filename$, ".")))
End Function


Function GetFolderPath(Optional ByVal DialogTitle$, Optional ByVal InitialPath$ = "c:\") As String
    On Error Resume Next
    If DialogTitle$ = "" Then DialogTitle$ = tt("SelectFolderDialogCaption")
    Dim PS$: PS = Application.PathSeparator
    With Application.FileDialog(msoFileDialogFolderPicker)
        If Not Right$(InitialPath, 1) = PS Then InitialPath = InitialPath & PS
        .ButtonName = tt("SelectButtonCaption")
        .Title = DialogTitle$: .InitialFileName = InitialPath$
        If .Show <> -1 Then Exit Function
        GetFolderPath = .SelectedItems(1)
        If Not Right$(GetFolderPath, 1) = PS Then GetFolderPath = GetFolderPath & PS
    End With
End Function

Function GetFilePath(Optional ByVal DialogTitle$, Optional ByVal InitialPath$ = "c:\", _
                     Optional ByVal FilterDescription$ = "Excel files", Optional ByVal FilterExtension$ = "*.xls*") As String
    On Error Resume Next
    If DialogTitle$ = "" Then DialogTitle$ = tt("SelectFileDialogCaption")
    With Application.FileDialog(msoFileDialogOpen)
        .Title = DialogTitle$: .InitialFileName = InitialPath$
        .Filters.Clear: .Filters.Add FilterDescription, FilterExtension
        If .Show <> -1 Then Exit Function
        GetFilePath = .SelectedItems(1)
    End With
End Function

Function GetFilenamesCollection(Optional ByVal DialogTitle$, Optional ByVal InitialPath$ = "c:\") As FileDialogSelectedItems
    On Error Resume Next
    If DialogTitle$ = "" Then DialogTitle$ = tt("SelectFilesDialogCaption")
    With Application.FileDialog(3)        ' msoFileDialogFilePicker
        .Title = DialogTitle$: .InitialFileName = InitialPath$
        If .Show <> -1 Then Exit Function
        Set GetFilenamesCollection = .SelectedItems
    End With
End Function

Function FilenamesCollection(ByVal FolderPath$, Optional ByVal mask$ = "*", Optional ByVal SearchDeep& = 999) As Collection
    On Error Resume Next: Dim FSO As Object: Set FilenamesCollection = New Collection
    Set FSO = CreateObject("Scripting.FileSystemObject")
    GetAllFileNamesUsingFSO FolderPath, mask, FSO, FilenamesCollection, SearchDeep
    Set FSO = Nothing        ': Application.StatusBar = False
End Function

Function GetAllFileNamesUsingFSO(ByVal FolderPath$, ByVal mask$, ByRef FSO, ByRef FileNamesColl As Collection, ByVal SearchDeep&)
    On Error Resume Next: Dim oCurrFolder As Object, oFile As Object, oSubFolder As Object
    Const ExcludeFiles$ = "Thumbs.db, desktop.ini"

    Set oCurrFolder = FSO.GetFolder(FolderPath)
    If Not oCurrFolder Is Nothing Then
        ' Application.StatusBar = "Searching in: " & FolderPath$
        For Each oFile In oCurrFolder.files
            If oFile.Name Like "*" & mask Then
                If InStr(1, ExcludeFiles$, oFile.Name, vbTextCompare) = 0 Then
                    If InStr(1, oFile.Name, "~$", vbTextCompare) <> 1 Then FileNamesColl.Add oFile.Path
                End If
            End If
        Next
        SearchDeep& = SearchDeep& - 1
        If SearchDeep& Then
            For Each oSubFolder In oCurrFolder.SubFolders
                GetAllFileNamesUsingFSO oSubFolder.Path, mask, FSO, FileNamesColl, SearchDeep&
            Next
        End If
        Set oFile = Nothing: Set oSubFolder = Nothing: Set oCurrFolder = Nothing
    End If
End Function

Function SubFoldersCollection(ByVal FolderPath$, Optional ByVal mask$ = "*") As Collection
    On Error Resume Next: Dim FSO As Object, oSubFolder As Object

    Set SubFoldersCollection = New Collection
    Set FSO = CreateObject("Scripting.FileSystemObject")
    If Right(FolderPath$, 1) <> "\" Then FolderPath$ = FolderPath$ & "\"

    For Each oSubFolder In FSO.GetFolder(FolderPath$).SubFolders
        If oSubFolder.Path Like FolderPath$ & mask$ Then SubFoldersCollection.Add oSubFolder.Path & "\"
    Next oSubFolder
    Set oSubFolder = Nothing: Set FSO = Nothing
End Function


Function ReadTXTfile(ByVal filename$) As String
    On Error Resume Next: Dim FSO As Object, ts As Object
    Set FSO = CreateObject("scripting.filesystemobject")
    Set ts = FSO.OpenTextFile(filename$, 1, True): ReadTXTfile = ts.ReadAll: ts.Close
    Set ts = Nothing: Set FSO = Nothing
End Function

Function SaveTXTfile(ByVal filename$, ByVal txt$) As Boolean
    On Error Resume Next: Err.Clear: Dim FSO As Object, ts As Object
    Set FSO = CreateObject("scripting.filesystemobject")
    Set ts = FSO.CreateTextFile(filename$, True)
    ts.Write txt: ts.Close
    SaveTXTfile = Err = 0: Set ts = Nothing: Set FSO = Nothing
End Function

Function AddIntoTXTfile(ByVal filename$, ByVal txt$) As Boolean
    On Error Resume Next: Err.Clear: Dim FSO As Object, ts As Object
    Set FSO = CreateObject("scripting.filesystemobject")
    Set ts = FSO.OpenTextFile(filename$, 8, True): ts.Write txt: ts.Close
    AddIntoTXTfile = Err = 0: Set ts = Nothing: Set FSO = Nothing
End Function


Function Replace_symbols(ByVal txt$, Optional ReplaceWith$ = "_", Optional ByVal AllowPathSeparator As Boolean = False) As String
    On Error Resume Next: Dim i&, CharsList$: CharsList$ = "/\:?*|""<>"
    If AllowPathSeparator Then CharsList$ = Replace(CharsList$, Application.PathSeparator, "")
    For i& = 1 To Len(CharsList$)
        txt$ = Replace(txt$, Mid(CharsList$, i&, 1), ReplaceWith$)
    Next
    Replace_symbols = txt$
End Function

Sub OpenFolder(ByVal FolderPath$)        ' to open FolderPath$ in Windows Explorer
    On Error Resume Next: If FolderPath$ = "" Then Exit Sub
    If FolderExists(FolderPath$) Then
…