![image](https://habrastorage.org/getpro/habr/post_images/cd8/fdf/0cc/cd8fdf0ccba029dd3d8b4037255d7c3f.jpg)
Как быть, если однажды вы обнаружите, что ваш любимый сайт Sharepoint не доступен и все что от него осталось — это база контента, которую нет возможности присоединить к серверу Sharepoint? Как восстановить «триллион» наиважнейших документов, хранящихся в базе Sharepoint? Короткую печальную историю и ответы на эти вопросы вы можете почерпнуть из данной статьи.
В одно не самое прекрасное утро мне довелось столкнуться с ситуацией, описанной в статье Тодда Клиндта «Восстановление SharePoint 2010 в аварийных ситуациях. Часть 2»:
Предположим, у вас есть обычная маленькая ферма, состоящая из одной системы SQL Server и одного сервера SharePoint. Как хороший администратор SQL Server, вы каждый вечер делаете копии всех баз данных. В одно прекрасное утро вы приходите и слышите крики пользователей: «SharePoint рухнул!». Выпив чашечку кофе, вы пытаетесь подключиться к SharePoint и понимаете, что он действительно отказал. Причем не только SharePoint, но и весь сервер. Вы не можете подсоединиться к серверу через RDP, он не отвечает на запросы по ping — это явная смерть. Вы спешите в серверную комнату и видите, что сервер SharePoint застыл на экране загрузки, поскольку не может найти жесткий диск, с которого эта загрузка должна осуществляться. Какая бы дисковая подсистема ни была, один ли диск, RAID 1 или RAID 5, все это может сломаться. Сервер и весь контент на нем исчезли. Что вам делать, кроме как поискать в кармане флэшку со своим резюме?!
На самом деле это не такой уж серьезный тип аварийной ситуации, поскольку рухнул только ваш сервер SharePoint. Хотя сервер SharePoint и является важной частью системы SharePoint, сервер SQL Server не менее важен, поэтому вы можете воспользоваться преимуществом функционирования системы SQL Server для быстрого исправления ситуации. Вам нужно заставить сервер работать либо при помощи нового сервера, либо починив то, что сломалось в имеющемся сервере, а затем переустановить Windows и загрузить все обновления, выполнить настройки и присоединиться к домену. Затем требуется переустановить SharePoint. После того как все предварительные условия выполнены и файлы SharePoint установлены, следует запустить мастер SharePoint Products Configuration Wizard.
Вот именно здесь и происходит «волшебство». Вместо строительства новой фермы SharePoint вы можете просто подсоединиться к существующей ферме. Когда появится запрос, к какой ферме подсоединиться, укажите существующую систему SQL Server и базу данных конфигурации SharePoint, которую эта система содержит. Вооруженный информацией, содержащейся в базе данных конфигурации вашей фермы, вновь построенный сервер SharePoint может получить доступ к существующим веб-приложениям и начать их обслуживание практически немедленно. SharePoint использует плановые задания для создания той среды, которая необходима для обслуживания контента. Ваши веб-приложения будут созданы в Microsoft IIS при помощи этих плановых заданий. Решения, которые были установлены в вашей ферме, будут установлены на новый сервер при помощи этих заданий. Когда параметры конфигурации будут заданы, возможно, потребуется подчистить некоторые мелочи, но эти задачи — ничто по сравнению с выполненным восстановлением сервера после полного краха.
Вздохнув с облегчением, я подумал — вот оно решение проблемы, однако мой случай оказался не из простых и «волшебства» не произошло, подсоединится к существующем серверу SQL и подтянуть базу конфигурации не удалось. Мастер импорта SharePoint всячески уверял меня, что база конфигурации не валидна и ничего нельзя сделать. Убедить SharePoint в обратном также не удалось (как выяснилось позднее, данный сервер SharePoint был получен путем миграции с SharePoint версии 2007 года и уже тогда дело не обошлось без «танцев с бубном»).
В этот момент стало ясно, что «воскрешение» SharePoint затягивается, а пользователя с факелами и вилами уже на подходе.
Полчаса терзаний google различными запросами на тему аварийного восстановления данных SharePoint привели меня к статье Mike Smith’s «Exploring SharePoint CMP Export Files» блога «Mike Smith's Tech Training Notes».
В статье описывается процесс экспорта контента из не присоединённой базы Sharepoint (экспорт производится в файлы формата *.cmp), а так же процесс извлечения необходимых документов из файлов формата *.cmp. Так же к статье прилагается уже скомпилированная программа и исходный код проекта, осуществляющего автоматическую распаковку cmp файлов с последующим извлечением документов из них. Возможность быстро восстановить документы, которые позарез нужны пользователям прямо здесь и прямо сейчас это уже неплохо. Так я думал, запуская только что скачанную программу и мысленная воздавая хвалы её автору. Но и здесь меня ждал неприятный сюрприз – при попытке извлечения документов программа завершалась с ошибкой обращения по несуществующему адресу памяти. Радовало одно – описанный в статье алгоритм ручного извлечения документов из cmp файлов работал, чтобы было определено опытным путем. Процесс заключается в следующей последовательности действий:
- Экспортировать из не присоединенной базы контента необходимый раздел сайта (на выходе файл в формате cmp).
- Изменить расширение полученного файла с cmp на cab и извлечь из него все файлы встроенными средствами Windows.
- Открыть файл manifest.xml, выполнить в нем поиск строки с названием интересующего документа, в найденной секции найти значение атрибута FileValue
- Переименовать соответствующий значению атрибута FileValue dat-файл в интересующий нас документ.
Извлечение нескольких тысяч файлов вручную показалось мне несколько непродуктивным (поиск и исправление ошибки, из-за который некорректно работает программа Майка Смита так же оказалось для меня не тривиальной задачей), поэтому было принято решение написать небольшую консольную программу на C#, выполняющую требуемые операции. В моем случае алгоритм действий получился следующим:
- Экспортировать из не присоединенной базы контента необходимый раздел сайта (на выходе файл в формате cmp).
- Изменить расширение полученного файла с cmp на cab и извлечь из него все файлы встроенными средствами Windows.
- Удалить все файлы xml кроме manifest.xml.
- Скопировать программу в каталог с распакованными файлами, запустить её и ждать пока не обработаются все файлы.
Программа действует по следующему алгоритму:
- Считывается manifest.xml.Для каждого dat файла в текущем каталоге ищется соответствующее имя документа в manifest.xml.Dat файл копируется с оригинальным именем документа.Dat файл удаляется.
Исходный код программы:
using System; using System.Windows.Forms; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace SertConsole { class Program { static void Main(string[] args) { try { //=========================================================== if (File.Exists("Manifest.xml")) { Console.WriteLine("Файл Manifest.xml найден"); //=========================================================== string ManifestXML = System.IO.File.ReadAllText(@"Manifest.xml"); Console.WriteLine("Файл Manifest.xml считан"); //=========================================================== DirectoryInfo dir = new DirectoryInfo(Directory.GetCurrentDirectory() + "\\"); FileInfo[] DatFiles = dir.GetFiles("*.dat"); Console.WriteLine("Количество dat файлов: " + DatFiles.Length); //=========================================================== for (int i = 0; i < DatFiles.Length; i++) { if (File.Exists(GetNameFromDat(ManifestXML, DatFiles[i].Name))) { File.Move(DatFiles[i].Name, GetFreeName(GetNameFromDat(ManifestXML, DatFiles[i].Name))); } else { File.Move(DatFiles[i].Name, GetNameFromDat(ManifestXML, DatFiles[i].Name)); } File.Delete(DatFiles[i].Name); Console.WriteLine("Файл: " + DatFiles[i].Name + " -> " + GetNameFromDat(ManifestXML, DatFiles[i].Name)); } Console.WriteLine("Работа завершена, можно идти пить чай..."); Console.ReadLine(); } } catch (Exception ex) { MessageBox.Show("Информация об ошибке:" + "\r\n\r\n" + ex.ToString(), "Во время выполнения программы возникла ошибка", MessageBoxButtons.OK,MessageBoxIcon.Error); } } //------------------------------------------------------------------------------------------- static string GetNameFromDat(string XMLString, string FileName) { string result = ""; int FileNamePos = XMLString.LastIndexOf(FileName); for (int i = FileNamePos-11; i > 0; i--) { if (XMLString.Substring(i, 5) == "Name=") { for (int j = i+6; j < FileNamePos-11; j++) { if (XMLString[j] == '"') { result = XMLString.Substring(i + 6, j - (i + 6)); goto to_Out; } } } } to_Out: return result; } //------------------------------------------------------------------------------------------- static string GetFreeName(string FileName) { string result = FileName; int count = 0; do { result = count.ToString() + "_" + FileName; count++; } while (File.Exists(result) == true); return result; } } }
Конечно, программа примитивна и может вызвать массу нареканий со стороны любого программиста, но со своей задачей автоматизации рутинных операция справляется. Надеюсь, что описанная в статье информация окажется не бесполезной и найдет своего читателя. Ссылки на скомпилированную программу и файлы проекта (использовалась Visual Studio Express 2013 for Desktop) прилагаются:
Скомпилированная программа.
Проект VisualStudio 2013.