Простой сценарий Perl для очистки кэша
. Если возникают подобные проблемы, можно задействовать сценарий на языке Perl CleanCache.p», приведенный в листинге 8, для удаления большинства часто используемых кэшированных файлов в системе Windows и списков MRU. В этой статье мы будем исходить из того, что читатели имеют элементарное представление о языке Perl; более подробно c языком Perl можно ознакомиться на сайте http://www.roth.net/perl.
Кэш Internet Explorer
Когда мы используем Microsoft Internet Explorer (IE) для навигации по Web-ресурсам, он сохраняет Web-страницы, графику, cookies-файлы, сценарии Java и другую загружаемую информацию в область на жестком диске, обозначенную как кэш IE. Последующие обращения к сохраненным подобным образом объектам не требуют повторного соединения с Web-сервером. Таким образом, мы экономим время, которое затрачивается на установку соединения и загрузку, особенно при работе с объемными файлами. Однако правом использования кэша обладает не только IE. Точнее, данная область принадлежит библиотеке WinInet.dll. Эта библиотека предоставляет доступ по протоколам Web и FTP, а также функции кэширования. IE и другие приложения, такие как Microsoft Outlook Express и Windows Media Player (WMP), используют кэш библиотеки WinInet, поэтому кэш может содержать файлы с полезной информацией. Для просмотра списка содержимого кэша следует открыть приложение Internet Options в Control Panel, перейти на вкладку General и нажать кнопку Settings (под надписью «Temporary Internet files»). Затем нужно нажать кнопку View Files в окне диалогового окна Settings.
Кэш библиотеки WinInet состоит из двух компонентов: базы данных и контейнера. Контейнер - это каталог или набор каталогов, в которых размещаются кэшированные файлы. База данных содержит записи, которые отображают каждый из URL-адресов кэшированных элементов на диске. Когда вы загружаете файл с Web-ресурса, IE сохраняет файл в контейнере и добавляет запись-указатель в базу данных. Обычно контейнер кэша находится по адресу %systemroot%documents and settingsusernamelocal settings emporary internet files, где «username» - имя пользователя.
Очистить кэш легко - нужно лишь удалить кэшированные файлы из контейнера на жестком диске (обычно папка Temporary Internet Files) и удалить записи из базы данных. Однако записи базы данных кэша могут указывать не только на контейнер, но и на любой файл, в любом каталоге, на любом диске локальной системы или даже на удаленной системе. Поэтому просто удалив все файлы из папки Temporary Internet Files, вы можете пропустить некоторые кэшированные файлы.
Кроме того, кэш библиотеки WinInet может хранить файлы как по отдельности, так и группами. Следовательно, любой сценарий, чистящий кэш, должен запрашивать из базы данных кэша как отдельные файлы, так и группы. Например, WMP может кэшировать потоковые данные с потокового сервера или постепенно загружать данные с Web-сервера. WMP хранит эти файлы группой в кэше библиотеки WinInet. Следовательно, если сценарий ищет и удаляет только файлы, он может пропустить группу кэшированных файлов WMP. Аналогично, чтобы удалить только те файлы, которые принадлежат WMP, вам необходим сценарий, который будет распознавать группу кэшированных файлов.
Более того, удаление кэшированных файлов не приводит к очистке базы данных кэша, для которой приложение (например, IE) или сценарий могут продолжать создание запросов. Для полной очистки кэша требуется перебрать все записи в базе данных, после чего удалить каждый кэшированный файл и связанную с ним запись из базы данных. Библиотека WinInet предлагает функцию для перебора всех записей кэша и функцию DeleteUrlCacheEntry(), которая удаляет и сам кэшированный файл, и запись в базе данных кэша. Та же процедура необходима для удаления кэшированных данных cookies-файлов и информации по использованию браузера. Так как множество процессов могут использовать кэш библиотеки WinInet одновременно, файлы базы данных обычно находятся в открытом состоянии. Поэтому сценарий не может удалить текущие файлы базы данных. Вместо этого он должен просто удалить информацию из файлов базы данных.
Временные файлы
Приложения генерируют все виды временных файлов. Хотя некоторые приложения автоматически удаляют свои временные файлы при завершении работы, многие программы оставляют эти файлы. Пакет Microsoft Word печально известен тем, что создает временные файлы и никогда не удаляет их.
Со временем накопленные временные файлы могут занять значительный объем дискового пространства, начиная от нескольких килобайт и заканчивая сотнями мегабайт. Еще хуже то, что большинство этих файлов имеет небольшой размер, и их хранение может привести к фрагментации более крупных файлов. Сильная фрагментация файла может снизить общую производительность системы (особенно если фрагментируется файл подкачки Windows) и послужить причиной чрезмерной загрузки жесткого диска. Поэтому важно периодически очищать каталоги временных файлов.
Временные файлы хранятся в двух местах: в каталоге Temporary Internet и в каталоге Temp. Как я пояснил выше, библиотека WinInet использует каталог Temporary Internet Files для хранения кэшированных файлов из Internet. Каталог Temp хранит временные файлы, создаваемые приложениями и операционной системой. Переменная окружения temp описывает точное местоположение папки Temp. Вы можете просмотреть эту переменную, выполнив команду Set в командной строке. В языке Perl можно задействовать список %ENV с ключом TEMP - ENV{'TEMP'} - для получения пути к папке Temp. Для просмотра списка переменных окружения нужно щелкнуть правой кнопкой мыши на ярлыке My Computer и выбрать пункт Properties. Затем следует перейти на вкладку Advanced и нажать кнопку Environment Variables. Удаление временных файлов сводится к удалению всех файлов из каталога Temp; удаление файлов из папки Temporary Internet Files, как я объяснил выше, является чуть более сложной задачей.
Другие данные
Несколько других типов данных могут создавать хаотичность в дисковом пространстве. Сценарий CleanCache.pl можно использовать для удаления следующих данных (часто путем простого удаления значений определенных подключей реестра):
Данные форм IE. IE может запоминать данные, которые пользователь вводит в Web-формы. Эта способность облегчает пользователю процесс заполнения форм. Данная запоминаемая информация располагается в подразделе реестра HKEY_CURRENT_USERSoftwareMicrosoftInternet ExplorerIntelliFormsSPW.
Список введенных адресов URL. Когда вы вводите адрес URL в строку адреса, IE сохраняет этот адрес в списке введенных адресов, из которого в дальнейшем можно выбирать ранее введенные адреса. Список введенных адресов располагается в разделе реестра HKEY_CURRENT_USERSoftwareMicrosoftInternet ExplorerTypedURLs.
Список MRU. Выбор в меню Start пункта Run открывает текстовое поле, в которое можно ввести команду или путь для запуска приложения. Это текстовое поле имеет ниспадающий список путей, введенных ранее. Данный список известен как список MRU. Список MRU удобен, так как вам не требуется целиком запоминать команду или путь, использовавшийся ранее для запуска приложения. Однако любой, кто получит доступ к вашей учетной записи или разделу реестра, в котором содержатся настройки списка MRU, также сможет получить эту информацию. Список MRU располагается в подразделе реестра HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExplorerRunMRU.
Список Recent File. Система Windows содержит список Recent File (также известный как список My Documents), который содержит все недавно загруженные файлы, аналогично тому, как список MRU отображает список недавно запущенных приложений. «Троянский конь» или вирус могут обратиться к списку Recent File для получения данных о рабочих привычках пользователя. На самом деле список представляет собой папку на жестком диске. Обычно это папка %systemdrive%documents and settingsuser- name ecent. Каталог содержит ярлыки (файлы .lnk), которые указывают на реальные файлы. Папка может содержать сотни записей, но система Windows отображает только короткий список файлов, доступ к которым осуществлялся недавно.
Корзины. Когда вы используете службу Windows Explorer для удаления файлов, система Windows сохраняет файлы в «Корзине», чтобы можно было при необходимости восстановить их. Обычно своя корзина существует на каждом диске, а общая корзина располагается на рабочем столе. Корзины могут со временем занимать довольно много места, и их следует периодически очищать.
Сценарий
Сценарий CleanCache.pl удаляет различные типы кэшируемых данных. Этот сценарий полезен для очистки пользовательской учетной записи на системе, чтобы другие пользователи не смогли узнать, к каким данным обращался тот или иной пользователь. Сценарий, на первый взгляд, может показаться сложным, но на самом деле он достаточно прост. Давайте рассмотрим наиболее важные части сценария.
Код, приведенный в Листинге 1, отображает работу с настройками. В этом разделе сценарий присваивает значения различным переменным. Я обнаружил большинство этих значений в документации Microsoft Developer Network (MSDN); остальные были получены методом проб и ошибок. Эта часть сценария находится в блоке "без ограничений", в котором отключено ограничение языка Perl, так что большинство из определенных переменных, лексически не выделенных словом «my», не вызовут появления предупреждений при работе сценария.
Код, приведенный в Листинге 2, отображает загрузку различных библиотек для получения доступа к необходимым функциям. Lfyysq сценарий будет использовать модуль Win32::API::Prototype для вызова этих функций, чтобы выполнить определенные задачи, такие как очистка «Корзины» (функция SHQueryRecycleBin()) и удаление записи из кэша (функция DeleteUrlCacheEntry()). Код, приведенный в Листинге 3, отображает вызов различных процедур для удаления файлов, значений подключей реестра и вызова функций операционной системы.
Процедура DeleteUrlCacheGroups перебирает группы кэша, которые находятся в базе данных кэша библиотеки WinInet. Далее сценарий получает информацию (например, используемый группой объем дискового пространства) по каждой группе в кэше, как показывает код в Листинге 4. Нужно иметь в виду, что в коде блока A Листинга 4 для присвоения значений списку используется небольшая хитрость. Этот прием работает, так как порядок массива значений хорошо известен. Однако данный блок может вызвать ошибки, если для него не отменено ограничение. Код в Листинге 5 отображает удаление файлов и очистку кэша (как я объяснил выше, предполагается, что сценарий создается именно для этого).
Процедура DeleteUrlCacheFiles делает практически то же самое, что и процедура DeleteUrlCacheGroups, но она сама по себе сложнее и вместо групп из кэша удаляет отдельные записи. Каждая запись в кэше содержит информацию и атрибуты, такие как дата и время кэширования файла, адрес URL и время истечения срока хранения файла. Каждая запись имеет свой размер, поэтому сценарий сначала адресует буфер размером 1 Кбайт с помощью переменной $pCacheInfo. Код в Листинге 6 отображает начало процесса перебора, но при этом он должен определять, достаточно ли выделенного буфера для хранения данных из записей. Если объем буфера недостаточен, сценарий производит повторное размещение буфера в памяти. Сценарий использует эту стратегию каждый раз при обращении к записи из кэша. Код в Листинге 7 демонстрирует использование технологии из Листинга 4 для распаковки данных из записи в список %Cache. После извлечения данных кэша сценарий определяет тип записи (то есть данные cookie-файлов, данные по использованным URL-адресам или кэшированный файл).
Процедура CleanDirectory осуществляет вызов системной функции SHGetFolderpath(). Используя значение идентификатора класса (CLSID), функция возвращает полный путь к специализированной каталога (например, к папкам My Documents, Recent File или Temporary Internet Files). Дополнительную информацию о том, как сценарий получает пути, можно найти во врезке «Определение путей». Функция возвращает строку в кодировке Unicode, и код удаляет все символы NULL в строке. Выполнение этой операции может оказаться затруднительным для путей, в которых на самом деле используются символы Unicode. Затем процедура вызывает функцию CleanDirectoryAndFiles() для удаления файлов из данного каталога. Если файлы удалить невозможно, сценарий пытается переименовать файл, чтобы его было легко обнаружить при следующей очистке.
Процедура ClearRegistryKey() удаляет все значения из определенного подраздела реестра. Сценарий вызывает эту процедуру несколько раз для очистки списка MRU, данных формIE и списка введенных адресовIE.
Процедура EmptyRecycleBin запрашивает статистику по «Корзине» на данной системе (например, какое количество файлов хранится в корзине) и очищает «Корзины». Когда сценарий вызывает системную функцию SHEmptyRecycleBin() для очистки «Корзин», функция выставляет несколько флагов, чтобы предотвратить появление окна подтверждения. Флаги также подавляют любое звуковое оповещение об очистке корзины и появление диалогового окна, отображающего степень очистки «Корзины».
Работа со сценарием
Сценарий использует модуль Win32::API::Prototype, который можно установить, используя службу Perl Package Manager (PPM). Для этого в командной строке нужно ввести:
ppm install http://www.roth.net/perl/ packages/win32-api-prototype.ppd
Этот модуль использует расширение Win32-API, которое входит в стандартный набора пакета Perl от компании ActiveState. Вы также можете получить расширение по адресу http://dada.perl.it/#api и задействовать службу PPM для его установки с помощью команды:
ppm install win32-api
Когда вы запускаете сценарий без указания параметров, он собирает информацию об объеме дискового пространства, используемого кэшем, количестве кэшированных файлов, количестве элементов в списке MRU и так далее. В результате сценарий отобразит итоговые значения, но не удалит ни одного элемента и не очистит кэш. Если вы запускаете сценарий с параметром /v, результаты будут отображены подробно. Если вы запускаете сценарий с параметром /s, он будет работать в скрытом режиме, не выдавая никакой информации. Данный параметр перекрывает параметр /v. Если вы запускаете сценарий с параметром /d, сценарий удалит кэшированные файлы, очистит базу данных кэша и удалит другие упомянутые выше типы данных.
Успех использования сценария зависит от количества приложений, имеющих доступ к базе данных кэша. Если другой процесс использует библиотеку WinInet, база данных кэша может быть очищена не полностью. Поэтому, необходимо закрыть все сопроцессы IE, включая процессы, внедренные в другие приложения, такие как WMP Media Guide,перед запуском сценария. Кроме того, некоторые неграмотно написанные приложения используют библиотеку WinInet и должны быть остановлены перед запуском сценария.
Так же можете иметь в виду, что даже после запуска сценария с параметром /d, список MRUWindows Explorer может казаться не очищенным. Windows Explorer загружает список MRU в память и не обязательно производит повторную загрузку с диска. Чтобы отобразить очищенный список MRU, следует остановить и перезапустить Windows Explorer, завершив сеанс и заново зарегистрировавшись в системе.
Определение путей
Каждый пользователь имеет выделенный каталог со своим профилем. Этот профиль содержит информацию об учетной записи, индивидуальную для пользователя (например, Internet-закладки, раздел реестра, список Recent File, список My Documents, настройки программ). Обычно каталог с профилем является подкаталогом папки C:documents and settings и идентифицируется по имени пользователя. Например, пользователь с именем Ralph будет иметь профиль в папке C:documents and settings alph. Однако предположение, что недавно загруженные пользователем документы будут храниться в папке C:documents and settings alph ecent, может оказаться неверным. Например, если пользователь имеет перемещаемый профиль, каталог с профилем может иметь адрес profile_serverusers alph.
В системе Windows есть специальная функция, SHGetFolderPath(), предназначенная для определения пути к папкам, таким как Temporary Internet Files и Recent File List. Когда вы вызываете эту функцию, необходимо указать значение папки, называемое идентификатором класса (CLSID), которое идентифицирует требуемый каталог. Дополнительную информацию о значениях CLSID можно найти по адресу http://msdn.microsoft.com/library/en-us/shellcc/platform/shell/reference/enums/csidl.asp.
ЛИСТИНГ 1. Присвоение значений переменным
$CACHEGROUP_SEARCH_ALL = 0x00000000; $CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x00000002; $CACHEGROUP_ATTRIBUTE_GET_ALL = 0xffffffff; $ERROR_NO_MORE_FILES = 18; $ERROR_NO_MORE_ITEMS = 259; $ERROR_INSUFFICIENT_BUFFER = 122; $GROUPNAME_MAX_LENGTH = 120; $GROUP_OWNER_STORAGE_SIZE = 4; $CACHE_GROUP_STRUCT = "L5L$GROUP_OWNER_STORAGE_SIZE" . "A$GROUPNAME_MAX_LENGTH"; $KILOBYTE = 1024; $MEGABYTE = 1024 * $KILOBYTE; $GIGABYTE = 1024 * $MEGABYTE; $TERABYTE = 1024 * $GIGABYTE; $SHERB_NOCONFIRMATION = 0x00000001; $SHERB_NOPROGRESSUI = 0x00000002; $SHERB_NOSOUND= 0x00000004; $QUERY_RECYCLE_BIN_STRUCT = "LL2L2"; $SHGFP_TYPE_CURRENT = 0; $CSIDL_RECENT= 0x0008; $CSIDL_INTERNET_CACHE = 0x0020; $CSIDL_HISTORY= 0x0022; $DELETED_FILE_RENAME_SUFFIX = ".OLD.DELETE.ME"; $SHERB_NOCONFIRMATION = 0x00000001; $SHERB_NOPROGRESSUI = 0x00000002; $SHERB_NOSOUND= 0x00000004; $QUERY_RECYCLE_BIN_STRUCT = "LL2L2"; %REGISTRY_KEY = ( 'typed_url_list' => "SoftwareMicrosoftInternet ExplorerTypedURLs", 'intelliforms_data' => "SoftwareMicrosoftInternet ExplorerIntelliFormsSPW", 'explorer_run_mru' => "SoftwareMicrosoftWindows CurrentVersionExplorerRunMRU", ); %CACHE_ENTRY = ( COOKIE => 0x00100000, NORMAL => 0x00000001, STICKY => 0x00000004, TRACK_OFFLINE => 0x00000010, TRACK_ONLINE => 0x00000020, URLHISTORY => 0x00200000, SPARSE => 0x00010000 ); %Time = (); ########################################################################## # # Set up the %Config global hash with default values and # configure the %Config hash with user settings passed in. # %Config = ( filter => $CACHE_ENTRY{COOKIE} | $CACHE_ENTRY{STICKY} | $CACHE_ENTRY{URLHISTORY} | $CACHE_ENTRY{NORMAL}, );
Листинг 2. Загрузка библиотек
ApiLink( "wininet", "HANDLE FindFirstUrlCacheEntry( LPCTSTR lpszUrlSearchPattern, PVOID lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize )" ) || die; ApiLink( "wininet", "BOOL FindNextUrlCacheEntry( HANDLE hEnumHandle, PVOID lpNextCacheEntryInfo, LPWORD lpdwNextCacheEntryInfoBufferSize )" ) || die; ApiLink( "wininet", "HANDLE FindFirstUrlCacheGroup( DWORD dwFlags, DWORD dwFilter, LPVOID lpSearchCondition, DWORD dwSearchCondition, LPVOID lpGroupId, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL FindNextUrlCacheGroup( HANDLE hFind, PVOID lpGroupId, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL DeleteUrlCacheGroup( DWORD GroupIdLo, DWORD GroupIdHi, DWORD dwFlags, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL GetUrlCacheGroupAttribute( DWORD GroupIdLo, DWORD GroupIdHi, DWORD dwFlags, DWORD dwAttributes, PVOID lpGroupInfo, LPDWORD lpdwGroupInfo, LPVOID lpReserved )" ) || die; ApiLink( "shell32", "HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath )" ) || die; ApiLink( "wininet", "BOOL FindCloseUrlCache ( HANDLE hEnumHandle )" ) || die; ApiLink( "wininet", "BOOL DeleteUrlCacheEntry (LPCTSTR lpszUrlName)" ) || die; ApiLink( "shell32.dll", "HRESULT SHEmptyRecycleBin( HWND hwnd, LPSTR pszRootPath, DWORD dwFlags )" ) || die; ApiLink( "shell32.dll", "HRESULT SHQueryRecycleBin( LPSTR pszRootPath, PVOID pSHQueryRBInfo )" ) || die;
Листинг 3. Вызов процедур для очистки данных
print " Deleting cache groups. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlCacheGroups(); print " Deleting cache files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlCacheFiles(); print " Deleting history files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlHistory(); print " Deleting recent files list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteRecentFileList(); print " Deleting temporary files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteTempFiles(); print " Deleting temporary Internet files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteTempInternetFiles(); print " Emptying recycle bins. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); EmptyRecycleBin(); print " Clearing IE forms data. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearFormData(); print " Clearing IE typed URL list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearTypedURLList(); print " Clearing Explorer Run Most Recent Used (MRU) list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearRunMRU();
Листинг 4. Получение информации о группах в кэше
if( 0 != GetUrlCacheGroupAttribute( $GroupHi, $GroupLo, 0, $CACHEGROUP_ATTRIBUTE_GET_ALL, $pGroupInfo, $pdwGroupInfoSize, undef ) ) { # Следующее значение присваивается для снятия ограничения и предотвращения появления предупреждений { no strict; @Group{ StructSize, Flags, Type, DiskUsageKB, DiskQuotaKB, Temp} = unpack( "L5a*", $pGroupInfo ); } @{$Group{OwnerStorage}} = unpack( "L$GROUP_OWNER_STORAGE_SIZE", $Group{Temp} ); ($Group{Name} ) = unpack( "@" x ( 4 * $GROUP_OWNER_STORAGE_SIZE ) . "x16A*", $Group{Temp} ); # Не обновляйте данные Total,если имя группы - "", и она занимает # 0 Кб на диске. Это может быть ошибкой в библиотеке WinInet. if( ! ( "" eq $Group{Name} && 0 == $Group{DiskUsageKB} ) ) { print " Group Name: $Group{Name} (" . FormatNumberPretty( $Group{DiskUsageKB} * 1024 ) . " bytes) " if( $Config{verbose} ); } }
Листинг 5. Очистка кэша
if( $Config{delete} ) { if( 0 != DeleteUrlCacheGroup( $GroupHi, $GroupLo, $CACHEGROUP_FLAG_FLUSHURL_ONDELETE, undef ) ) { # Не обновляйте данные Total, если имя группы - "", и она занимает # 0 Кб на диске. Это может быть ошибкой в библиотеке WinInet if( ! ( "" eq $Group{Name} && 0 == $Group{DiskUsageKB} ) ) { $Total{'Internet Cache Group'}->{_size} += $Group{DiskUsageKB} * 1024; $Total{'Internet Cache Group'}->{_count}++; } } }
Листинг 6. Начало перебора записей
my $hCache = FindFirstUrlCacheEntry( undef, $pCacheInfo, $pdwSize ); if( 0 == $hCache && $ERROR_INSUFFICIENT_BUFFER == Win32::GetLastError() ) { my $NewBufferSize = unpack( "L", $pdwSize ); $pCacheInfo = pack( "C$NewBufferSize", 0 ); $hCache = FindFirstUrlCacheEntry( undef, $pCacheInfo, $pdwSize ); }
Листинг 7. Распаковка данных из записи
my( $Temp, $dwHeaderSize ) = unpack( "A68L", $pCacheInfo ); my $Result; my %Cache; # Следующее значение присваивается для снятия ограничения и предотвращения появления предупреждений { no strict; @Cache{ StructSize, SourceUrl, LocalFile, CacheEntryType, UseCount, HitRate, SizeLow, SizeHigh, LastModifiedTimeLow, LastModifiedTimeHigh, ExpireTimeLow, ExpireTimeHigh, LastAccessTimeLow, LastAccessTimeHigh, LastSyncTimeLow, LastSyncTimeHigh, HeaderInfo, HeaderSize, FileExtension, Reserved, ExemptDelta } = unpack( "Lp2L5L8P" . $dwHeaderSize . "LpL2", $pCacheInfo ); } my $DeleteUrl = $Cache{SourceUrl}; my $Type = "Internet Cache"; if( $CACHE_ENTRY{COOKIE} & $Cache{CacheEntryType} ) { $Type = "Internet Cookie"; } elsif( $CACHE_ENTRY{URLHISTORY} & $Cache{CacheEntryType} ) { $Type = "Internet History"; }
Листинг 8. CleanCache.pl
########################################################################## # # CleanCache.pl # ------------- # Этот сценарий удаляет все из кэша Windows Internet, в том числе # кэшированные группы, кэшированные файлы, данные «cookies», список использованных URL-адресов, временные файлы, # временные файлы из Internet, список истории использования и список недавно загруженных файлов # # Этот сценарий требует для работы наличия расширений Win32::API и Win32::API::Prototype, # которые можно получить по адресам http://dada.perl.it/#api и # http://www.roth.net/perl/packages, соответственно. use Win32::API::Prototype; use Win32::Registry; use Getopt::Long; use strict; use vars qw( %Config %Total $VERSION %REGISTRY_KEY %Time %CACHE_ENTRY $CACHEGROUP_SEARCH_ALL $CACHEGROUP_FLAG_FLUSHURL_ONDELETE $CACHEGROUP_ATTRIBUTE_GET_ALL $ERROR_NO_MORE_FILES $ERROR_NO_MORE_ITEMS $ERROR_INSUFFICIENT_BUFFER $GROUPNAME_MAX_LENGTH $GROUP_OWNER_STORAGE_SIZE $CACHE_GROUP_STRUCT $KILOBYTE $MEGABYTE $GIGABYTE $TERABYTE $SHERB_NOCONFIRMATION $SHERB_NOPROGRESSUI $SHERB_NOSOUND $QUERY_RECYCLE_BIN_STRUCT $SHGFP_TYPE_CURRENT $CSIDL_RECENT $CSIDL_INTERNET_CACHE $CSIDL_HISTORY $DELETED_FILE_RENAME_SUFFIX $SHERB_NOCONFIRMATION $SHERB_NOPROGRESSUI $SHERB_NOSOUND $QUERY_RECYCLE_BIN_STRUCT ); ########################################################################## # # Глобальные переменные, лексически не выделяются # $VERSION = 20030806; $CACHEGROUP_SEARCH_ALL = 0x00000000; $CACHEGROUP_FLAG_FLUSHURL_ONDELETE = 0x00000002; $CACHEGROUP_ATTRIBUTE_GET_ALL = 0xffffffff; $ERROR_NO_MORE_FILES = 18; $ERROR_NO_MORE_ITEMS = 259; $ERROR_INSUFFICIENT_BUFFER = 122; $GROUPNAME_MAX_LENGTH = 120; $GROUP_OWNER_STORAGE_SIZE = 4; $CACHE_GROUP_STRUCT = "L5L$GROUP_OWNER_STORAGE_SIZE" . "A$GROUPNAME_MAX_LENGTH"; $KILOBYTE = 1024; $MEGABYTE = 1024 * $KILOBYTE; $GIGABYTE = 1024 * $MEGABYTE; $TERABYTE = 1024 * $GIGABYTE; $SHERB_NOCONFIRMATION = 0x00000001; $SHERB_NOPROGRESSUI = 0x00000002; $SHERB_NOSOUND= 0x00000004; $QUERY_RECYCLE_BIN_STRUCT = "LL2L2"; $SHGFP_TYPE_CURRENT = 0; $CSIDL_RECENT= 0x0008; $CSIDL_INTERNET_CACHE = 0x0020; $CSIDL_HISTORY= 0x0022; $DELETED_FILE_RENAME_SUFFIX = ".OLD.DELETE.ME"; $SHERB_NOCONFIRMATION = 0x00000001; $SHERB_NOPROGRESSUI = 0x00000002; $SHERB_NOSOUND= 0x00000004; $QUERY_RECYCLE_BIN_STRUCT = "LL2L2"; %REGISTRY_KEY = ( 'typed_url_list' => "SoftwareMicrosoftInternet Explorer TypedURLs", 'intelliforms_data' => "SoftwareMicrosoftInternet Explorer IntelliFormsSPW", 'explorer_run_mru' => "SoftwareMicrosoftWindows CurrentVersionExplorerRunMRU", ); %CACHE_ENTRY = ( COOKIE => 0x00100000, NORMAL => 0x00000001, STICKY => 0x00000004, TRACK_OFFLINE => 0x00000010, TRACK_ONLINE => 0x00000020, URLHISTORY => 0x00200000, SPARSE => 0x00010000 ); %Time = (); ########################################################################## # # Настройка глобального списка %Config по умолчанию и # настройка списка %Config с пользовательскими параметрами. # %Config = ( filter => $CACHE_ENTRY{COOKIE} | $CACHE_ENTRY{STICKY} | $CACHE_ENTRY{URLHISTORY} | $CACHE_ENTRY{NORMAL}, ); Configure( \%Config ); if( $Config{help} ) { Syntax(); exit; } ########################################################################## # # Загрузка двоичных библиотек. # ApiLink( "wininet", "HANDLE FindFirstUrlCacheEntry( LPCTSTR lpszUrlSearchPattern, PVOID lpFirstCacheEntryInfo, LPDWORD lpdwFirstCacheEntryInfoBufferSize )" ) || die; ApiLink( "wininet", "BOOL FindNextUrlCacheEntry( HANDLE hEnumHandle, PVOID lpNextCacheEntryInfo, LPWORD lpdwNextCacheEntryInfoBufferSize )" ) || die; ApiLink( "wininet", "HANDLE FindFirstUrlCacheGroup( DWORD dwFlags, DWORD dwFilter, LPVOID lpSearchCondition, DWORD dwSearchCondition, LPVOID lpGroupId, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL FindNextUrlCacheGroup( HANDLE hFind, PVOID lpGroupId, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL DeleteUrlCacheGroup( DWORD GroupIdLo, DWORD GroupIdHi, DWORD dwFlags, LPVOID lpReserved )" ) || die; ApiLink( "wininet", "BOOL GetUrlCacheGroupAttribute( DWORD GroupIdLo, DWORD GroupIdHi, DWORD dwFlags, DWORD dwAttributes, PVOID lpGroupInfo, LPDWORD lpdwGroupInfo, LPVOID lpReserved )" ) || die; ApiLink( "shell32", "HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath )" ) || die; ApiLink( "wininet", "BOOL FindCloseUrlCache( HANDLE hEnumHandle )" ) || die; ApiLink( "wininet", "BOOL DeleteUrlCacheEntry(LPCTSTR lpszUrlName)" ) || die; ApiLink( "shell32.dll", "HRESULT SHEmptyRecycleBin( HWND hwnd, LPSTR pszRootPath, DWORD dwFlags )" ) || die; ApiLink( "shell32.dll", "HRESULT SHQueryRecycleBin( LPSTR pszRootPath, PVOID pSHQueryRBInfo )" ) || die; ########################################################################## # # Здесь начинается код основного блока. # print " **** NOT DELETING ANY FILES **** " if( ! $Config{delete} && ! $Config{silent} ); $Time{begin} = time(); print " Deleting cache groups. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlCacheGroups(); print " Deleting cache files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlCacheFiles(); print " Deleting history files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteUrlHistory(); print " Deleting recent files list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteRecentFileList(); print " Deleting temporary files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteTempFiles(); print " Deleting temporary Internet files. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); DeleteTempInternetFiles(); print " Emptying recycle bins. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); EmptyRecycleBin(); print " Clearing IE forms data. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearFormData(); print " Clearing IE typed URL list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearTypedURLList(); print " Clearing Explorer Run Most Recent Used (MRU) list. " if( ! $Config{silent} && ( $Config{delete} || $Config{verbose} ) ); ClearRunMRU(); $Time{end} = time(); if( ! $Config{silent} ) { print " " . "=" x 40 . " "; print "Totals: "; foreach my $Type (sort keys %Total ) { my $Size = $Total{$Type}->{_size}; my $Count = $Total{$Type}->{_count}; next if( "_totals" eq $Type ); printf( " % 20s: %s item%s (%s bytes) ", $Type, FormatNumber( $Count ), ( 1 == $Count)? " ":"s", FormatNumberPretty( $Size ) ); $Total{_totals}->{_size} += $Size; $Total{_totals}->{_count} += $Count; } print " "; print " Total size: " . FormatNumberPretty( $Total{_totals}->{_size} ) . " bytes. "; print " Total count: $Total{_totals}->{_count} items. "; print " Total time taken: " . FormatNumber( $Time{end} - $Time{begin} ) . " seconds. "; print " Note: "; print "* Changes to the Windows Explorer won't show up until your next logon. "; print "* Changes to Internet Explorer won't show up until you run IE again. "; } ########################################################################## sub DeleteUrlCacheGroups { my $pGroupId = pack( "L2", 0 ); my $hGroup = FindFirstUrlCacheGroup( 0, $CACHEGROUP_SEARCH_ALL, 0, 0, $pGroupId, 0 ); while( 0 != $hGroup ) { my $Result; my( $GroupHi, $GroupLo ) = unpack( "L2", $pGroupId ); my $pdwGroupInfoSize; my $pGroupInfo = pack( $CACHE_GROUP_STRUCT, 0 ); my %Group; # Recreate structure this type setting the dwGroupSize member of this structure... $pGroupInfo = pack( $CACHE_GROUP_STRUCT, length( $pGroupInfo ), 0 ); $pdwGroupInfoSize = pack( "L", length( $pGroupInfo ) ); if( 0 != GetUrlCacheGroupAttribute( $GroupHi, $GroupLo, 0, $CACHEGROUP_ATTRIBUTE_GET_ALL, $pGroupInfo, $pdwGroupInfoSize, undef ) ) { # Следующее значение присваивается для снятия ограничения и предотвращения появления предупреждений { no strict; @Group{ StructSize, Flags, Type, DiskUsageKB, DiskQuotaKB, Temp} = unpack( "L5a*", $pGroupInfo ); } @{$Group{OwnerStorage}} = unpack( "L$GROUP_OWNER_STORAGE_SIZE", $Group{Temp} ); ($Group{Name} ) = unpack( "@" x ( 4 * $GROUP_OWNER_STORAGE_SIZE ) . "x16A*", $Group{Temp} ); # Don't update the Total stats if the group name is "" and it consumes # 0KB on disk. This might be a bug in WinInet. if( ! ( "" eq $Group{Name} && 0 == $Group{DiskUsageKB} ) ) { print " Group Name: $Group{Name} (" . FormatNumberPretty( $Group {DiskUsageKB} * 1024 ) . " bytes) " if( $Config{verbose} ); } } if( $Config{delete} ) { if( 0 != DeleteUrlCacheGroup( $GroupHi, $GroupLo, $CACHEGROUP_FLAG_FLUSHURL_ONDELETE, undef ) ) { # Don't update the Total stats if the group name is "" and it consumes # 0KB on disk. This might be a bug in WinInet. if( ! ( "" eq $Group{Name} && 0 == $Group{DiskUsageKB} ) ) { $Total{'Internet Cache Group'}->{_size} += $Group{DiskUsageKB} * 1024; $Total{'Internet Cache Group'}->{_count}++; } } } $Result = FindNextUrlCacheGroup( $hGroup, $pGroupId, undef ); if( 0 == $Result ) { # Continue enumerating until the Win32 error generated by FindNextUrlCacheGroup() # == 2 (file not found) or ERROR_NO_MORE_FILES or ERROR_NO_MORE ENTRIES my $Error = Win32::GetLastError(); $hGroup = 0 if( $Error == 2 || $Error == $ERROR_NO_MORE_ITEMS || $Error == $ERROR_NO_MORE_FILES ); } } } sub DeleteUrlCacheFiles { my $Type = shift @_; my $pCacheInfo = pack( "C1024", 0 ); my $pdwSize = pack( "L", length( $pCacheInfo ) ); my $hCache = FindFirstUrlCacheEntry( undef, $pCacheInfo, $pdwSize ); if( 0 == $hCache && $ERROR_INSUFFICIENT_BUFFER == Win32::GetLastError() ) { my $NewBufferSize = unpack( "L", $pdwSize ); $pCacheInfo = pack( "C$NewBufferSize", 0 ); $hCache = FindFirstUrlCacheEntry( undef, $pCacheInfo, $pdwSize ); } if( 0 != $hCache ) { do { my( $Temp, $dwHeaderSize ) = unpack( "A68L", $pCacheInfo ); my $Result; my %Cache; # Следующее значение присваивается для снятия ограничения и предотвращения появления предупреждений { no strict; @Cache{ StructSize, SourceUrl, LocalFile, CacheEntryType, UseCount, HitRate, SizeLow, SizeHigh, LastModifiedTimeLow, LastModifiedTimeHigh, ExpireTimeLow, ExpireTimeHigh, LastAccessTimeLow, LastAccessTimeHigh, LastSyncTimeLow, LastSyncTimeHigh, HeaderInfo, HeaderSize, FileExtension, Reserved, ExemptDelta } = unpack( "Lp2L5L8P" . $dwHeaderSize . "LpL2", $pCacheInfo ); } my $DeleteUrl = $Cache{SourceUrl}; my $Type = "Internet Cache"; if( $CACHE_ENTRY{COOKIE} & $Cache{CacheEntryType} ) { $Type = "Internet Cookie"; } elsif( $CACHE_ENTRY{URLHISTORY} & $Cache{CacheEntryType} ) { $Type = "Internet History"; } print "$Type: $Cache{SourceUrl} " if( $Config{verbose} ); $Total{$Type}->{_count}++; $Total{$Type}->{_size} += $Cache{SizeHigh} * (256**4) + $Cache{SizeLow}; if( $Config{delete} ) { if( DeleteUrlCacheEntry( $DeleteUrl ) ) { print " successfully deleted." if( $Config{verbose} ); } else { print " failed to delete." if( $Config{verbose} ); } } print " " if( $Config{verbose} ); $pdwSize = pack( "L", length( $pCacheInfo ) ); # Убедитесь, что размер буфера достаточен для хранения # кэшированного элемента. do { my $Result = FindNextUrlCacheEntry( $hCache, $pCacheInfo, $pdwSize ); if( 0 == $Result ) { my $Error = Win32::GetLastError(); if( $Error == $ERROR_INSUFFICIENT_BUFFER ) { my $NewBufferSize = unpack( "L", $pdwSize ); $pCacheInfo = pack( "C$NewBufferSize", 0 ); } elsif( $Error == $ERROR_NO_MORE_ITEMS ) { # На практике, в данную точку сценария можно попасть не только в случае указанной ошибкиFindCloseUrlCache( $hCache ); $hCache = 0; } else { print "Due to an error we are stopping enumeration of cache files. "; print " ERROR: $Error (" . Win32::FormatMessage( $Error ) . ") "; FindCloseUrlCache( $hCache ); $hCache = 0; } } } while( 0 != $hCache && 0 != $Result ); } while( 0 != $hCache ); } } sub DeleteUrlHistory { $Total{'URL History List'} = CleanDirectory( $CSIDL_HISTORY ); } sub DeleteTempFiles { # Because we're passing in the full path (and not # the CSIDL value), call into RemoveDirectoryAndFiles(). $Total{'Temporary Files'} = CleanDirectoryAndFiles( $ENV{'TEMP'} ); } sub DeleteRecentFileList { $Total{'Recent File List'} = CleanDirectory( $CSIDL_RECENT ); } sub DeleteTempInternetFiles { $Total{'Temp Internet Files'} = CleanDirectory( $CSIDL_INTERNET_CACHE ); } sub CleanDirectory { my( $FolderType ) = @_; my $SubTotal = {}; my $pszPath = NewString( 1024 ); if( 0 == SHGetFolderPath( undef, $FolderType, undef, $SHGFP_TYPE_CURRENT, $pszPath ) ) { $pszPath =~ s/x00//g; $SubTotal = CleanDirectoryAndFiles( $pszPath ); } return( $SubTotal ); } sub CleanDirectoryAndFiles { my( $Dir ) = @_; my %SubTotal = ( '_count' => 0, '_size' => 0 ); my( @DirList, @FileList ); print " Directory: '$Dir' " if( $Config{verbose} ); if( opendir( DIR, $Dir ) ) { while( my $Object = readdir( DIR ) ) { my $Path = "$Dir$Object"; next if( "." eq $Object || ".." eq $Object ); push( @DirList, $Path ) if( -d $Path ); # If a file exists, the script tries to remove it. The script assumes that all # files in the folder are OK to remove. This includes # (at least) index.dat and desktop.ini. if( -f $Path ) { print " File: '$Object'" if( $Config{verbose} ); $SubTotal{_count}++; $SubTotal{_size} += ( stat( $Path ) )[7]; if( $Config{delete} ) { print "...deleting..." if( $Config{verbose} ); if( 0 == unlink( $Path ) ) { print "FAILED...rename..." if( $Config{verbose} ); # The script is unable to delete a particular file # (it is probably already opened by another process), so # the script will attempt to rename the file to something that is no longer # useful. # But first, if a renamed index file already exists, the script deletes it. unlink( $Path . $DELETED_FILE_RENAME_SUFFIX ); # The script renames the index file. print ( rename( $Path, $Object . $DELETED_FILE_RENAME_SUFFIX ) ? "done" : "FAILED" ) if( $Config{verbose} ); } else { print "done" if( $Config{verbose} ); } } print " " if( $Config{verbose} ); } } closedir( DIR ); } foreach my $Path ( @DirList ) { my $SubSubTotal = CleanDirectoryAndFiles( $Path ); # Сценарий пытается удалить папку, чтобы покинуть цикл FOR. # Удаление этого каталога произойдет, только если он не будет содержать объекты (файлы или папки) $SubTotal{_count} += $SubSubTotal->{_count}; $SubTotal{_size} += $SubSubTotal->{_size}; rmdir( $Path ) if( $Config{delete} ); } return( \%SubTotal ); } sub ClearTypedURLList { $Total{'Typed URL List'} = ClearRegistryKey( $::HKEY_CURRENT_USER, $REGISTRY_KEY{typed_url_list} ); } sub ClearFormData { $Total{'Forms Data'} = ClearRegistryKey( $::HKEY_CURRENT_USER, $REGISTRY_KEY{intelliforms_data} ); } sub ClearRunMRU { $Total{'Run MRU List'} = ClearRegistryKey( $::HKEY_CURRENT_USER, $REGISTRY_KEY{explorer_run_mru} ); } sub ClearRegistryKey { my( $Root, $Path ) = @_; my $Key; my %Data; print " Registry: $Path " if( $Config{verbose} ); if( $Root->Open( $Path, $Key ) ) { my %List; $Key->GetValues( \%List ); foreach my $ValueName ( sort keys %List ) { print " $ValueName => '$List{$ValueName}->[2]' " if( $Config{verbose} ); $Key->DeleteValue( $ValueName ) if( $Config{delete} ); $Data{_count}++; $Data{_size} += length( $List{$ValueName}->[2] ); } $Key->Close(); } return( \%Data ); } sub EmptyRecycleBin { # Переменная $Path указывает на то, какую «корзину» надо очистить. Пустая строка говорит о том, что # надо очистить все корзины на данном компьютере. my $Path = ""; $Total{'Recycle bins'} = QueryRecycleBin(); SHEmptyRecycleBin( undef, $Path, $SHERB_NOCONFIRMATION | $SHERB_NOPROGRESSUI | $SHERB_NOSOUND ) if( $Config{delete} ); } sub QueryRecycleBin { my $pQueryBinInfo; my $Result; my $Size; my $Count; $pQueryBinInfo = pack( $QUERY_RECYCLE_BIN_STRUCT, 0,0,0,0 ); $pQueryBinInfo = pack( $QUERY_RECYCLE_BIN_STRUCT, length($pQueryBinInfo),0,0,0 ); $Result = SHQueryRecycleBin( "", $pQueryBinInfo ); if( 0 == $Result ) { my( undef, $SizeLo, $SizeHi, $CountLo, $CountHi ) = unpack( $QUERY_RECYCLE_BIN_STRUCT, $pQueryBinInfo ); $Size = $SizeLo + $SizeHi * (256**4); $Count = $CountLo + $CountHi * (256**4); } return( { _size => $Size, _count => $Count } ); } sub FormatNumber { my( $Number ) = @_; $Number = 0 if( "" eq $Number ); while( $Number =~ s/^(-?d+)(d{3})/$1,$2/ ){}; return( $Number ); } sub FormatNumberPretty { my( $Number ) = @_; my $Suffix = ""; if( $Number >= $TERABYTE ) { $Number /= $TERABYTE; $Suffix = "T"; } elsif( $Number >= $GIGABYTE ) { $Number /= $GIGABYTE; $Suffix = "G"; } elsif( $Number >= $MEGABYTE ) { $Number /= $MEGABYTE; $Suffix = "M"; } elsif( $Number > $KILOBYTE ) { $Number /= $KILOBYTE; $Suffix = "K"; } $Number = sprintf( "%0.2f", $Number ); return( FormatNumber( $Number ) . $Suffix ); } sub Configure { my( $Config ) = @_; my $Result; Getopt::Long::Configure( "prefix_pattern=(-|/)" ); $Result = GetOptions( $Config, qw( delete|d verbose|v silent|s help|? ) ); $Config->{verbose} = 0 if( $Config->{silent} ); return( $Result ); } sub Syntax { print<<"EOT"; $0 This will delete everything in the Internet cache. This includes URL history, cached files and cookies. Syntax: $0 [/d][/v][/s][/?] /d...........Delete the entries found. /v...........Verbose mode. Display additional information. /s...........Silent mode. Don't display any text. This overrides Verbose mode. /?...........Display this help message. EOT }