1 (изменено: Androgen, 2007-05-12 05:46:14)

Тема: AutoHotkey и Total Uninstall

Есть такая совершенно изумительная прожка - Total Uninstall. Одна из самых используемых мной. Отслеживает изменения в файловой системе и реестре. Предназначена, как явствует из названия, для полного и корректного удаления приложений, установка которых была отслежена с помощью этой прожки. В этом она похожа на всем, наверное, известную Ashampoo UnInstaller. Только работает намного быстрее и корректнее, а главное в разы более удобна и настраиваема (ИМХО, разумеется). На мой взгляд, такого же удобного и функционального инструмента для поиска изменений просто не существует. (А поскольку тема отслеживания изменений интересует меня уже несколько лет, то, я полагаю, что перепробовал уже, видимо, все прожки на эту тему).
Лично я почти не использую Total Uninstall по его прямому назначению. Гораздо, гораздо чаще я ищу этой прогой изменения, производимые какой-нибудь другой прогой (или мной лично). Например, так: запускаю TU, делаю снимок. Что-нибудь меняю (ну, скажем, некую настройку рабочего стола). Делаю второй снимок, получаю список изменений. Если изменения были, допустим, в реестре, то можно сохранить файл реестра с зафиксированными изменениями. Теперь я могу воткнуть эту настройку на других компах (ну или использовать как-нибудь ещё).
Одной фишки мне не хватало: нельзя так же легко, как изменения в реестре, сохранить добавленные-измененные файлы (для последующего использования). Вот и написал я скрипт (уже, наверное, с год назад). Хотел, прежде чем выложить его, доделать кое-что, но заленился. Выкладываю в таком недоделанном (но полностью работоспособном) виде.
Приведу ОДИН примерчик применения скрипта. Вот поставил я какую-нибудь игруху (я их ставлю на отдельный раздел). Разумеется, отмониторил инсталляцию. Вижу, что игруха (вот собака, поубивал бы) набросала dll-шек в system32. Запускаю скрипт, и он мне сохраняет все накиданные dll-шки отдельно. Бросаю эти dll-шки в папку с игрухой. Теперь, когда я переустановлю систему (или восстановлюсь из образа), игруха будет запускаться без всяких вопросов и возмущений, что, мол, ей чего-то не хватает.
Короче, прога мне нравится, но FAQ по её использованию писать не хочется. Сама прога живет здесь: http://www.martau.com/tu.php, всякое про неё можно почитать здесь: http://forum.ru-board.com/topic.cgi?for … opic=24554.
Вот скрипт. Будут замечания, пожелания - не стесняйтесь.

Скрипт сохраняет (для отслеженного TU приложения):
- созданные/изменённые файлы/папки
- файл реестра с созданными/изменёнными/удалёнными разделами и параметрами
Папка для сохранения задаётся в настройках скрипта, а для откомпилированного скрипта - в командной строке.
Приложение будет сохранено в указанную папку, в подпапку с именем сохраняемого приложения.
Вдобавок, при сохранении, можно создавать отдельные подпапки для созданных и для измененных файлов.

Работает скрипт так:
1. Запускаем Total Uninstall
2. Ставим курсор на имя сохраняемого приложения (слева в списке отслеженных приложений)
а) если желаем сохранять не целиком приложение, а только какие-то его части, то
ставим курсор (справа) на сохраняемые файл/папку или раздел/параметр реестра
3. Запускаем этот скрипт
4. Получаем желаемый результат

Скрипт тестировался на Total Uninstall v3.70 - v3.83.

;*****************************************************************************
; AutoHotkey Version: 1.0.46.15+
; Автор:              Androgen Belkin
; Имя скрипта:        SaveApp4TU.ahk (v.1.4)
;
; Тестировалось на Total Uninstall v3.70 - v3.83.
;*****************************************************************************
; Сохранить приложение, отслеженное Total Uninstall`ом.
; Скрипт сохраняет (для отслеженного приложения):
; - созданные/изменённые файлы/папки
; - файл реестра с созданными/изменёнными/удалёнными разделами и параметрами
;
; Папка для сохранения задаётся в настройках скрипта, а для откомпилированного скрипта - в командной строке.
; Приложение будет сохранено в указанную папку, в подпапку с именем сохраняемого приложения.
;
; Порядок использования скрипта:
; 1. Запускаем Total Uninstall
; 2. Ставим курсор на имя сохраняемого приложения (слева в списке отслеженных приложений)
;     а) если желаем сохранять не целиком приложение, а только какие-то его части, то
;     ставим курсор (справа) на сохраняемые файл/папку или раздел/параметр реестра
; 3. Запускаем этот скрипт
; 4. Получаем желаемый результат :)
;*****************************************************************************
;~ #NoTrayIcon    ; не отображать значок скрипта в трее
#NoEnv        ; запрещаем имена переменных как у переменных окружения
#SingleInstance force ; перезапускаем скрипт без вопросов

/*
Что сделать:
1) дополнить скриптом для копирования сохранённого в исходное расположение
2) изменить (расширить) варианты сохранения через переменные окружения в путях
    (чтобы отвязать от профиля пользователя и т.д.)
*/


;-------------------------------------------------------------------------------
; НАСТРОЙКИ ПОЛЬЗОВАТЕЛЯ
KeepSetCompile := ;True ; сохранять заданные ниже настройки в скомпилированном скрипте,
; ...или использовать переданные в ком. строке (используется для компиляции скрипта с текущими настройками)
Mon_App_Path = D:\Рабочая папка\Сохраненное через Total Uninstall ; путь к папке, куда сохранять отслеженные приложения
Create_Sub_Dir     := True        ; создавать ли отдельные подпапки для созданных и для измененных файлов (True/False)
Del_Reg_Check     := True        ; ставить ли галочку на "удаленные ключи" при сохранении файла реестра (True/False)
Log_Name = SaveApp4TU.log     ; имя файла лога операций скрипта
Separator = ==================================================================== ; разделитель для визуальной отбивки в логе
; КОНЕЦ НАСТРОЕК ПОЛЬЗОВАТЕЛЯ
;-------------------------------------------------------------------------------


;-------------------------------------------------------------------------------
; ЗАДАНИЕ ПЕРЕМЕННЫХ ПОД АНГЛИЙСКИЙ И РУССКИЙ ИНТЕРФЕЙС TU
; Внимание! Переменные под En и Ru интерфейсы могут измениться в будущих версиях TU. Возможно, их понадобится изменить.
; Кроме того, ниже знаками (!!!) отмечены могущие измениться места интерфейса.
MonApp_En = Monitored Applications ; текст контрола при английском интерфейсе
MonApp_Ru = Отслеженные приложения ; текст контрола при русском интерфейсе

; ПЕРЕМЕННЫЕ ПОД АНГЛИЙСКИЙ ИНТЕРФЕЙС TU (задаются по умолчанию)
wFolder = (FOLDER)                 ; какое слово ищем в файле изменений
wFile = (FILE)                     ; какое слово ищем в файле изменений
Allow_Details = All Details,Created Items,Deleted Items,Changed Items ; разрешенные названия подробностей
nDetails = All Details            ; нужное название подробностей
wSelection = (Selection)        ; слово в файле изменений (сохранены не все, а выделенные изменения)
wView = FILE SYSTEM DETAILS        ; часть служебной строки, где указано, что сохранено все, или только выделенное
wReg = REGISTRY DETAILS            ; часть служебной строки, откуда начинается лог изменений реестра

; ПЕРЕМЕННЫЕ ПОД РУССКИЙ ИНТЕРФЕЙС TU (задаются вызовом функции)
ChangeVars() ; функция изменения значений переменных под русский интерфейс
{
    Global                            ; все переменные функции будут глобальными
    wFolder = (ПАПКА)                 ; какое слово ищем в файле изменений
    wFile = (ФАЙЛ)                     ; какое слово ищем в файле изменений
    Allow_Details = Показать все,Созданные элементы,Удаленные элементы,Измененные элементы ; разрешенные названия подробностей
    nDetails = Показать все            ; нужное название подробностей
    wSelection = (Выделение)        ; слово в файле изменений (сохранены не все, а выделенные изменения)
    wView = ПОДРОБНОСТИ О ФАЙЛОВОЙ     ; часть служебной строки, где указано, что сохранено все, или только выделенное
    wReg = ДЕТАЛИ РЕЕСТРА            ; часть служебной строки, откуда начинается лог изменений реестра
    Return                            ; выходим из функции
} ; конец функции
; КОНЕЦ ЗАДАНИЯ ПЕРЕМЕННЫХ
;-------------------------------------------------------------------------------

If A_IsCompiled ; если скрипт скомпилирован
{
    If NOT KeepSetCompile ; если настройки, указанные в скрипте не используются
    {
        ; ПРОВЕРКА НАЛИЧИЯ ПЕРЕДАННЫХ ПАРАМЕТРОВ
        If 0 = 0 ; если скрипт запущен без параметров
        {
            MsgBox, 0, SaveApp4TU -- by Androgen Belkin©,
            ( LTrim
                Скрипт следует запускать с параметром:
                /dir=path        где path - путь к папке, в которую будет сохраняться выбранное приложение.
                %A_Space%        (Если путь содержит пробелы, то его нужно заключить в кавычки).
                Приложение будет сохранено в указанную папку, в подпапку с именем сохраняемого приложения.

                Дополнительные параметры:
                /sd        создавать отдельные подпапки для созданных и для изменённых файлов
                /rd        писать в создаваемый файл реестра и удалённые разделы реестра
            )
            ExitApp ; конец скрипта
        }
        Mon_App_Path = ; сбрасываем путь к папке, куда сохранять отслеженные приложения
        Create_Sub_Dir = ; сбрасываем "создавать отдельные подпапки для созданных и для измененных файлов"
        Del_Reg_Check = ; сбрасываем "ставить ли галочку на "удаленные ключи" при сохранении файла реестра"
        Loop, %0% ; лопатим полученную ком. строку
        {
            Param := %A_Index% ; берем каждый параметр (чтобы далее не париться с синтаксисом)
            If ( Param = "/sd" )
                Create_Sub_Dir := True ; создавать отдельные подпапки для созданных и для измененных файлов
            If ( Param = "/rd" )
                Del_Reg_Check := True ; ставить галочку на "удаленные ключи" при сохранении файла реестра
            If ( InStr( Param, "dir=" ) = 1 ) OR ( InStr( Param, "dir=" ) = 2 ) ; если стоит в начале (или после "/")
            {
                StringTrimLeft, Mon_App_Path, Param, InStr( Param, "dir=" ) + 3 ; отрезаем идентификатор
                StringRight, RightSymbol, Mon_App_Path, 1 ; берем последний символ
                If RightSymbol = \ ; если последний символ в строке бэкслэш "\", то...
                    StringTrimRight, Mon_App_Path, Mon_App_Path, 1 ; удалить последний символ ("\")
            }
        }
    }
}

Mon_App_Path := ExpandEnvVars( Mon_App_Path ) ; разворачиваем переменные окружения в пути

; ОПРЕДЕЛЯЕМ ЗАПУЩЕН ЛИ TU
Process, Exist, Tu.exe ; проверить существование процесса
TU_PID = %ErrorLevel% ; запоминаем PID (понадобится далее)
If TU_PID = 0 ; если процесс НЕ существует, то...
{
    MsgBox, 0, SaveApp4TU -- by Androgen Belkin©,
    ( LTrim
        Не запущена программа Total Uninstall.

        Порядок использования скрипта:
        1. Запускаем Total Uninstall
        2. Ставим курсор на имя сохраняемого приложения (слева в списке отслеженных приложений)
        %A_Tab%а) если желаем сохранять не целиком приложение, а только какие-то его части, то
        %A_Tab%ставим курсор (справа) на сохраняемые файл/папку или раздел/параметр реестра
        3. Запускаем этот скрипт
    )
    ExitApp ; конец скрипта
}

; ОПРЕДЕЛЯЕМ НЕ НАХОДИТСЯ ЛИ TU В РАБОТЕ (создает/сравнивает снимки)
IfWinExist, ahk_class TfrmInstall.UnicodeClass ; если существует окно снимков (Total Uninstall работает), то...
{
    Text = Программа Total Uninstall находится в процессе создания снимков.`nСкрипт прерывает свою работу.
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}

; ОПРЕДЕЛЯЕМ СУЩЕСТВУЕТ ЛИ ГЛАВНОЕ ОКНО TU (на всякий случай)
DetectHiddenWindows, On ; искать в скрытых окнах...
IfWinNotExist, ahk_class TfrmTUMain.UnicodeClass ; если главное окно не существует, то...
{
    Text = Не могу найти главное окно программы Total Uninstall.`nСкрипт прерывает свою работу.
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}

; ОПРЕДЕЛЯЕМ ЯЗЫК ИНТЕРФЕЙСА
; Получить текст контрола (Отслеженные приложения или Monitored Applications)
ControlGetText, Interface_Lang, TTntPanel.UnicodeClass3, ahk_class TfrmTUMain.UnicodeClass ; (!!!)
If ( Interface_Lang != MonApp_En AND Interface_Lang != MonApp_Ru ) ; если язык не тот, что нужно, то...
{
    Text =
    ( LTrim
        Интерфейс Total Uninstall НЕ АНГЛИЙСКИЙ и НЕ РУССКИЙ.
        Скрипт рассчитан на работу только с этими языками интерфейса.
        Скрипт прерывает свою работу.
    )
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}
If Interface_Lang = %MonApp_Ru% ; если язык - русский, то...
    ChangeVars() ; вызываем функцию изменения значений переменных под русский интерфейс

; ПОЛУЧАЕМ ИМЯ ПРИЛОЖЕНИЯ (и текущий вид подробностей изменений)
ControlGetText, App_Name_and_Details, TTitlePanel.UnicodeClass1, ahk_class TfrmTUMain.UnicodeClass ; (!!!)

; ИЗВЛЕКАЕМ ТЕКУЩИЙ ВИД ПОДРОБНОСТЕЙ ИЗМЕНЕНИЙ
StringGetPos, Pos_L_Bracket, App_Name_and_Details, [, R ; получаем позицию символа "[" в строке
StringGetPos, Pos_R_Bracket, App_Name_and_Details, ], R ; получаем позицию символа "]" в строке
StringMid, Cur_Details, App_Name_and_Details, % Pos_L_Bracket + 2, % Pos_R_Bracket - Pos_L_Bracket - 1 ; текущий вид подробностей

; ПРЕДУПРЕДИТЬ, ЕСЛИ ВИД ПОДРОБНОСТЕЙ ИЗМЕНЕНИЙ НЕ "ПОКАЗАТЬ ВСЕ"
;~ If Cur_Details contains %Allow_Details% ; если текущий вид подробностей из списка разрешенных названий подробностей, то...
;~ {
    If ( Cur_Details != nDetails ) ; если вид не "Показать все", то...
    {
        Text =
        ( LTrim
            Текущий вид подробностей изменений:
            "%Cur_Details%".
            БУДУТ СОХРАНЕНЫ НЕ ВСЕ ОТСЛЕЖЕННЫЕ ИЗМЕНЕНИЯ.
            Продолжить?
        )
        Button := My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text, 1 ) ; вызываем функцию отображения MsgBox'а с текстом по центру
        If Button != OK ; если не ОК, то...
            ExitApp ; конец скрипта
    }
;~ }
;~ Else ; если текущий вид подробностей неверен (значит скобки "[]" в строке - часть имени (это косяк TU))
;~     Pos_L_Bracket := StrLen( App_Name_and_Details ) ; устанавливаем положение скобки на всю длину имени (чтобы ниже ничего не отрезалось)

; ПОЛУЧИТЬ ИМЯ ОТСЛЕЖЕННОГО ПРИЛОЖЕНИЯ
StringLeft, Mon_App_Name, App_Name_and_Details, Pos_L_Bracket ; получить имя отслеженного приложения
Mon_App_Name = %Mon_App_Name% ; избавляемся от начальных и/или конечных пробелов в имени

; ПРОВЕРИТЬ КОРРЕКТНОСТЬ ПОЛУЧЕННОГО ИМЕНИ
If Mon_App_Name = ; если имя пустое (состоит из пробелов)
{
    Mon_App_Name_Temp = %A_DD%-%A_MMM%-%A_YYYY%_%A_Hour%.%A_Min%.%A_Sec% ; имя папки будет текущая дата-время
    Text =
    ( LTrim
        Не задано имя приложения (возможно, имя состоит из пробелов).
        Приложение будет сохранено с именем "%Mon_App_Name_Temp%".
        Продолжить?
    )
    Button := My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text, 1 ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    If Button != OK ; если не ОК, то...
        ExitApp ; конец скрипта
    Mon_App_Name = %Mon_App_Name_Temp% ; фиксируем новое имя
}
If Mon_App_Name contains \,*,/,:,?,`",<,> ; если имя содержит недопустимые символы
{
    Mon_App_Name_Temp = %A_DD%-%A_MMM%-%A_YYYY%_%A_Hour%.%A_Min%.%A_Sec% ; имя папки будет текущая дата-время
    Text =
    ( LTrim
        Имя: "%Mon_App_Name%" содержит недопустимые символы \*/?:"<>.
        Приложение будет сохранено с именем "%Mon_App_Name_Temp%".
        Продолжить?
    )
    Button := My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text, 1 ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    If Button != OK ; если не ОК, то...
        ExitApp ; конец скрипта
    Mon_App_Name = %Mon_App_Name_Temp% ; фиксируем новое имя
}
If Mon_App_Name contains | ; если имя содержит "особо недопустимый" символ
{
    Text =
    ( LTrim
        Имя сохраняемого приложения: "%Mon_App_Name%".
        Total Uninstall отказывается сохранять изменения для приложения,
        в имени которого содержится символ "|".
        Переименуйте приложение, и запустите скрипт снова.
        Скрипт прерывает свою работу.
    )
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}

; СОЗДАТЬ ПАПКУ ДЛЯ СОХРАНЕНИЯ ОТСЛЕЖЕННОГО ПРИЛОЖЕНИЯ
Mon_App_Path = %Mon_App_Path%\%Mon_App_Name% ; путь к папке, куда сохранять отслеженное приложение (начало - в настройках пользователя)
; Проверяем папку для сохранений на возможное существование
IfExist, %Mon_App_Path% ; если папка для сохранений УЖЕ существует, то...
{
    Text = Уже существует папка:`n"%Mon_App_Path%".`n`nПерезаписать?
    Button := My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text, 1, 2 ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    If Button != OK ; если не ОК, то...
        ExitApp ; конец скрипта
    ; если нажата "ОК"
    FileRemoveDir, %Mon_App_Path%, 1 ; удалить папку со всем содержимым
    If ErrorLevel ; если удалить папку почему-либо не удалось, то...
    {
        Text = НЕ УДАЕТСЯ УДАЛИТЬ ПАПКУ:`n"%Mon_App_Path%".`nСкрипт прерывает свою работу.
        My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
        ExitApp ; конец скрипта
    }
    Loop ; подождать завершения удаления
    {
        Sleep, 50 ; небольшая пауза
        IfNotExist, %Mon_App_Path% ; если папка для сохранений УЖЕ НЕ существует, то...
            Break ; выходим из цикла
    }
}

; Создаем папку
FileCreateDir, %Mon_App_Path% ; создать папку для сохранений
If ErrorLevel ; если папка почему-либо не создалась (например, в настройках пользователя указан неверный путь), то...
{
    Text = Не могу создать папку:`n"%Mon_App_Path%"`nСкрипт прерывает свою работу.
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}

; Показываем сообщение о работе скрипта
SplashImage,, FS10 B1 CT000080, Работаю...`nПожалуйста`, подождите.

; СОХРАНЯЕМ ЛОГ TU
; Выбрать пункт меню "Извлечь -> Изменения"
WinMenuSelectItem, ahk_class TfrmTUMain.UnicodeClass,, 1&, 8&, 1& ; (!!!) выбрать пункт меню Файл -> Извлечь -> Изменения
WinWait, ahk_class #32770 ahk_pid %TU_PID%,, 6 ; ждем окно "Сохранить как", назначаем его последним найденным (сейчас понадобится)
If ErrorLevel
{
    Text = Не дождались нужного окна!`n`nСкрипт прерывает свою работу.
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    ExitApp ; конец скрипта
}

; Сохранить файл изменений для отслеженного приложения
Changes_File_Path = %A_Temp%\%Mon_App_Name%.txt ; составить путь к сохраняемому файлу изменений
IfExist, %Changes_File_Path% ; если файл уже существует
    FileDelete, %Changes_File_Path% ; удалить его (чтобы не подтверждать запрос на перезапись)
Loop ; убеждаемся, что путь к сохраняемому файлу вставлен правильно
{
    ControlSetText, Edit1, %Changes_File_Path% ; вставить путь к сохраняемому файлу
    Sleep, 10*A_index ; увеличивающаяся пауза, чтобы путь успевал вставиться
    ControlGetText, Changes_File_Path_New, Edit1 ; получить имя сохраняемого файла изменений
    If Changes_File_Path_New = %Changes_File_Path% ; если имя (и путь) вставились нормально, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что подтверждение принято
{
    ControlSend, Button2, {Space} ; нажать ОК
    Sleep, 10*A_index ; увеличивающаяся пауза, чтобы окно успевало среагировать
    IfWinNotExist ; если окно сохранения файла уже НЕ существует, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что файл изменений создан
{
    IfExist, %Changes_File_Path% ; если файл существует, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что сохранение файла изменений завершено
{
    FileGetSize, File_Sise, %Changes_File_Path% ; получаем размер файла изменений
    If File_Sise_Bak = %File_Sise% ; если текущий размер файла равен предыдущему размеру, то...
        Break ; конец цикла
    Sleep, 50 ; небольшая пауза
    File_Sise_Bak = %File_Sise% ; запоминаем предыдущий размер файла изменений
}

; СОХРАНЯЕМ ОТСЛЕЖЕННЫЕ ИЗМЕНЕНИЯ В ФАЙЛОВОЙ СИСТЕМЕ
; --------------------------------------------------
Loop, Read, %Changes_File_Path% ; читаем файл изменений
{
    Cur_String = %A_LoopReadLine% ; избавляемся от начальных и/или конечных пробелов в строке

    ; определяем сохраняется ли все или только выделенное
    IfInString, Cur_String, %wView% ; находим нужную служебную строку
    {
        IfInString, Cur_String, %wSelection% ; если в ней указано, что сохранено только выделенное, то...
        {
            OnlySelection = True ; добавляем текст к финальному сообщению (в его начало)
            Selection_Log = СОХРАНЯЛИСЬ ТОЛЬКО ВЫДЕЛЕННЫЕ ПУНКТЫ ; добавляем текст в лог
        }

        ; Получить текущий вид подробностей изменений (данные берутся из файла изменений, т.к. иногда их нет в основном окне TU)
        StringGetPos, Pos_L_Bracket, Cur_String, [, R ; получаем позицию символа "[" в строке
        StringGetPos, Pos_R_Bracket, Cur_String, ], R ; получаем позицию символа "]" в строке
        StringMid, Cur_Details, Cur_String, % Pos_L_Bracket + 2, % Pos_R_Bracket - Pos_L_Bracket - 1 ; текущий вид подробностей
    }

    ; определяем нужно ли сохранять реестр
    IfInString, Cur_String, %wReg% ; находим нужную служебную строку
        wRegN = %A_index% ; запоминаем номер этой служебной строки
    If ( A_index = wRegN + 2 AND Cur_String = "" ) ; если здесь уже должны быть разделы/параметры реестра а их нет, то...
        Reg_Empty = 1 ; выставляем флаг, что отсутствуют изменения реестра (реестр сохранять не надо)

    ; определяем положение служебных символов
    Pos_Deleted := InStr( Cur_String, "(-)" ) ; получаем в строке положение знака удаления (если он там есть)
    Pos_Created := InStr( Cur_String, "(+)" ) ; получаем в строке положение знака создания (если он там есть)
    Pos_Modified := InStr( Cur_String, "(*)" ) ; получаем в строке положение знака изменения (если он там есть)

    ; пропускаем удаленные файлы/папки
    If Pos_Deleted = 1 ; если строка начинается со знака удаления файла/папки, то...
        Continue ; перейти к следующей строке ( не обрабатывать эту строку)

    ; обрабатываем исходные папки
    Pos_wFolder := InStr( Cur_String, wFolder ) ; получаем позицию слова "(ПАПКА)" в строке
    If ( Pos_wFolder = 1 OR Pos_wFolder = 4 ) ; если строка "начинается" со слова "(ПАПКА)" (или (+)(ПАПКА)), то...
    {
        StringTrimLeft, Cur_Dir, Cur_String, Pos_wFolder + StrLen( wFolder ) ;  отрезаем начальную служебную инфу, получаем имя исходной папки
        Continue ; перейти к следующей строке (если в следующей строке - снова папка, то текущая папка пуста и копироваться не будет)
    }

    ; обрабатываем исходные файлы
    Pos_wFile := InStr( Cur_String, wFile ) ; получаем позицию слова "(ФАЙЛ)" в строке
    If Pos_wFile = 4 ; если строка "начинается" со слова "(ФАЙЛ)" (со служебными знаками), то...
    {
        ; получаем имя исходного файла
        StringTrimLeft, Cur_File, Cur_String, Pos_wFile + StrLen( wFile ) ; отрезаем начальную служебную инфу
        ; если файл был изменен, то этого достаточно, т.к. останется только имя файла (служебная инфа - в следующей строке)
        ; если файл был добавлен, то после имени файла будет еще служебная инфа, поэтому ниже мы ее удаляем
        If Pos_Created = 1 ; если файл был добавлен (гарантируем, что не испортим имя файла, имеющее знак " = "), то...
        {
            Pos_equal := InStr( Cur_File, " = ","", 0 ) ; получаем позицию последнего знака " = " в строке
            StringLeft, Cur_File, Cur_File, Pos_equal - 1 ; выбираем из строки окончательное имя исходного файла (до служебной инфы)
        }

        ; проверяем нет ли косяка TU (иногда, при экспорте изменений в файл, он не пишет исходную папку)
        If Cur_Dir = ; если текущая папка не указана, то...
        {
            Text =
            ( LTrim
                Программа Total Uninstall ОШИБЛАСЬ ПРИ ЭКСПОРТЕ ИЗМЕНЕНИЙ В ФАЙЛ.
                В созданном TU логе изменений, не указана исходная папка, откуда должны копироваться файлы.
                Попробуйте сохранить другое приложение, а затем опять попытаться сохранить "%Mon_App_Name%"
                Скрипт прерывает свою работу.
            )
            My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
            FileRemoveDir, %Mon_App_Path%, 1 ; удалить созданную папку для изменений со всем содержимым
            ExitApp ; конец скрипта
        }

        ; проверяем существование исходного файла
        IfNotExist, %Cur_Dir%\%Cur_File% ; если не существует исходный файл
        {
            If Log_Error_Exists = ; если лог ошибок существования файлов пуст, то...
                Log_Error_Exists = Не существуют следующие файлы:`n%Separator%`n
                ; вносим заголовок в переменную для лога ошибок существования файлов
            Log_Error_Exists = %Log_Error_Exists%%Cur_Dir%\%Cur_File%`n
            ; добавляем отсутствующий файл в переменную для лога ошибок существования файлов
            Continue ; продолжаем читать файл изменений
        }

        ; создаем отдельные подпапки для созданных и для измененных файлов, если указано в настройках пользователя
        If ( Create_Sub_Dir = True ) ; если указано создавать, то...
        {
            If Pos_Created = 1 ; если файл был добавлен, то...
                Sub_Dir = \Created ; назначаем подпапку для целевого файла
            If Pos_Modified = 1 ; если файл был изменен, то...
                Sub_Dir = \Modified ; назначаем подпапку для целевого файла
        }

        ; создаем дерево папок
        StringReplace, Tree_Dir, Cur_Dir, : ; удаляем из строки ":" (подготавливаем строку к обработке)
        Cur_Tree_Dir = ; очищаем переменную (от предыдущего цикла)
        Loop, Parse, Tree_Dir,\ ; парсим строку
        {
            If A_Index = 1 ; если первая итерация цикла, то...
                Cur_Tree_Dir = %A_LoopField% ; вносим текущую папку в дерево
            Else ; если НЕ первая итерация цикла, то...
                Cur_Tree_Dir = %Cur_Tree_Dir%\%A_LoopField% ; добавляем текущую папку в дерево
            Target_Dir = %Mon_App_Path%%Sub_Dir%\%Cur_Tree_Dir% ; составляем имя целевой папки
        }
        IfNotExist, %Target_Dir% ; если целевая папка не существует, то...
            FileCreateDir, %Target_Dir% ; создать целевую папку

        ; копируем исходные файлы в целевую папку
        FileCopy, %Cur_Dir%\%Cur_File%, %Target_Dir% ; скопировать исходный файл в целевую папку
        If ErrorLevel = 0 ; если файл скопирован успешно, то...
        {
            If Log_Success = ; если лог успешных операций пуст, то...
                Log_Success = Сохранены следующие файлы:`n%Separator%`n
                ; вносим заголовок в переменную для лога успешных операций
            Log_Success = %Log_Success%%Cur_Dir%\%Cur_File%`n
            ; добавляем файл в переменную для лога успешных операций
        }
        Else ; если есть ошибки копирования, то...
        {
            If Log_Error_Copy = ; если лог ошибок копирования пуст, то...
                Log_Error_Copy = Следующие файлы существуют, но сохранить их не удалось:`n%Separator%`n
                ; вносим заголовок в переменную для лога ошибок копирования
            Log_Error_Copy = %Log_Error_Copy%%Cur_Dir%\%Cur_File%`n
            ; добавляем файл в переменную для лога ошибок копирования
        }
    }
}

; Удаляем все пустые подпапки в созданной папке сохранений
; (пустые папки могут появиться, если к-либо файлы сохранить не удалось)
DelEmpty( Mon_App_Path ) ; вызываем функцию удаления пустых подпапок (см. строку ниже)
DelEmpty( dir ) ; функция удаления пустых подпапок
{
    BatchLines_Before := A_BatchLines ; запоминаем текущие настройки скорости выполнения скрипта
    SetBatchLines, -1 ; устанавливаем max скорость для скрипта
    Global Mon_App_Path ; объявляем переменную глобальной (чтобы использовать внутри функции)
    Loop %dir%\*.*, 2 ; обрабатываем текущую папку и ее подпапки
        DelEmpty( A_LoopFileFullPath ) ; берем первую же папку и вызываем из функции саму себя, чтобы дойти до самой нижней подпапки
    If dir != %Mon_App_Path% ; если текущая папка НЕ папка для сохранений, то...
        FileRemoveDir, %dir% ; удаляем её, если она пуста
    SetBatchLines, %BatchLines_Before% ; восстанавливаем скорость скрипта
}
; Удаляем сохранённый файл изменений
FileDelete, %Changes_File_Path%

; СОХРАНЯЕМ ОТСЛЕЖЕННЫЕ ИЗМЕНЕНИЯ В РЕЕСТРЕ
; -----------------------------------------
; Проверяем нужно ли сохранять реестр
If Reg_Empty = 1 ; если в логе отсутствуют изменения реестра (выяснили выше), то...
    Goto Log_Write ; перейти сразу к записи лога

; Выбрать пункт меню "Извлечь -> Данные реестра"
WinMenuSelectItem, ahk_class TfrmTUMain.UnicodeClass,, 1&, 8&, 5& ; (!!!) выбрать пункт меню Файл -> Извлечь -> Данные реестра
DetectHiddenWindows, Off ; НЕ искать в скрытых окнах...
WinWait, ahk_class TdlgExportRegistry.UnicodeClass ; (!!!) подождать окно сохранения реестра
If ( Del_Reg_Check = True ) ; если в настройках пользователя указано ставить галочку на "удаленные ключи" при сохранении файла реестра
    Control, Check,, TTntCheckBox.UnicodeClass3 ; (!!!) поставить галочку на "удаленные ключи"

Loop ; убеждаемся, что подтверждение принято
{
    ControlSend, TTntButton.UnicodeClass2, {Space} ; (!!!) нажать ОК
    Sleep, 10*A_index ; увеличивающаяся пауза, чтобы окно успевало среагировать
    IfWinNotExist ; если окно сохранения реестра уже НЕ существует, то...
        Break ; конец цикла
}

; Сохранить файл реестра для отслеженного приложения
; Сохраняем сначала во временную папку, затем копируем в целевую. Финт, чтобы TU не блокировал целевую папку от удаления.
WinWait, ahk_class #32770 ahk_pid %TU_PID% ; ждем окно "Сохранить как", назначаем его последним найденным (сейчас понадобится)
IfExist, %A_Temp%\%Mon_App_Name%.reg ; если файл реестра уже существует
    FileDelete, %A_Temp%\%Mon_App_Name%.reg ; удалить его (чтобы не подтверждать запрос на перезапись)
Loop ; убеждаемся, что путь к сохраняемому файлу вставлен правильно
{
    ControlSetText, Edit1, %A_Temp%\%Mon_App_Name%.reg ; вставить путь к сохраняемому файлу реестра
    Sleep, 10*A_index ; увеличивающаяся пауза, чтобы путь успел вставиться
    ControlGetText, Changes_Reg_Path, Edit1 ; получить имя (и путь) сохраняемого файла реестра
    If Changes_Reg_Path = %A_Temp%\%Mon_App_Name%.reg ; если имя (и путь) вставились нормально, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что подтверждение принято
{
    ControlSend, Button2, {Space} ; нажать ОК
    Sleep, 10*A_index ; увеличивающаяся пауза, чтобы окно успевало среагировать
    IfWinNotExist ; если окно сохранения файла реестра уже НЕ существует, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что файл реестра создан
{
    IfExist, %Changes_Reg_Path% ; если файл существует, то...
        Break ; конец цикла
}

Loop ; убеждаемся, что сохранение файла изменений завершено
{
    FileGetSize, File_Sise, %Changes_Reg_Path% ; получаем размер файла реестра
    If File_Sise_Bak = %File_Sise% ; если текущий размер файла равен предыдущему размеру, то...
        Break ; конец цикла
    Sleep, 50 ; небольшая пауза
    File_Sise_Bak = %File_Sise% ; запоминаем предыдущий размер файла изменений
}
FileMove,  %Changes_Reg_Path%, %Mon_App_Path%, 1 ; перемещаем файл реестра из временной папки в целевую
;~ FileCopy, %Changes_Reg_Path%, %Mon_App_Path%, 1 ; копируем файл реестра из временной папки в целевую
; Готовим строчку для записи в лог
Log_Reg = Сохранен файл реестра:`n%Separator%`n%Mon_App_Name%.reg ; записать его сохранение в лог


; ПИШЕМ ЛОГ ОПЕРАЦИЙ
Log_Write: ; сюда будет переход, если реестр сохранять не нужно
If ( Log_Сorrection != "" OR Log_Error_Copy != "" OR Log_Success != "" OR Log_Error_Exists != "" OR Log_Reg != "" ) ; если логи не пусты
{
    FileAppend, Имя сохраняемого приложения: %Mon_App_Name%`n, %Mon_App_Path%\%Log_Name% ; заголовок в общем логе
    FileAppend, %Cur_Details%`n, %Mon_App_Path%\%Log_Name% ; текущий вид подробностей - в общий лог
    If Selection_Log ; если были сохранены только выделенные пункты (соответствующая строка НЕ пуста), то...
        FileAppend, %Selection_Log%`n, %Mon_App_Path%\%Log_Name% ; записать это в общий лог
    FileAppend, *******************************************`n`n, %Mon_App_Path%\%Log_Name% ; закрытие шапки в общем логе
}
If Log_Сorrection ; если лог исправления пути НЕ пуст, то...
    FileAppend, %Log_Сorrection%`n, %Mon_App_Path%\%Log_Name% ; записать лог исправления пути в общий лог
If Log_Error_Copy ; если лог ошибок копирования НЕ пуст, то...
    FileAppend, %Log_Error_Copy%`n, %Mon_App_Path%\%Log_Name% ; записать лог ошибок копирования в общий лог
If Log_Success ; если лог успешных операций НЕ пуст, то...
    FileAppend, %Log_Success%`n, %Mon_App_Path%\%Log_Name% ; записать лог успешных операций в общий лог
If Log_Error_Exists ; если лог ошибок существования файлов НЕ пуст, то...
    FileAppend, %Log_Error_Exists%`n, %Mon_App_Path%\%Log_Name% ; записать лог ошибок существования в общий лог
If Log_Reg ; если лог сохранения реестра НЕ пуст, то...
    FileAppend, %Log_Reg%`n, %Mon_App_Path%\%Log_Name% ; записать лог сохранения реестра в общий лог

SplashImage, Off ; удаляем сплэш

; Готовим дополнительную инфу для сообщений
If OnlySelection ; если сохранялось только выделенное, то...
    ExtInfo = Сохранялись только выделенные пункты ; дополнительная информация
StringMid, Cur_Details, Cur_Details, InStr( Cur_Details, ":" ) + 2 ; вырезаем текущий вид (без слова Вид/View)
If ( Cur_Details != nDetails ) ; если вид не "Показать все", то...
{
    If ExtInfo ; если уже содержит текст
        ExtInfo = %ExtInfo%`nи текущий вид подробностей изменений: "%Cur_Details%" ; добавить текст
    Else
        ExtInfo = Текущий вид подробностей изменений: "%Cur_Details%" ; назначить текст
}

IfNotExist, %Mon_App_Path%\%Log_Name% ; если лог НЕ существует (например, в логе TU были только удаленные файлы)
{
    If ExtInfo ; если есть расширенная информация
        Text =
        ( LTrim
            Ничего не найдено для сохранения!
            Приложение "%Mon_App_Name%" НЕ СОХРАНЕНО.

            Возможно, это потому, что:
            %ExtInfo%
        )
    Else ; если нет расширенной информации
        Text =
        ( LTrim
            Ничего не найдено для сохранения!
            Приложение "%Mon_App_Name%" НЕ СОХРАНЕНО.
        )
    My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text ) ; вызываем функцию отображения MsgBox'а с текстом по центру
    FileRemoveDir, %Mon_App_Path%, 1 ; удалить созданную папку для изменений со всем содержимым
    ExitApp ; конец скрипта
}

; Показываем финальное сообщение
If ExtInfo ; если есть расширенная информация
    Text =
    ( LTrim
        Сохранение успешно завершено!

        Приложение сохранено в:
        "%Mon_App_Path%"

        %ExtInfo%

        Показать лог операций?
    )
Else ; если нет расширенной информации
    Text =
    ( LTrim
        Сохранение успешно завершено!

        Приложение сохранено в:
        "%Mon_App_Path%"

        Показать лог операций?
    )

Button := My_MsgBox( "SaveApp4TU -- by Androgen Belkin©", Text, 1 ) ; вызываем функцию отображения MsgBox'а с текстом по центру
If Button = OK ; если ОК, то...
    Run, %Mon_App_Path%\%Log_Name% ; показать лог
ExitApp ; конец скрипта

;-------------------------------------------------------------------------------
My_MsgBox( Caption, Text, MsgBoxId = 0, Default = 1 ) ; функция отображения MsgBox'а с текстом по центру
{
    ; в качестве параметра функция принимает переменные, содержащие заголовок, текст, индекс MsgBox'а, номер кнопки по умолчанию
    ; функция возвращает текст нажатой кнопки (на случай, если это представляет интерес)
    /*
    Доступные Индексы MsgBox'а
    OK                0
    OK/Cancel        1
    Yes/No/Cancel    3
    Yes/No            4
    */
    ; Предварительные приготовления
    BatchLines_Before := A_BatchLines ; запоминаем текущие настройки скорости выполнения скрипта
    SetBatchLines, -1 ; указываем максимальную скорость для скрипта
    B_Width = 75 ; ширина кнопок
    B_Height = 23 ; высота кнопок
    B_Space = 7 ; расстояние между кнопками
    Text = %Text%`n ; добавляем к тексту перевод строки

    ; Создаем GUI
    Gui, +LastFound -MinimizeBox ; делаем окно GUI последним найденным, удаляем дополнительные кнопки из заголовка окна
    Gui, Margin, 11, 9 ; задаем отступы разметки для GUI
    Gui, Font,, Tahoma ; задаем шрифт GUI
    Gui, Add, Text, Center, %Text% ; добавляем текст (со свойством "по центру")

    ; Добавляем нужные кнопки в GUI
    If MsgBoxId = 0
        Gui, Add, Button, w%B_Width% h%B_Height% gButton, OK ; добавляем кнопку "ОК" с заданными размерами
    If MsgBoxId = 1
    {
        Gui, Add, Button, w%B_Width% h%B_Height% gButton, OK ; добавляем кнопку "ОК" с заданными размерами
        Gui, Add, Button, yp wp hp gButton, Отмена ; добавляем кнопку "Отмена" (y-координата, ширина и высота как у предыдущей)
    }
    If MsgBoxId = 3
    {
        Gui, Add, Button, w%B_Width% h%B_Height% gButton, Да ; добавляем кнопку "Да" с заданными размерами
        Gui, Add, Button, yp wp hp gButton, Нет ; добавляем кнопку "Нет" (y-координата, ширина и высота как у предыдущей)
        Gui, Add, Button, yp wp hp gButton, Отмена ; добавляем кнопку "Отмена" (y-координата, ширина и высота как у предыдущей)
    }
    If MsgBoxId = 4
    {
        Gui, Add, Button, w%B_Width% h%B_Height% gButton, Да ; добавляем кнопку "Да" с заданными размерами
        Gui, Add, Button, yp wp hp gButton, Нет ; добавляем кнопку "Нет" (y-координата, ширина и высота как у предыдущей)
    }
    Gui, Show, Hide, %Caption%  ; отобразить окно, но в скрытом виде (сейчас начнем его обрабатывать)
    WinGetPos,,, Gui_Width ; получить ширину окна GUI

    ; Распределяем кнопки (и текст, если нужно) по ширине окна
    If MsgBoxId = 0 ; если кнопка ОДНА
        GuiControl, Move, Button1, % "x" (Gui_Width - B_Width)/2 - 2 ; поместить кнопку "ОК" по центру ширины
    If ( MsgBoxId = 1 OR MsgBoxId = 4 ) ; если кнопок ДВЕ
    {
        If ( Gui_Width < B_Width*2 + B_Space ) ; если кнопки не помещаются в окно, то...
        {
            Gui_Width := B_Width*2 + B_Space + 28 ; задаем новую ширину GUI
            WinMove,,,,, %Gui_Width% ; раздвигаем окно до нужной ширины
            ControlGetPos,,, T_Width,, Static1 ; получаем ширину контрола текста
            GuiControl, Move, Static1, % "x" (Gui_Width - T_Width)/2 ; поместить контрол текста по центру ширины
        }
        x1 := (Gui_Width - B_Space - B_Width*2)/2 - 3 ; вычисляем координату x для кнопки 1
        x2 := x1 + B_Width + B_Space ; вычисляем координату x для кнопки 2
        GuiControl, Move, Button1, x%x1% ; перемещаем кнопку "ОК" (или "Да")
        GuiControl, Move, Button2, x%x2% ; перемещаем кнопку "Отмена" (или "Нет")
    }
    If MsgBoxId = 3 ; если кнопок ТРИ
    {
        If ( Gui_Width < B_Width*3 + B_Space*2 ) ; если кнопки не помещаются в окно, то...
        {
            Gui_Width := B_Width*3 + B_Space*2 + 28 ; задаем новую ширину GUI
            WinMove,,,,, %Gui_Width% ; раздвигаем окно до нужной ширины
            ControlGetPos,,, T_Width,, Static1 ; получаем ширину контрола текста
            GuiControl, Move, Static1, % "x" (Gui_Width - T_Width)/2 ; поместить контрол текста по центру ширины
        }
        x1 := (Gui_Width - B_Space*2 - B_Width*3)/2 - 3 ; вычисляем координату x для кнопки 1
        x2 := x1 + B_Width + B_Space ; вычисляем координату x для кнопки 2
        x3 := x2 + B_Width + B_Space ; вычисляем координату x для кнопки 3
        GuiControl, Move, Button1, x%x1% ; перемещаем кнопку "Да"
        GuiControl, Move, Button2, x%x2% ; перемещаем кнопку "Нет"
        GuiControl, Move, Button3, x%x3% ; перемещаем кнопку "Отмена"
    }

    ; Назначаем кнопку по умолчанию
    If Default = 1
        GuiControl, +Default, Button1
    If Default = 2
        GuiControl, +Default, Button2
    If Default = 3
        GuiControl, +Default, Button3

    ; Показываем окно MsgBox'а
    Gui, Show,, %Caption% ; показываем окно (ранее скрытое)
    ControlFocus, Button%Default% ; ставим фокус на кнопку по умолчанию
    ControlGetPos, bX, bY, bWidth, bHeight, Button%Default% ; получаем положение кнопки по умолчанию
    MouseMove, bX+bWidth/2, bY+bHeight/2, 0 ; перемещаем мышь на кнопку по умолчанию
    Pause, On ; ждем действий юзера

    SetBatchLines, %BatchLines_Before% ; восстанавливаем скорость скрипта
    Return, ButtonText ; возвращаем из функции текст нажатой кнопки

    ; подпрограммы Gui
    GuiClose:  ; при нажатии кнопки закрытия окна
    GuiEscape: ; при нажатии кнопки ESC
    Button:    ; при нажатии любой кнопки
    ButtonText = %A_GuiControl% ; запоминаем, текст нажатой кнопки
    Gui Destroy ; убиваем GUI
    Pause, Off ; снимаем с паузы
    Return ; возвращаемся из подпрограммы (продолжаем с места паузы)
} ; конец функции
;-------------------------------------------------------------------------------


;-------------------------------------------------------------------------------
ExpandEnvVars( ppath ) ; функция по разворачиванию переменных окружения в их содержание
{
    VarSetCapacity( Dest, 2000 ) ; обеспечиваем достаточную вместимость переменной
    DllCall( "ExpandEnvironmentStrings", Str, ppath, Str, Dest, Int, 1998 ) ; получаем содержание п. окруж-я
    Return, Dest ; возвращаем содержание переменной окружения
}
;-------------------------------------------------------------------------------

Этот скрипт можно забрать архивчиком.

Post's attachments

SaveApp4TUv1.4.zip 10.05 kb, 678 downloads since 2006-12-02 

You don't have the permssions to download the attachments of this post.
Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).

2

Re: AutoHotkey и Total Uninstall

Обновил описание и скрипт до v1.4
В версии скрипта 1.4 (по сравнению с версией 1.3):
Добавил поддержку командной строки для откомпилированного скрипта.
Исправил некоторые недоработки.
Добавил разные полезные сообщения.

Крокодил, крокожу и буду крокодить! (Твёрдое обещание нетрезвого кодера).