Malicious Office (OLE) — malware analysis report

Static analysis result for SHA-256 d8e04c4ccb5e33aa…

MALICIOUS

Office (OLE)

1.41 MB Created: 2021-08-24 02:37:51 Authoring application: AddinUpdater First seen: 2021-11-07
MD5: 28e8a8fedf41cc0b572c46f19c392272 SHA-1: 1c0c34101a5d2358b585e2dbca6f9a4d61691ffe SHA-256: d8e04c4ccb5e33aaa565b1f22ad81eb0f410b1d2b76e15d982eabba7a2dc7fcc
878 Risk Score

Malware Insights

MITRE ATT&CK
T1059.005 Visual Basic T1105 Ingress Tool Transfer T1204.002 Malicious File T1059.003 Windows Command Shell

The sample is a malicious Office document containing obfuscated VBA macros. These macros utilize WScript.Shell and URLDownloadToFile to download and execute a second-stage payload from a remote URL, likely disguised as an update for an Excel add-in. The presence of cmd.exe and CreateProcess API calls further indicates execution of downloaded content. The ClamAV detection 'Doc.Dropper.Valyria-6791994-0' aligns with this dropper behavior.

Heuristics 21

  • ClamAV: Doc.Dropper.Valyria-6791994-0 critical CLAMAV_DETECTION
    ClamAV detected this file as malware: Doc.Dropper.Valyria-6791994-0
  • Reference to URLDownloadToFile API critical SC_STR_URLDOWNLOAD
    Reference to URLDownloadToFile API
  • VBA macros detected medium 12 related findings OLE_VBA_MACROS
    Document contains VBA macro code
  • Potential Shell call in VBA critical OLE_VBA_SHELL
    Potential Shell call in VBA
    Matched line in script
                    '                If Err = 0 And res$ = code$ Then
                    Shell "Cmd.exe /c echo " & Chr(7), vbHide
                    '                Else
  • WScript.Shell usage critical OLE_VBA_WSCRIPT
    WScript.Shell usage
    Matched line in script
        On Error Resume Next: Err.Clear
        TheBAT_PATH = CreateObject("WScript.Shell").RegRead("HKEY_CURRENT_USER\Software\RIT\The Bat!\EXE path")
  • 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, _
  • Obfuscated VBA Shell command with URL critical OLE_VBA_OBFUSCATED_SHELL_URL
    VBA macro invokes Shell with command text assembled through decoder or string-manipulation functions and includes a URL. This is a high-confidence downloader/dropper pattern, stronger than Shell or URL evidence on their own.
    Matched line in script
                    '                If Err = 0 And res$ = code$ Then
                    Shell "Cmd.exe /c echo " & Chr(7), vbHide
                    '                Else
  • 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
                    '                Do
                    '                    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
                    '                        .SetText code$
  • 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
            Set WA = GetObject(, "Word.Application")
            If WA Is Nothing Then Set WA = CreateObject("Word.Application") Else WordAlreadyOpen = True
            If WA Is Nothing Then
  • GetObject call high OLE_VBA_GETOBJ
    GetObject call
    Matched line in script
                    '                Do
                    '                    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
                    '                        .SetText code$
  • cmd.exe reference in VBA high OLE_VBA_CMD
    cmd.exe reference in VBA
    Matched line in script
                    '                If Err = 0 And res$ = code$ Then
                    Shell "Cmd.exe /c echo " & Chr(7), vbHide
                    '                Else
  • 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
                    '                Do
                    '                    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
                    '                        .SetText code$
  • 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
  • Suspicious cmd.exe invocation with execution flag high SC_STR_CMD
    Suspicious cmd.exe invocation with execution flag
  • Reference to Windows Script Host high SC_STR_WSCRIPT
    Reference to Windows Script Host
  • LOLBin token sequence in document text high SE_LOLBIN_RUN_COMMAND
    Extracted document text contains a Windows script/execution tool name (PowerShell, mshta, cmd, rundll32, regsvr32, …) within 220 characters of a dangerous flag, command verb, or URL. This is a visible 'run this' instruction in HTML/PDF/RTF lure bodies, or — in macro-laden Office files — the macro's own string-pool entries appearing adjacent in extracted text.
  • Suspicious extracted artifact high 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
    • http://ExcelVBA.ru/paymentsReferenced by macro
    • http://excelvba.ru/code/translitReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailingReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/insert/word_docsReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailing/TheBATReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailing/setupReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailing/UnisenderReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailing/html_mailReferenced by macro
    • https://excelvba.ru/programmes/FillDocuments/manuals/mailing/OutlookReferenced by macro
    • http://ExcelVBA.ru/Referenced by macro
    • http://excelvba.ru/resources/FillDocuments/Referenced by macro
    • https://ExcelVBAReferenced by macro
    • http://ExcelVBA.ru/php2/updates.phpReferenced by macro
    • http://Excel-Automation.com/Referenced by macro
    • http://www.wordarticles.com/Shorts/RibbonVBA/RibbonVBADemo.phpReferenced by macro
    • https://api.unisepoReferenced by macro
    • http://vbaccelerator.comReferenced by macro
    • http://vbaccelerator.com/Referenced by macro
    • http://excelvba.ru/Referenced by macro
    • http://www.unisender.com/?a=FillDocumentsReferenced by macro
    • https://api.unisender.com/ru/api/sendEmailReferenced by macro
    • https://www.unisender.com/ru/support/integration/apiReferenced by macro
    • https://support.unisender.com/index.php?/Knowledgebase/Article/View/69/0/sendemail---uproshhjonnja-otprvk-individulnykh-email-soobshhenijj&_ga=1.127073677.1479843546.1474516806Referenced by macro
    • http://translate.google.com/translate?sl=ru&tl=Referenced by macro
    • https://api.unisender.com/ru/api/getListsReferenced by macro
    • http://code.google.com/p/vba-json/Referenced by macro
    • http://en.wikipedia.org/wiki/Percent-encodingReferenced by macro
    • https://api.unisender.com/ru/api/createListReferenced 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) 1377792 bytes
SHA-256: be77f58988872d9cff5a4fc4d777041882a97d3e8a285f6ad3a455fef81e1947
Detection
ClamAV: No threats found
Obfuscation or payload: likely
Carved artifact contains 2 eval/decoder/string-building token(s). 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
'---------------------------------------------------------------------------------------
' VBA Document      : ThisWB
' Author        : EducatedFool                     Date: 18.01.2013
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/         Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Public WithEvents app As Application
Attribute app.VB_VarHelpID = -1

Private Sub app_SheetBeforeDoubleClick(ByVal sh As Object, ByVal Target As Range, Cancel As Boolean)
    On Error Resume Next: Err.Clear
    If sh.Parent Is ThisWorkbook Then Exit Sub
    
    If Not SETT.GetBoolean("CheckBox_DisableDoubleClickOnHeader") Then
        If Target.Row = HEADER_ROW And Target.Cells.Count = 1 Then
            If Len(Trim(Target)) > 0 Then
                Cancel = True
                If Target <> Trim(Target) Then Target = Trim(Target)
                TriesCount& = 0: code$ = "{" & Trim(Target) & "}"
                
                SetClipboard code$
                Err.Clear
                '                Do
                '                    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
                '                        .SetText code$
                '                        .PutInClipboard
                '
                '                        .GetFromClipboard
                '                        res$ = .GetText
                '                        TriesCount& = TriesCount& + 1
                '                    End With
                '                Loop While res$ <> code$ And TriesCount& < 10
                
                '                If Err = 0 And res$ = code$ Then
                Shell "Cmd.exe /c echo " & Chr(7), vbHide
                '                Else
                '                    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
                '                        .SetText ""
                '                        .PutInClipboard
                '                    End With
                '                End If
            End If
        End If
    End If
    
    If Target.Cells.Count = 1 Then
        If HasLinkToObject(Target.Cells(1).Text) Then Cancel = True: CtrlShiftT
    End If
End Sub

Private Sub Workbook_BeforeClose(Cancel As Boolean)
    On Error Resume Next
    DeleteProgramCommandBar
    Disable_HotKeys
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
    
    Enable_HotKeys
End Sub

Attribute VB_Name = "sh_wdf"
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


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
' Автор     : EducatedFool  (Игорь)                    Дата: 26.03.2012
' Разработка макросов для Excel, Word, CorelDRAW. Быстро, профессионально, недорого.
' https://ExcelVBA.ru/         Skype: ExcelVBA.ru
' Реквизиты для оплаты работы: http://ExcelVBA.ru/payments
'---------------------------------------------------------------------------------------
Option Compare Text
Option Private Module

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 TemplatesInfo(ByVal files As Collection)
    For Each item In files
        TemplatesInfo = TemplatesInfo & ";" & TemplateType(item)
    Next
    TemplatesInfo = Left(Mid(TemplatesInfo, 2), 100)
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 FindAll(SearchRange As Range, _
                 FindWhat As Variant, _
                 Optional LookIn As XlFindLookIn = xlValues, _
                 Optional LookAt As XlLookAt = xlWhole, _
                 Optional SearchOrder As XlSearchOrder = xlByRows, _
                 Optional MatchCase As Boolean = False, _
                 Optional BeginsWith As String = vbNullString, _
                 Optional EndsWith As String = vbNullString, _
                 Optional BeginEndCompare As VbCompareMethod = vbTextCompare) As Range

    Dim FoundCell As Range, FirstFound As Range, LastCell As Range, rngResultRange As Range
    Dim XLookAt As XlLookAt, Include As Boolean, CompMode As VbCompareMethod
    Dim Area As Range, MaxRow As Long, MaxCol As Long, BeginB As Boolean, EndB As Boolean

    CompMode = BeginEndCompare
    XLookAt = LookAt: If BeginsWith <> vbNullString Or EndsWith <> vbNullString Then XLookAt = xlPart

   For Each Area In SearchRange.Areas
        With Area
            If .Cells(.Cells.Count).Row > MaxRow Then MaxRow = .Cells(.Cells.Count).Row
            If .Cells(.Cells.Count).Column > MaxCol Then MaxCol = .Cells(.Cells.Count).Column
        End With
    Next Area
    Set LastCell = SearchRange.Worksheet.Cells(MaxRow, MaxCol)
    Set FoundCell = SearchRange.Find(what:=FindWhat, after:=LastCell, _
                                     LookIn:=LookIn, LookAt:=XLookAt, _
                                     SearchOrder:=SearchOrder, MatchCase:=MatchCase)

    If Not FoundCell Is Nothing Then
        Set FirstFound = FoundCell
        Do Until False    ' Loop forever. We'll "Exit Do" when necessary.
           Include = False
            If BeginsWith = vbNullString And EndsWith = vbNullString Then
                Include = True
            Else
                If BeginsWith <> vbNullString Then
                    If StrComp(Left(FoundCell.Text, Len(BeginsWith)), _
                               BeginsWith, BeginEndCompare) = 0 Then Include = True
                End If
                If EndsWith <> vbNullString Then
                    If StrComp(Right(FoundCell.Text, Len(EndsWith)), _
                               EndsWith, BeginEndCompare) = 0 Then Include = True
                End If
            End If
            If Include = True Then
                If rngResultRange Is Nothing Then
                    Set rngResultRange = FoundCell
                Else
                    Set rngResultRange = Application.Union(rngResultRange, FoundCell)
                End If
            End If
            Set FoundCell = SearchRange.FindNext(after:=FoundCell)
            If (FoundCell Is Nothing) Then Exit Do
            If (FoundCell.Address = FirstFound.Address) Then Exit Do
        Loop
    End If
    Set FindAll = rngResultRange
End Function


Attribute VB_Name = "mod_Main"
'---------------------------------------------------------------------------------------
' Module        : mod_Main                    Version:
' Author        : Igor Vakhnenko                   Date: 22.09.2016
' 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$ = "FillDocuments", PROJECT_YEAR& = 2012

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, NeedToSelectParser As Boolean
    Application.ScreenUpdating = False
    If Not RefreshOnly Then Run DeleteOldCommandBar
    Set AddinMenu = GetCommandBar(PROJECT_NAME, True Or RefreshOnly)

    ' menu begin
    Set MainMacroButton = Add_Control(AddinMenu, ct_BUTTON, 593, "CreateAllDocuments", tt("MENU_MainButton"), msoButtonIconAndCaption, True)

    If SETT.GetBoolean("CheckBox_ShowAdditionalMenu") Then
        Set ExtendedMenu = Add_Control(AddinMenu, ct_POPUP, 0, "", "  " & tt("MENU_Extra"))
        Add_Control ExtendedMenu, ct_BUTTON, 385, "UpdateUDFs", "Восстановить формулы", msoButtonIconAndCaption, True        ' 202
        Add_Control ExtendedMenu, ct_BUTTON, 142, "CtrlShiftT", "Вставить ссылку на таблицу... (Ctrl + Shift + T)", msoButtonIconAndCaption, True
        Add_Control ExtendedMenu, ct_BUTTON, 218, "CtrlShiftI", "Вставить ссылку на изображение... (Ctrl + Shift + I)", msoButtonIconAndCaption, False        ' 508
        Add_Control ExtendedMenu, ct_BUTTON, 0, "AddImagesFilenamesValidationList_IntoSelectedRange", _
                    "Вставить список имён файлов картинок в выделенный диапазон, в виде выпадающего списка", msoButtonIconAndCaption, False        ' 508
        Add_Control ExtendedMenu, ct_BUTTON, 0, "AddAbsentFieldCodes", "Импортировать коды полей из шаблонов в заголовок таблицы ...", msoButtonIconAndCaption, True
        Add_Control ExtendedMenu, ct_BUTTON, 0, "ShowUsageExample", tt("ShowUsageExample"), msoButtonIconAndCaption, True

    End If

    '    If Not SETT.GetBoolean("HideSettingsButton") Then Add_Control AddinMenu, ct_BUTTON, 548, "ShowSettingsPage", "Настройки", msoButtonIconAndCaption, True
    '    If Not SETT.GetBoolean("HideAboutButton") Then Add_Control AddinMenu, ct_BUTTON, 487, "ShowMainForm", "О программе ...", msoButtonIconAndCaption, True

    AddSettingsSwitcher AddinMenu, ct_DROPDOWN, , "CreateAllDocuments", MainMacroButton
    ' menu end
    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

    Set ThisWorkbook.app = Application
    Application.ScreenUpdating = True
End Sub

Sub SaveDefaultSettings()
    On Error Resume Next
    With SETT
        .LoadAllSettings

        .AddDefaultValue "CheckBox_InsertIntoFields", False
        .AddDefaultValue "CheckBox_TemplatesForm_SortByName", True
        .AddDefaultValue "MultiPage_Mode", 0        ' режим «обычная (горизонтальная) таблица»
        .AddDefaultValue "CheckBox_ReplaceExistingFieldCodesOnly", True

        .AddDefaultValue "TextBox_CombineXLS_filename", "Сводный файл.xls", , True
        .AddDefaultValue "TextBox_SendInterval_Min", 0
        .AddDefaultValue "TextBox_SendInterval_Max", 0
        .AddDefaultValue "TextBox_HyperlinkText", "открыть файл", , True

        .AddDefaultValue "ComboBox_FirstRow", 1
        .AddDefaultValue "ComboBox_BaseColumn", 2, True
        .AddDefaultValue "ComboBox_TemplatesFilter_Column", 5, True
        .AddDefaultValue "ComboBox_LineFeed", Chr(11), , True
        .AddDefaultValue "TextBox_OutputMask", "{%str%} - {%filename%}.{%ext%}", , True

        .AddDefaultValue "ComboBox_FirstColumn", 1, True
        .AddDefaultValue "ComboBox_BaseRow", 2
        .AddDefaultValue "CheckBox_UseAllColumns", True

        .AddDefaultValue "TextBox_AttachCreatedFilesMask", "*", , True
        .AddDefaultValue "TextBox_AttachStaticFilesMask", "*", , True

        .AddDefaultValue "CheckBox_ShowFolderWhenDone", True
        .AddDefaultValue "CheckBox_TemplatesFilter_Enabled", False
        .AddDefaultValue "ComboBox_SendMark_Column", 10, True
        .AddDefaultValue "ComboBox_SendTime_Column", 11, True
        .AddDefaultValue "ComboBox_Multirow_GroupColumn", 1, True
        
        .AddDefaultValue "ComboBox_PasteWordDoc_Format", 0
        .AddDefaultValue "CheckBox_ToDelete_Enabled", True
    End With
End Sub

Sub SettingSetChanged()
    RunWithDelay "CreateProgramCommandBar", 0.5
End Sub

Sub UpdateAddinToolbar()
    RunWithDelay "CreateProgramCommandBar", 0.6
End Sub

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

Function GetFile_MainPicture() As String
    ' создаёт во временной папке файл, возвращает путь к созданному файлу
    On Error Resume Next:
    Dim F_TXT$, buf$, tmp_file$, i&, res$, ff&: Const BufLen& = 5000
    F_TXT$ = F_TXT$ & "FFD8FFE000104A46494600010101012C012C0000FFDB0043000302020302020303030304030304050805050404050A070706080C0A0C0C0B0A0B0B0D0E12100D0E110E0B0B1016101113141515150C0F171816141812141514FFDB00430103040405040509050509140D0B0D1414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414FFC00011080030003003012200021101031101FFC400190000030101010000000000000000000000060708040905FFC40034100001030303020305060700000000000001020304050611000712213108132223324151611415717281911617245382A1A2FFC4001801000301010000000000000000000000000405060201FFC4002811000102060104000700000000000000000102110003040521314112132361323342517191E1FFDA000C03010002110311003F00EA9E82EEFDEAB06C0756CDC579512912539CC69339B4BDD3BFB3CF23FB6A6F455EA7E212BB5F37DEE1B9615AB02E155B4C59D4575516548925452D2653E472579B8C84A7D2A00E0A483A6BDA9B0BB55B774D4AE15914286FB5E8765CE6448712B1DCF9AF152867BF7F96B8F968D360AA31CAF1CDB2AC3CB618BC155092919F260D2E63C55F810D71FF007A67"
    F_TXT$ = F_TXT$ & "EDB6E7DB5BB96CA2BF6AD49352A71714CA95C14DADA713EF2168500A4A8641C11D8823A10740957A641AA4528A30A83C8EBC5119A5AE3FE8A5613FB13A9AAAF7F567C20EE755EE216FCC5D06EB88E34F53494F13554A14A8EEA42547A38414AB1D7AA8F5200D6886C98C020EA191E2777B6E4977FA6C0B1EB8EDBED52228A85C75A88DA5C758E63D8464641C2943D671D4829EA0056883C226F45D5B833EEFB5AE99AC57DFB70455355E8EC0655212F254783A84FA4389E1DD27AE7E9932B57E6CFDBFB3DF4CF52EAB79572519D5420725CBA83E7D0C803B8493C703A745918CEAE4F0CFB3436576BE152E514BD70CE51A856657425D94E60A867E2940C207E5CFC4EA36C5799D7A9F513909029D27A5079536CFE391AC11CC3CAEA34514996857CC564FA1C085D6F5C4776537C287B8D01EFBBEDDBB7CBB7AE57529050C48C7F473540F4CA4FA0A8F40918EEAD1CEE2DFD44DA8A741A95420546E59B2E4A6031163321D7E44929529011C8A50080859383D00FA68DF75F6F20EEC6DCDC1695470235522A990E1192D39DDB707D52B0950FCBA866BD7EDC53A2EDCDD1715424BD26DD151B72753D653C6257633652952940057379B0559513D720633AAA9EB32E52963603C2BA79626CE4CB27E22DFB865DCBE263712B0CADC8B1ADFB069DFDC"
    F_TXT$ = F_TXT$ & "94B352983F5CB6D27FEF4978551B937277426D46E5B8AA574C1B4E2B5528D1A5A1086DC92EE436F36CB684278200CE403EA03AE0EA8B776EE914C91C9EA0B956ABCF67CD764A5030CAD5D17C14E2B8B585F2F4A3A8C763AC7797856A7F888AD53EF1B96AEFDBD528713EEC9EDD2DFE42506CABD4B2A09E190A27033E950F97584956DBB5C513A55C6B1BAD253D2848012FB2FB38C64F382F98ACAB996FA444B5D1C87C8254A2E4B70DA1FCD44BD47A1DE9B9F7E1AFDB11A6B9FC3CFA9EA64D654CA5B5CF694DA94A3E6AD21C42393692067A2BEBAE8CECCEE635BB160C2AE18C69F514AD70EA54F5FBD0E63478BCD1FC14323E69524FC74ACA2DA966DA56052A9F46A84A71BA3151FB05216A506D414A121398C9E473ED082B249F4924EBCCDA0AAC7B0FC4655683104D6A877953CCF613350F026A11B01C29F33AFAD85249CF7F246AAAD76C976A9029E4A89480031660DB231F51C9C9CEA272B2B155ABEE2D201CE72E7ECF9E34303DC53DA86BC4BD9A2DFDDFB9E8EDE59837E52D35CA6908E41AACC0C1250803A9520214AC64AB2411AB9749FF00125B2351DE3A1505DA05523516E7A054533E04B96D95B441494B8DAC0EA12A041E80FBA07C73A710BE046C3BBFF9A9B7146AD427DDA63D2584BEF06D2952DA7127CB90D7AC10087123B8"
    F_TXT$ = F_TXT$ & "CE4A8FC74516952E814BABCA3582C496DD693210BAABE1D2975278A94942C9EA52A4754A46388F98D26697B45BB3B26E552834482EEE1FDFAA1514D618718A7330A5385425A1495ACF14A92968A4A41C92A38CF4D1150BC33EE55CB2FEDB73DE54FB590B6D2DAA25BF1CCB91C3912417DE012951CE094B67B0C76D0DD9F2F70183454F83B043FB86556378AD5B6555A7532D4F450E216BCA030CB4AF2D208E6B23A10127DDF89EF9D25769ABCEEFCEEFDA72EDB8A98F6ED96E2E649ACF985E52F932E30D454B984A54A5A5654BC27A0477C919725B5E11B6D6852D33AA3497AEFAA0214675CF2153D448EC7CB57B24FF008A0761F21A704488C408CDC78ACB71A3B69E2869A404A103E400E8068980A3FFD9"
    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$ Else Debug.Print FileLen(tmp_file$), Len(F_TXT$) / 2
End Function

Sub ShowUsageExample()        ' запуск формы "ИНСТРУКЦИИ по работе с программой"
    On Error Resume Next: Dim UF As Object: Set UF = UserForms.Add("F_UsageExample")
    If Not UF Is Nothing Then UF.Show
End Sub

Sub FirstRunActions()
    On Error Resume Next
    SETT.SetText "TEMPLATE_FOLDER", tt("CONST_TEMPLATE_FOLDER"), "Setup"
    SETT.SetText "OUTPUT_FOLDER", tt("CONST_OUTPUT_FOLDER"), "Setup"
    x = TEMPLATES_FOLDER$
    x = OUTPUT_FOLDER$
End Sub


Attribute VB_Name = "mod_Functions"
'---------------------------------------------------------------------------------------
' Module        : mod_Functions
' Author        : EducatedFool                     Date: 06.06.2014
' Professional application development for Microsoft Excel
' https://ExcelVBA.ru/         Skype: ExcelVBA.ru
'---------------------------------------------------------------------------------------

Option Explicit
Option Compare Text
Option Private Module

#If VBA7 Then        '  Office 2010-2013
    Declare PtrSafe Function SHCreateDirectoryEx Lib "shell32" Alias "SHCreateDirectoryExA" _
        (ByVal Hwnd As LongPtr, ByVal pszPath As String, ByVal psa As Any) As LongPtr
    Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
#Else        '  Office 2003-2007
    Declare Function SHCreateDirectoryEx Lib "shell32" Alias "SHCreateDirectoryExA" _
        (ByVal hwnd As Long, ByVal pszPath As String, ByVal psa As Any) As Long
    Declare Function GetTickCount Lib "kernel32" () As Long
#End If

Sub CtrlShiftV()        ' PasteFormulasForSeparateLetters
    On Error Resume Next: Err.Clear
    With GetObject("New:{1C3B4210-F441-11CE-B9EA-00AA006B1A69}")
        .GetFromClipboard
        CopyFormulas .GetText
    End With
End Sub

Sub CopyFormulas(ByVal txt$)
    On Error Resume Next: Err.Clear
    Dim ra As Range, n&, nn&, k&, cell As Range, addr$
    
    Application.ScreenUpdating = False
    Set ra = Selection
    
    For Each cell In ra.Cells
        n = n + 1
        If n = 1 Then
            addr$ = cell.Address(1, 1, xlA1)
            cell.value = txt
            cell.Font.Color = vbWhite
            cell.Font.Size = 1
        Else
            If cell.Address = cell.MergeArea.Cells(1).Address Then
                k = k + 1: cell.NumberFormat = "General"
                cell.Formula = "=MID(" & addr & "," & k & ",1)"
            End If
        End If
    Next cell
    If Err = 0 Then Shell "Cmd.exe /c echo " & Chr(7), vbHide
    Application.ScreenUpdating = True
End Sub

Sub Enable_HotKeys()
    ' назначает комбинации клавиш, если соответствующие опция включены в настройках программы
    On Error Resume Next
    With Application
        If SETT.GetBoolean("CheckBox_PasteFormulasForSeparateLetters") Then .OnKey "^+v", "CtrlShiftV" Else .OnKey "^+v"
        If SETT.GetBoolean("CheckBox_InsertTableLinks") Then .OnKey "^+t", "CtrlShiftT" Else .OnKey "^+t"
        If SETT.GetBoolean("CheckBox_InsertImageLinks") Then .OnKey "^+i", "CtrlShiftI" Else .OnKey "^+i"
    End With
End Sub

Sub Disable_HotKeys()
    On Error Resume Next: Err.Clear
    Application.OnKey "^+v"
End Sub

Function SpecialCells_TypeConstants(ByRef ra As Range) As Range
    ' возвращает диапазон, содержащий все заполненные ячейки диапазона ra
    On Error Resume Next: Dim en&: 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: Dim en&: 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 RenderString(ByVal txt$, ByRef options As Dictionary) As String
    On Error Resume Next: Dim arr, i&: arr = options.Keys
    For i = LBound(arr) To UBound(arr)
        txt$ = Replace(txt$, arr(i), options(arr(i)))
    Next i
    RenderString = txt$
End Function

Function CreatePathForFile(ByVal OldFilename$, Optional ByRef options As Dictionary) As String
    On Error Resume Next: Err.Clear
    Dim Mask$, RelativeFilePath$, subfolder$, filename$, pcc&, NewFolderPath$, NewFilename$
    If options Is Nothing Then Set options = New Dictionary ' для отладки. в функцию ВСЕГДА передаётся словарь options
    
    Mask$ = Replace(SETT.GetText("TextBox_OutputMask"), "/", "\")      ' f.e. {%str%} - {%filename%}.{%ext%}
    If InStr(1, Mask$, "{%ext%}", vbTextCompare) = 0 Then Mask$ = Mask$ & ".{%ext%}" ' добавлено в феврале 2021 (дописываем расширение, если пользователь стёр его из маски)
    
    RelativeFilePath$ = Replace(OldFilename$, TEMPLATES_FOLDER$, "") ' относительный путь к файлу шаблона, вида  file.doc или folder1\folder2\file.doc
    
    subfolder$ = Left(RelativeFilePath$, InStrRev(RelativeFilePath$, "\") - 1)
    If Len(subfolder$) Then
        options("{%subfolder%}") = subfolder$ ' добавлено в феврале 2021
        subfolder$ = subfolder$ & "\"
    End If
    
    filename$ = GetFilename(OldFilename$, False)
    
    If filename$ Like "*{print=#*}*" Then
        pcc& = Val(Split(filename$, "{print=")(1))
        filename$ = Replace(filename$, "{print=" & pcc& & "}", "")
        options("{%pcc%}") = pcc&
    End If
    If filename$ Like "*{pdf}*" Then
        filename$ = Replace(filename$, "{pdf}", "")
        options("{%pdf%}") = 1
    End If
    
    options("{%filename%}") = RenderString(filename$, options)
    options("{%ext%}") = GetExtensionForNewFile(RelativeFilePath$)
    
    If InStr(1, Mask$, "{%subfolder%}", vbTextCompare) = 0 Then
        NewFilename$ = OUTPUT_FOLDER$ & subfolder$ & FWF.Replace_symbols(RenderString(Mask$, options), , True)
    Else ' добавлено в феврале 2021 для маски имени файла с параметром {%subfolder%}
        NewFilename$ = OUTPUT_FOLDER$ & Replace(FWF.Replace_symbols(RenderString(Mask$, options), , True), "\{%subfolder%}\", "\")
    End If
    
    ' создание папки для файла
    NewFolderPath$ = Left(NewFilename$, InStrRev(NewFilename$, "\"))
    If Not FWF.FolderExists(NewFolderPath$) Then          ' если папка отсутствует
        SHCreateDirectoryEx Application.Hwnd, NewFolderPath$, ByVal 0&        ' создаём путь
    End If
    
    If Val(Application.Version) > 11 And (SETT.GetBoolean("CheckBox_PDF") Or (Val(options("{%pdf%}")) = 1)) Then        ' вывод в ПДФ
        If TemplateType(OldFilename$) <> "TXT" Then
            NewFilename$ = Left(NewFilename$, InStrRev(NewFilename$, ".") - 1) & ".pdf"
        End If
    End If
    
    CreatePathForFile = NewFilename$
End Function

Function GetExtensionForNewFile(ByVal filename$)
    On Error Resume Next: Err.Clear
    Select Case FWF.GetFileExtension(filename$)
        Case "XLT": GetExtensionForNewFile = "XLS"
        Case "XLTM": GetExtensionForNewFile = "XLSM"
        Case "XLTX": GetExtensionForNewFile = "XLSX"
        Case "DOT": GetExtensionForNewFile = "DOC"
        Case "DOTM": GetExtensionForNewFile = "DOCM"
        Case "DOTX": GetExtensionForNewFile = "DOCX"
        Case "DOCXML": GetExtensionForNewFile = "XML"
            
        Case Else: GetExtensionForNewFile = FWF.GetFileExtension(filename$)
    End Select
End Function

Function GetFileFormatForNewFile(ByVal filename$) As Long
    On Error Resume Next: Err.Clear
    Select Case FWF.GetFileExtension(filename$)
        Case "CSV": GetFileFormatForNewFile = xlCSV
        Case "XLS": GetFileFormatForNewFile = xlWorkbookNormal
        Case "XLSM": GetFileFormatForNewFile = 52        ' xlOpenXMLWorkbookMacroEnabled
        Case "XLSX": GetFileFormatForNewFile = 51        ' xlOpenXMLWorkbook
        Case "DOC": GetFileFormatForNewFile = 0        ' wdFormatDocument
        Case "DOCM": GetFileFormatForNewFile = 13        ' wdFormatXMLDocumentMacroEnabled
        Case "DOCX": GetFileFormatForNewFile = 12        ' wdFormatXMLDocument
        Case "XML": GetFileFormatForNewFile = 19        ' wdFormatFlatXML (XML)
            ' Case "XML": GetFileFormatForNewFile = 11        ' wdFormatXML (XML 2003)
    End Select
End Function


Function CollectionOfRowsBlocks(ByRef ra As Range) As Collection
    ' получает диапазон строк ra, ищет в столбце ComboBox_Multirow_GroupColumn уникальные значения,
    ' разбивает диапазон на блоки строк, по каждому из уникальных значений
    On Error Resume Next: Err.Clear
    Set CollectionOfRowsBlocks = New Collection
    Dim cell As Range, coll As New Collection, txt$, block As Range, col&, msg$, v
    
    If SETT.GetBoolean("CheckBox_Multirow_GroupRows") Then
        col& = SETT.GetNumber("ComboBox_Multirow_GroupColumn")
        If col& = 0 Then
            msg$ = "В настройках программы включен режим «MiltiRow» с опцией" & vbNewLine & _
                "«Группировать строки по заданному столбцу»" & vbNewLine & vbNewLine & _
                "А номер столбца, по которому надо группировать строки, — не указан." & vbNewLine & vbNewLine & _
                "Измените настройки программы, и снова запустите формирование документов."
            MsgBox msg, vbExclamation, "Не задан столбец, по которому группировать строки"
            ShowSettingsPage
            F_Settings.MultiPage_Options.value = 4
            F_Settings.ComboBox_Multirow_GroupColumn.SetFocus
            F_Settings.ComboBox_Multirow_GroupColumn.BackColor = vbRed
            Exit Function
        End If
        
        For Each cell In Intersect(ra.EntireRow, ra.Worksheet.Columns(col&)).Cells
            txt$ = Trim(cell): If Len(txt$) Then coll.Add txt$, txt$
        Next cell
        
        If coll.Count = 0 Then
            msg$ = "В настройках программы включен режим «MiltiRow» с опцией" & vbNewLine & _
                "«Группировать строки по заданному столбцу»" & vbNewLine & vbNewLine & _
                "Указан номер столбца, по которому надо группировать строки: «" & col& & "»" & vbNewLine & vbNewLine & _
                "В этом столбце, в выбранных строках, программа не нашла ни одной заполненной ячейки." & vbNewLine & _
                "Измените настройки программы, и снова запустите формирование документов."
            MsgBox msg, vbExclamation, "Не задан столбец, по которому группировать строки"
            ShowSettingsPage
            F_Settings.MultiPage_Options.value = 4
            F_Settings.ComboBox_Multirow_GroupColumn.SetFocus
            F_Settings.ComboBox_Multirow_GroupColumn.BackColor = vbRed
            Exit Function
        End If
        
        For Each v In coll
            Set block = Nothing
            For Each cell In Intersect(ra.EntireRow, ra.Worksheet.Columns(col&)).Cells
                If Trim(cell.MergeArea.Cells(1)) = v Then
                    If block Is Nothing Then Set block = cell Else Set block = Union(block, cell)
                End If
            Next cell
            If block Is Nothing Then
                MsgBox "Ошибка группировки строк в режиме Multirow", vbCritical, "Обратитесь к разработчику программы"
                Exit Function
            Else
                CollectionOfRowsBlocks.Add block.EntireRow
            End If
        Next v
        
    Else        ' возвращаем один блок - со всеми строками
        CollectionOfRowsBlocks.Add ra
    End If
End Function

Function FillInThisWorksheet(ByRef sh As Worksheet) As Boolean
    On Error Resume Next: FillInThisWorksheet = True: Dim NamesList$
    If Not SETT.GetBoolean("CheckBox_SpecifiedTemplateWorksheets") Then Exit Function
    NamesList$ = Replace(SETT.GetText("TextBox_SpecifiedTemplateWorksheets"), " ", "")
    If NamesList$ = "" Then Exit Function
    FillInThisWorksheet = "," & NamesList$ & "," Like "*," & Replace(sh.name, " ", "") & ",*"
End Function

Function CreateAndFill_XLS(ByVal TemplateFilename$, ByVal NewFilename$, _
        ByRef AllOptions As Dictionary, Optional ByRef pi As ProgressIndicator) As Boolean
    
    On Error Resume Next: Err.Clear
    Dim wb As Workbook, sh As Worksheet, nam As name, ra As Range, calc As XlCalculation, i&, txt_Line2$, options As Dictionary
    pi.Line3 = "Файл: " & Dir(TemplateFilename$, vbNormal)
    
    calc = Application.Calculation
    Application.Calculation = xlCalculationManual
    Application.DisplayAlerts = False
    
    If TemplateType(TemplateFilename$) Like "*template*" Then
        pi.line2 = "Создание документа Excel по шаблону ..."
        Set wb = Application.Workbooks.Add(TemplateFilename$)
    Else
        pi.line2 = "Открытие исходного документа Excel ..."
        Set wb = Application.Workbooks.Open(TemplateFilename$, False, True)
    End If
    
    '  Main_PI.Log "Документ создан? " & Not (WB Is Nothing)
    
    If SETT.GetBoolean("CheckBox_ReplaceExistingFieldCodesOnly") Then
        Set options = GetTemplateOptions(AllOptions, LoadFieldCodes_ExcelWorkbook(wb))
    Else
        Set options = AllOptions
    End If
    
    
    If SETT.GetBoolean("CheckBox_MultiRow") Then        ' размножение специальных строк в шаблоне
        Dim rc&: rc = Val(AllOptions("{%rc%}"))
        If rc& = 0 Then Main_PI.Log vbTab & "Ошибка при подготовке документа Excel: rc& = 0": Exit Function
        pi.line2 = "Добавление строк (" & rc& & " шт.) - режим MULTIROW ..."
        
        For Each nam In wb.Names
            If nam.name Like "MultiRow*" Then
                Set ra = Nothing: Set ra = nam.RefersToRange.EntireRow
                'Debug.Print  nam.Name, ra.Address
                If (Not ra Is Nothing) And FillInThisWorksheet(ra.Worksheet) Then
                    If ra.Rows.Count <= 100 Then
                        For i = 1 To rc&
                            ra.Offset(i * ra.Rows.Count).Insert Shift:=xlDown
                            ra.Copy ra.Offset(i * ra.Rows.Count)
                            ra.Offset(i * ra.Rows.Count).Replace "#}", "#" & i & "}", xlPart
                            ra.Offset(i * ra.Rows.Count).Replace "{%index%}", i, xlPart
                        Next i
                        ra.EntireRow.Delete
                    End If
                End If
            End If
        Next
    End If
    
    txt_Line2$ = "Подстановка значений в созданный по шаблону документ ..."
    pi.line2 = txt_Line2$
    
    Dim RIC As Boolean, arr, key$, txt As Variant, File_Format
    arr = options.Keys
    
    RIC = SETT.GetBoolean("CheckBox_ReplaceInColon")
    
    For i = LBound(arr) To UBound(arr)
        key$ = arr(i)
        txt = options(arr(i))
        ' If Len(txt) Then Debug.Print i, key$ & "=""" & txt & """"
        
        For Each sh In wb.Worksheets
            If FillInThisWorksheet(sh) Then
                If HasLinkToObject(txt, key$) Then
                    InsertObjectIntoXLS sh, txt, key$, pi
                    Err.Clear
                Else
                    sh.UsedRange.Replace key$, txt, xlPart, , False
                    'Debug.Print VarType(txt), TypeName(txt), txt
                    
                    If RIC Then
                        With sh.PageSetup
                            .LeftFooter = Replace(.LeftFooter, key$, txt, , , vbTextCompare)
                            .LeftHeader = Replace(.LeftHeader, key$, txt, , , vbTextCompare)
                            .CenterFooter = Replace(.CenterFooter, key$, txt, , , vbTextCompare)
                            .CenterHeader = Replace(.CenterHeader, key$, txt, , , vbTextCompare)
                            .RightFooter = Replace(.RightFooter, key$, txt, , , vbTextCompare)
                            .RightHeader = Replace(.RightHeader, key$, txt, , , vbTextCompare)
                        End With
                    End If
                End If
            End If
        Next sh
        
        If i Mod IIf(RIC, 5, 30) = 0 Then
            pi.line2 = txt_Line2$ & "  (выполнено " & Format(i / UBound(arr), "0%") & ")"
        End If
        DoEvents
    Next i
    
    pi.line2 = "Вычисление (пересчёт) формул ..."
    For Each sh In wb.Worksheets
        If FillInThisWorksheet(sh) Then
            sh.Calculate
            If SETT.GetBoolean("CheckBox_FormulasToValues") Then sh.UsedRange.value = sh.UsedRange.value
        End If
    Next sh
    If SETT.GetBoolean("CheckBox_ToDelete_Enabled") Then FindRangesToDelete wb
    
    Application.Run "'" & wb.name & "'!FillDone"
    
    pi.line2 = "Сохранение заполненного документа ..."
    pi.Line3 = "Новое имя файла: " & Split(NewFilename$, "\")(UBound(Split(NewFilename$, "\")))
    Main_PI.Log vbTab & "Сохранение созданного файла: " & Replace(NewFilename$, OUTPUT_FOLDER$, "...\")
    pi.FP.Repaint
    If Trim(wb.BuiltinDocumentProperties(5)) = "" Then wb.BuiltinDocumentProperties(5) = tt("FILE_COMMENT")
    
    If Val(Application.Version) > 11 And (SETT.GetBoolean("CheckBox_PDF") Or (Val(AllOptions("{%pdf%}")) = 1)) Then        ' вывод в ПДФ
        wb.ExportAsFixedFormat 0, NewFilename$        ' xlTypePDF = 0
    Else        ' обычное сохранение файла Excel
        File_Format = GetFileFormatForNewFile(NewFilename$)
        If Len(File_Format) Then
            wb.SaveAs NewFilename$, Val(File_Format)
        Else
            wb.SaveAs NewFilename$
        End If
    End If
    If SETT.GetBoolean("CheckBox_ImmediatePrintOut") Then wb.PrintOut , , PrintCopiesCount(AllOptions)
    wb.Close False
    
    CreateAndFill_XLS = Err = 0
    
    Application.Calculation = calc
    Application.DisplayAlerts = True
End Function




Function PrintCopiesCount(ByRef options As Dictionary) As Long
    On Error Resume Next
    Dim en&, PrintCopiesField$, CopiesCount&, pcc As Variant
    en& = Err.Number
    PrintCopiesCount = 1
    
    PrintCopiesField$ = options("{%PrintCopiesCount%}")
    If PrintCopiesField$ Like "{*?}" Then
        CopiesCount& = Fix(Val(options(PrintCopiesField$)))
    Else
        CopiesCount& = Fix(Val(PrintCopiesField$))
    End If
    If CopiesCount& > 0 Then PrintCopiesCount = CopiesCount&
    
    pcc = options("{%pcc%}")
    If pcc <> "" Then PrintCopiesCount = Val(pcc)
    
    If en& = 0 Then Err.Clear        ' Debug.Print "PrintCopiesCount = " & PrintCopiesCount
End Function

Function CreateAndFill_DOC(ByVal TemplateFilename$, ByVal NewFilename$, _
        ByRef AllOptions As Dictionary, Optional ByRef pi As ProgressIndicator) As Boolean
    On Error Resume Next: Err.Clear
    Dim doc As Object, ecount As Long, bm As Object, myStoryRange As Object, i&, oFirstCellRange As Object, bmText$, options As Dictionary
    pi.Line3 = "Шаблон: " & Dir(TemplateFilename$, vbNormal)
    
    If TemplateType(TemplateFilename$) Like "*template*" Then
        pi.line2 = "Создание документа Word по шаблону ..."
        Set doc = WA.Documents.Add(TemplateFilename$)
    Else
        pi.line2 = "Открытие исходного документа Word ..."
        Set doc = WA.Documents.Open(TemplateFilename$, , False, False)
    End If
    
    If SETT.GetBoolean("CheckBox_ReplaceExistingFieldCodesOnly") Then
        Set options = GetTemplateOptions(AllOptions, LoadFieldCodes_WordDocument(doc))
    Else
        Set options = AllOptions
    End If
    
    '  Main_PI.Log "Документ создан? " & Not (doc Is Nothing)
    doc.ActiveWindow.View.ShowFieldCodes = True        ' отображаем поля
    
    Dim TimeStamp As Double, ProcessTime1$, ProcessTime2$, Mcol&, MColumnTxt$, MColValue$, InlineBookmark As Boolean: TimeStamp = Timer
    
    If SETT.GetBoolean("CheckBox_MultiRow") Then        ' размножение специальных строк в шаблоне
        
        TimeStamp = Timer: Dim rc&: rc = Val(AllOptions("{%rc%}"))
        If rc& = 0 Then Main_PI.Log vbTab & "Ошибка при подготовке документа Word: rc& = 0": Exit Function
        
        '  Dim bm As Bookmark, ra As word.Range, oFirstCellRange As word.Range
        pi.line2 = "Размножение строк (с закладками MULTIROW) ..."
        For Each bm In doc.Bookmarks
            If bm.name Like "MultiRow*" Then
                
                Mcol& = 0: InlineBookmark = False
                If bm.name Like "*Inline*" Then InlineBookmark = True ' тогда игнорируем, что закладка внутри таблицы
                
                If bm.name Like "MultiRow*_col?*" Then        ' закладка с указанием номера столбца, - на основании которого размножать строки
                    MColumnTxt$ = UCase(Split(bm.name, "_col")(1))
                    If Val(MColumnTxt$) Then
                        Mcol& = Val(MColumnTxt$)
                    Else
                        If (MColumnTxt$ Like "[A-Z]") Or (MColumnTxt$ Like "[A-Z][A-Z]") Then
                            Mcol& = ThisWorkbook.Worksheets(1).Range(MColumnTxt$ & "1").Column
                        End If
                    End If
                End If
                If Mcol& > 0 Then If rc& <> SourceRows.Rows.Count Then Debug.Print "rc&<>SourceRows.Rows.Count", rc&, SourceRows.Rows.Count
                
                If (Not InlineBookmark) And bm.Range.Information(12) Then    'Закладка в таблице
                    For i = 1 To rc&
                        pi.Line3 = "Метка «" & bm.name & "», подготавливается строка " & i & " из " & rc&
                        
                        MColValue$ = "": MColValue$ = Trim(SourceRows.Cells(i, Mcol&).Text)        ' для Multirow по заданному столбцу
                        If (Mcol& = 0) Or (Len(MColValue$) > 0) Then
                            With bm.Range
                                Set oFirstCellRange = .Cells(1).Range
                                oFirstCellRange.Collapse 1        'wdCollapseStart
                                .Copy
                                'Вставка строки из закладки над закладкой
                                oFirstCellRange.PasteAndFormat 16        'wdFormatOriginalFormatting
                                WordReplacements .Tables(1).Rows(.Rows(1).Index).Range, "#}", "#" & i & "}"
                                WordReplacements .Tables(1).Rows(.Rows(1).Index).Range, "{%index%}", i
                                If SETT.GetBoolean("CheckBox_Multirow_InsertPageBreaks") Then
                                    If i > IIf(SETT.GetBoolean("CheckBox_Multirow_InsertPageBreaksAfterSecondRow"), 1, 0) Then
                                        oFirstCellRange.InsertBreak Type:=7        ' wdPageBreak = 7
                                    End If
                                End If
                            End With
                            DoEvents
                        End If
                    Next
                    bm.Range.Rows(1).Delete
                Else
                    bmText$ = bm.Range.Text
                    For i = rc& To 1 Step -1
                        pi.Line3 = "Метка «" & bm.name & "», подготавливается строка " & rc - i + 1 & " из " & rc&
                        'Debug.Print "Метка «" & bm.Name & "», подготавливается строка " & rc - i + 1 & " из " & rc&
                        
                        MColValue$ = "": MColValue$ = Trim(SourceRows.Cells(i, Mcol&).Text)        ' для Multirow по заданному столбцу
                        If (Mcol& = 0) Or (Len(MColValue$) > 0) Then
                            With bm.Range
                                .InsertParagraphAfter
                                With .Paragraphs.First.Next
                                    .Range.InsertCrossReference ReferenceType:=2, ReferenceKind:=-1, _
                                        ReferenceItem:=bm.name, InsertAsHyperlink:=False, _
                                        IncludePosition:=False, SeparateNumbers:=False, SeparatorString:=" "
                                    .Range.Fields.Unlink
                                End With
                                WordReplacements .Paragraphs.First.Next.Range, "#}", "#" & i & "}"
                                WordReplacements .Paragraphs.First.Next.Range, "{%index%}", i
                            End With
                            DoEvents
                        End If
                    Next
                    bm.Range.Delete
                End If
                DoEvents
            End If
        Next
        ProcessTime1$ = Format(Timer - TimeStamp, "0.00") & " сек."
    End If
    
    pi.line2 = "Подстановка значений в созданный по шаблону документ..."
    pi.Line3 = "Шаблон: " & Dir(TemplateFilename$, vbNormal)
    
    Dim arr, FullReplace As Boolean, Replace_LF_with$, key$, txt$, File_Format As Long, lngJunk As Long, oShp As Object, rangeColl As Collection
    arr = options.Keys
    
    Replace_LF_with$ = Replace(SETT.GetText("ComboBox_LineFeed"), "del", "")
    FullReplace = SETT.GetBoolean("CheckBox_ReplaceInColon")
    ' Replace_LF_with$ = Chr(13) + Chr(10)
    
    TimeStamp = Timer
    For i = LBound(arr) To UBound(arr)
        key$ = arr(i)
        txt$ = options(arr(i))
        txt$ = Replace(txt$, Chr(10), Replace_LF_with$)        ' переносы строк
        Err.Clear
        
        pi.line2 = "Подстановка значений в созданный по шаблону документ... (" & i + 1 & " / " & UBound(arr) + 1 & ")"
        If HasLinkToObject(txt$, key$) Then
            InsertObjectIntoDOC doc, txt$, key$, pi
            Err.Clear
        Else
            If FullReplace Then
                ' новая версия замены
                
                lngJunk = doc.Sections(1).Headers(1).Range.StoryType    'Fix the skipped blank Header/Footer problem
                
                Set rangeColl = New Collection
                For Each myStoryRange In doc.StoryRanges
                    rangeColl.Add myStoryRange
                Next
                
                For Each myStoryRange In rangeColl          'For Each myStoryRange In doc.StoryRanges
                    Do
                        DoEvents
                        WordReplacements myStoryRange, key$, txt$
                        
                        If SETT.GetBoolean("CheckBox_ReplaceInColonShapes") Then
                            DoEvents
                            Select Case myStoryRange.StoryType
                                Case 6, 7, 8, 9, 10, 11
                                    If myStoryRange.ShapeRange.Count > 0 Then
                                        Dim Line3$, shapeindex&
                                        shapeindex& = 0: Line3$ = "Замена текста в графических объектах (XXX / " & myStoryRange.ShapeRange.Count & ")"
                                        For Each oShp In myStoryRange.ShapeRange
                                            If oShp.TextFrame.HasText Then WordReplacements oShp.TextFrame.TextRange, key$, txt$
                                            shapeindex& = shapeindex& + 1: If shapeindex& Mod 10 = 0 Then pi.Line3 = Replace(Line3$, "XXX", shapeindex&)
                                        Next
                                        pi.Line3 = ""
                                    End If
                                Case Else
                                    'Do Nothing
                            End Select
                        End If
                        
                        Set myStoryRange = myStoryRange.NextStoryRange    'Get next linked story (if any)
                    Loop Until myStoryRange Is Nothing
                    
                    '    WordReplacements myStoryRange, key$, txt$
                    '    While Not (myStoryRange.NextStoryRange Is Nothing)
                    '        DoEvents
                    '        Set myStoryRange = myStoryRange.NextStoryRange
                    '        WordReplacements myStoryRange, key$, txt$
                    '    Wend
                Next myStoryRange
                Set rangeColl = Nothing
                
            Else
                ' обычная быстрая замена
                'doc.Range.Find.Execute key$, False, , False, , , , , , txt$, 2
                WordReplacements doc.Range, key$, txt$
            End If
        End If
        
        
        If Err Then
            If (Err.Number = 4605) And SETT.GetBoolean("CheckBox_InsertIntoFields") Then
            Else
…