/
Radiator
/
VesAKAutoUpdate
Обзор
Документация
Войти
/
Radiator
/
VesAKAutoUpdate
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
Upd.cs
471 строка
20 KB
Radiator
Create: Program.cs, Upd.cs, Инструкция автоапдейт.odt, Form1.cs
15 июн 2026, 11:29
Верифицирован
15 июн 2026, 11:29
c1bb6d4
Код
Авторство
О чём код?
using System; using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; internal class Upd { // --- Поля для хранения данных из конфига --- // --- Параметры подключения к базе (файловой или серверной) --- private string _pathTo1Cv8; private string _pathToIB; // Для файловой базы private string _login; private string _password; // --- Параметры кластера 1С (для серверной базы) --- private string _clusterServer; private string _ibNameOnServer; private string _clusterAdminLogin; private string _clusterAdminPassword; // --- Параметры файла дампа --- private string _dumpPath; private string _dumpFileName; // --- Дополнительные флаги --- private bool _isServerType; private bool _useCompression; // Имя конфигурационного файла и лога private const string ConfigFileName = "VesAKAutoUpdate.cfg"; private const string LogFileName = "VesAKAutoUpdate.log"; // Публичный метод, который запускает весь процесс public async Task RunUpdateAsync(Action<string> updateStatusCallback) { MyLog("Процесс обновления запущен."); updateStatusCallback("Чтение конфигурации..."); try { // 1. Читаем данные из конфига САМОСТОЯТЕЛЬНО LoadConfig(); // 2. Проверяем, что все данные прочитались корректно // ValidateConfig(); // 3. Выполняем шаги обновления if (_isServerType) { TerminateSessionsAndDisableJobs(); } else { KillAll1CProcesses(); } updateStatusCallback("Создание резервной копии..."); MyLog("Создание резервной копии ИБ. Начало"); await CreateDumpAsync(); MyLog("Создание резервной копии ИБ. Окончание"); updateStatusCallback("Обновление конфигурации БД..."); MyLog("Обновление конфигурации базы данных. Начало"); await UpdateDBConfigAsync(); MyLog("Обновление конфигурации базы данных. Окончание"); updateStatusCallback("Обновление информационной базы..."); MyLog("Обновление информационной базы. Начало"); await UpdateIBAsync(); MyLog("Обновление информационной базы. Окончание"); if (_useCompression) { updateStatusCallback("Сжатие таблиц ИБ..."); MyLog("Сжатие таблиц информационной базы. Начало"); await IBCompressionAsync(); MyLog("Сжатие таблиц информационной базы. Окончание"); } updateStatusCallback("Все операции завершены успешно."); MyLog("Все операции завершены."); } catch (Exception ex) { // Обновляем статус и лог в случае любой ошибки updateStatusCallback("Ошибка: " + ex.Message); MyLog("Произошла критическая ошибка: " + ex.Message); } } #region Методы работы с конфигом private void LoadConfig() { if (!File.Exists(ConfigFileName)) { throw new FileNotFoundException($"Конфигурационный файл '{ConfigFileName}' не найден."); } using (var reader = new StreamReader(ConfigFileName, Encoding.GetEncoding(855))) { _pathTo1Cv8 = reader.ReadLine(); _pathToIB = reader.ReadLine(); // Читаем зашифрованную строку с логином и паролем string encodedCreds = reader.ReadLine(); string decodedCreds = Encoding.GetEncoding(855).GetString(Convert.FromBase64String(encodedCreds)); _login = decodedCreds.Substring(decodedCreds.IndexOf("Логин:") + 6, decodedCreds.IndexOf("Пароль:") - decodedCreds.IndexOf("Логин:") - 6); _password = decodedCreds.Substring(decodedCreds.IndexOf("Пароль:") + 7); _dumpPath = reader.ReadLine(); _dumpFileName = reader.ReadLine(); // Читаем опцию сжатия, если она есть в файле if (!reader.EndOfStream) { _useCompression = reader.ReadLine() == "True"; } else { return; } // Флаг типа базы (с проверкой на конец файла) if (!reader.EndOfStream) { bool.TryParse(reader.ReadLine(), out bool serverTypeResult); _isServerType = serverTypeResult; } else { return; } // 4. Читаем параметры кластера _clusterServer = reader.ReadLine(); _ibNameOnServer = reader.ReadLine(); // 5. Читаем и декодируем учетные данные администратора кластера string encodedClusterCreds = reader.ReadLine(); string decodedClusterCreds = Encoding.GetEncoding(855).GetString(Convert.FromBase64String(encodedClusterCreds)); _clusterAdminLogin = decodedClusterCreds.Substring(decodedClusterCreds.IndexOf("Логин:") + 6, decodedClusterCreds.IndexOf("Пароль:") - decodedClusterCreds.IndexOf("Логин:") - 6); _clusterAdminPassword = decodedClusterCreds.Substring(decodedClusterCreds.IndexOf("Пароль:") + 7); } } private void ValidateConfig() { if (string.IsNullOrEmpty(_pathTo1Cv8) || string.IsNullOrEmpty(_pathToIB) || string.IsNullOrEmpty(_login) || string.IsNullOrEmpty(_password) || string.IsNullOrEmpty(_dumpPath) || string.IsNullOrEmpty(_dumpFileName)) { throw new Exception("Ошибка конфигурационного файла. Некоторые поля пусты."); } } #endregion #region Вспомогательные методы /// <summary> /// Удаляет старые файлы дампов из папки, оставляя только самый свежий. /// </summary> private void KillOldDumps() { // Проверяем, что путь к папке дампа указан if (string.IsNullOrEmpty(_dumpPath)) { MyLog("Путь к папке дампа не указан. Очистка старых дампов пропущена."); return; } try { // Проверяем, существует ли указанная папка if (!Directory.Exists(_dumpPath)) { MyLog($"Папка для дампов не найдена: {_dumpPath}"); return; } // Перебираем все файлы в папке дампов. foreach (string filePath in Directory.EnumerateFiles(_dumpPath)) { // Получаем только имя файла (например, "base_2023-10-27.dt") string fileName = Path.GetFileName(filePath); // Условие для удаления: // 1. Имя файла должно содержать базовое имя дампа (_dumpFileName). // 2. Имя файла должно содержать расширение .dt (стандарт для дампов 1С). // Это защищает от случайного удаления других файлов в папке. if (fileName.Contains(_dumpFileName) && fileName.EndsWith(".dt", StringComparison.OrdinalIgnoreCase)) { // Удаляем старый файл. // Конструкция using гарантирует, что файл будет освобожден даже при ошибке. File.Delete(filePath); MyLog($"Удален старый дамп: {fileName}"); } } } catch (Exception ex) { // Ловим любые ошибки (например, нет прав на удаление) и пишем в лог. MyLog("Ошибка при очистке старых дампов:"); MyLog(ex.Message); } } /// <summary> /// Принудительно завершает все соединения с серверной базой и запрещает фоновые задания. /// Вызывается только если база серверная (_isServerType == true). /// </summary> private void TerminateSessionsAndDisableJobs() { MyLog("--- Начало администрирования кластера ---"); try { // Создаем COM-объект для подключения к агенту сервера 1С dynamic comConnector = Activator.CreateInstance(Type.GetTypeFromProgID("V83.COMConnector")); dynamic agent = comConnector.ConnectAgent(_clusterServer); // Получаем список кластеров на сервере и берем первый dynamic clusters = agent.GetClusters(); dynamic cluster = clusters[0]; // Аутентифицируемся к кластеру с правами администратора кластера agent.Authenticate(cluster, _clusterAdminLogin, _clusterAdminPassword); // Получаем список ИБ в кластере и находим нашу dynamic infobases = agent.GetInfoBases(cluster); dynamic ourIB = null; foreach (dynamic ib in infobases) { if (ib.Name == _ibNameOnServer) { ourIB = ib; break; } } if (ourIB == null) { throw new Exception($"Информационная база '{_ibNameOnServer}' не найдена в кластере."); } // 1. Завершаем все сеансы пользователей с нашей базой dynamic sessions = agent.GetInfoBaseSessions(cluster, ourIB); foreach (dynamic session in sessions) { try { agent.TerminateSession(cluster, session); MyLog($"Завершен сеанс: {session.SessionID} ({session.UserName})"); } catch (Exception ex) { MyLog($"Не удалось завершить сеанс {session.SessionID}: {ex.Message}"); } } // 2. Запрещаем запуск регламентных (фоновых) заданий ourIB.ScheduledJobsDenied = true; agent.UpdateInfoBase(cluster, ourIB); MyLog("Запуск фоновых заданий запрещен."); } catch (Exception ex) { MyLog("Ошибка при администрировании кластера:"); MyLog(ex.Message); } MyLog("--- Окончание администрирования кластера ---"); } private Task<int> RunProcessAsync(string fileName, string arguments) { var tcs = new TaskCompletionSource<int>(); var process = new Process { StartInfo = { FileName = fileName, Arguments = arguments, UseShellExecute = false }, EnableRaisingEvents = true }; process.Exited += (s, e) => { tcs.SetResult(process.ExitCode); process.Dispose(); }; try { process.Start(); } catch (Exception ex) { tcs.SetException(ex); } return tcs.Task; } /// <summary> /// Принудительно завершает все процессы платформы 1С:Предприятие. /// Это необходимо для операций, требующих монопольного доступа к информационной базе. /// </summary> private void KillAll1CProcesses() { // Получаем список всех процессов с именем '1cv8' Process[] processes = Process.GetProcessesByName("1cv8"); foreach (Process process in processes) { try { // Пытаемся завершить процесс process.Kill(); // Ожидаем, пока процесс действительно завершится. // Это предотвращает ситуацию, когда мы пытаемся создать дамп, // а старые процессы еще не до конца выгрузились из памяти. process.WaitForExit(5000); // Ждем до 5 секунд MyLog($"Процесс 1С (ID: {process.Id}) успешно завершен."); } catch (Exception ex) { // Обрабатываем возможные ошибки: // - Процесс уже завершился // - Недостаточно прав для его завершения MyLog($"Не удалось завершить процесс 1С (ID: {process.Id}). Ошибка: {ex.Message}"); } } } private void MyLog(string LogText) { StreamWriter val = new StreamWriter("VesAKAutoupdate.log", true, Encoding.Default); try { ((TextWriter)val).WriteLine(global::System.DateTime.Now.ToString("yyyy-MM-ddTHH-mm-ss") + " " + LogText); ((TextWriter)val).Dispose(); } finally { ((global::System.IDisposable)val)?.Dispose(); } } #endregion #region Логика обновления /// <summary> /// Создает резервную копию (дамп) информационной базы. /// </summary> private async Task CreateDumpAsync() { KillOldDumps(); // Формируем полное имя файла дампа, включая путь и метку времени String _fullDumpPath = Path.Combine(_dumpPath, $"{_dumpFileName}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.dt"); string logPath = Path.ChangeExtension(_fullDumpPath, ".log"); int exitCode; // Запускаем процесс создания дампа и ждем его завершения асинхронно if (_isServerType) { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER /S\"{_clusterServer}\\{_ibNameOnServer}\" /N\"{_login}\" /P\"{_password}\" /DumpIB \"{_fullDumpPath}\" -Force /DisableStartupDialogs"); ///DisableStartupDialogs } else { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER /F \"{_pathToIB}\" /N \"{_login}\" /P \"{_password}\" /DumpIB \"{_fullDumpPath}\" -Force /DisableStartupDialogs"); } // Проверяем код выхода процесса. Если он не 0, значит произошла ошибка. if (exitCode != 0) { throw new Exception($"Ошибка создания бэкапа. Код выхода процесса: {exitCode}"); } } /// <summary> /// Обновляет конфигурацию базы данных. /// </summary> private async Task UpdateDBConfigAsync() { int exitCode; // Запускаем процесс обновления конфигурации и ждем его завершения асинхронно if (_isServerType) { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER /S\"{_clusterServer}\\{_ibNameOnServer}\" /N\"{_login}\" /P\"{_password}\" /UpdateDBCfg -Force /DisableStartupDialogs" ); } else { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER /F\"{_pathToIB}\" /N\"{_login}\" /P\"{_password}\" /UpdateDBCfg -Force /DisableStartupDialogs" ); } if (exitCode != 0) { throw new Exception($"Ошибка обновления конфигурации базы данных. Код выхода: {exitCode}"); } } /// <summary> /// Обновляет информационную базу (применяет изменения к данным). /// </summary> private async Task UpdateIBAsync() { int exitCode; // Запускаем процесс обновления ИБ и ждем его завершения асинхронно if (_isServerType) { exitCode = await RunProcessAsync( _pathTo1Cv8, $"ENTERPRISE -Force /DisableStartupDialogs /S\"{_clusterServer}\\{_ibNameOnServer}\" /N\"{_login}\" /P\"{_password}\" /CЗакрытьИБПослеОбновления" ); } else { exitCode = await RunProcessAsync( _pathTo1Cv8, $"ENTERPRISE -Force /DisableStartupDialogs /F\"{_pathToIB}\" /N\"{_login}\" /P\"{_password}\" /CЗакрытьИБПослеОбновления" ); } if (exitCode != 0) { throw new Exception($"Ошибка обновления информационной базы. Код выхода: {exitCode}"); } } /// <summary> /// Выполняет сжатие таблиц информационной базы для оптимизации ее размера. /// </summary> private async Task IBCompressionAsync() { int exitCode; if (_isServerType) { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER -Force /DisableStartupDialogs /S\"{_clusterServer}\\{_ibNameOnServer}\" /N\"{_login}\" /P\"{_password}\" /IBCheckAndRepair -IBCompression" ); } else { exitCode = await RunProcessAsync( _pathTo1Cv8, $"DESIGNER -Force /DisableStartupDialogs /F\"{_pathToIB}\" /N\"{_login}\" /P\"{_password}\" /IBCheckAndRepair -IBCompression" ); } if (exitCode != 0) { throw new Exception($"Ошибка при сжатии таблиц информационной базы. Код выхода: {exitCode}"); } } #endregion }