Как стать автором
Поиск
Написать публикацию
Обновить

Получение версии конфигурации 1с напрямую из SQL

Время на прочтение10 мин
Количество просмотров16K
По долгу службы наша контора обслуживает несколько организаций, которые для управленческого и бухгалтерского учета используют 1с.
1с, как известно, постоянно выпускает обновления для своих конфигураций.
Соответственно на обновление хотя бы 5 баз уходит приличное количество времени.
Рассказ о том, как добиться полной (кроме скачивания обновлений) автоматизации процесса средствами MSSQL далее.


Автоматизировать процесс начнем с «конца»

В 1с есть командная строка: а вот её параметры
Загвоздка в следующем:
  • В 1с обновление возможно только с определенной версии на определенную. Это связано с тем, что файлы обновления поставляются не в виде полного «слепка» конфигурации. А в виде изменений от эталонной версии.
  • Так же в самом коде 1с есть предопределенные обработки, которые запускаются при переходе с одной версии на другую.


Значит нам надо получить текущую версию 1с.
Есть конечно вариант использования 1с-COMConnector, который не менее 2 секунд устанавливает соединение, «Кушает» лишнюю лицензию, память и пьет мой кофе.
Я пошел по другому пути. В версиях 1с >8.0 нет каталога базы данных, следовательно всё хранит в себе база данных MSSQL.
В таблицах БД есть таблица _config, а в ней строка с FileName=root. Смотрим содержимое (Binary Data):
{2,e0666db2-45d6-49b4-a200-061c6ba7d569}
Содержимое хранится в сжатом виде. Ниже будет пример его распаковки
Методом научного тыка находим этой же таблице есть запись с FileName=e0666db2-45d6-49b4-a200-061c6ba7d569 (тот же идетификатор):
{2,
{1e4190e9-76c2-456e-a607-4d817110ffd9},6,
{9cd510cd-abfc-11d4-9434-004095e12fc7,
{1,
{36,
{0,
{0,
{0,3,6b021b70-482d-4baf-b590-b86b68d4730e},"ЗарплатаИУправлениеПерсоналом",
{1,"ru","Зарплата и Управление Персоналом, редакция 2.5"},""}
},"",1,
{1,"ru","Зарплата и Управление Персоналом, редакция 2.5"},
{1,"ru","Зарплата и Управление Персоналом, редакция 2.5"},
{1,"ru","Copyright (С) ООО ""1C"", 2007-2011. Все права защищены"},
{1,"ru","http://www.1c.ru"},
{1,"ru","http://v8.1c.ru/hrm/"},72ef09b4-4393-4d23-a530-7e5b50cf1d24,31e2aa98-4a9c-4d99-a3e7-540a13396b8c,1e7940af-fba5-46c3-8ffb-e8389ac2b79f,87090b31-2cd0-4b45-8b24-efdb2383b1a2,1,"Фирма ""1С""","2.5.40.3","http://downloads.v8.1c.ru/tmplts/",1,

(приведена только самая интересная часть файла)

Вы наверное догадались что это за конфигурация 1с.
Теперь надо это объяснить MS SQL.
В c# я написал класс:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
 
namespace metadata
{
    class v8metadata
    {
        public object[] array_data;
        public v8metadata[] child; 
        public v8metadata parent;
        public string filename;
        private Decoder d;
 
        public v8metadata(MemoryStream ms, string file_name, v8metadata p,Decoder dec)
        {
            d = dec;
            filename = file_name;
            array_data = new object[0];
            child = new v8metadata[0];
            parent = p;
            parse_ms(ms, true);
        }
// Этот конструктор добавлен для MD файлов 1с версии 7.7 
// Там кодировка файла другая
        public v8metadata(MemoryStream ms, string file_name,Decoder dec)
        {
            d = dec;
            filename = file_name;
            array_data = new object[0];
            child = new v8metadata[0];
            if (parent == null)
                ms.Position = 0;
            parse_ms(ms);
        }
        public v8metadata(MemoryStream ms, string file_name)
        {
            d = Encoding.UTF8.GetDecoder(); 
            filename = file_name;
            array_data = new object[0];
            child = new v8metadata[0];
            if (parent == null)
                ms.Position = 0;
            parse_ms(ms);
        }
        public object get(int pos)
        {
            return array_data[pos];
        }
        public string get(string descr)
        {
            string res = "error wrong param";
            if (descr == "version")
                try
                {
                    res = ((v8metadata)(((v8metadata)(((v8metadata)(this.array_data[3])).array_data[1])).array_data[1])).array_data[15].ToString();
                }
                catch
                {
                    res = "Ошибка";
                }
            else if (descr == "vendor")
                res = ((v8metadata)(((v8metadata)(((v8metadata)(this.array_data[3])).array_data[1])).array_data[1])).array_data[14].ToString();
            else if (descr == "conf")
                res = ((v8metadata)(((v8metadata)(((v8metadata)(((v8metadata)(((v8metadata)(this.array_data[3])).array_data[1])).array_data[1])).array_data[1])).array_data[1])).array_data[2].ToString();
 
            if ((res.Substring(0, 1) == "\"") && (res.Substring(res.Length - 1) == "\""))
            {
                res = res.Substring(1);
                res = res.Substring(0, res.Length - 1);
            }
            return res.Replace("\"\"", "\"");
        }
        public string ToStr(string lev)
        {
            string ret = "";
            for (int i = 0; i < this.array_data.Length; i++)
            {
                ret = ret+ lev +"_" + i.ToString() + ":";
                if (this.array_data[i].GetType().FullName == "System.String")
                    ret = ret + " " + this.array_data[i].ToString() + @"
";
                else
                    ret = ret + ((v8metadata)this.array_data[i]).ToStr(lev + "_" + i.ToString()) + @"
";
            }
 
            return ret;
        }
        private void parse_ms(MemoryStream ms)
        {
            parse_ms(ms, false);
        }
        private int parse_ms(MemoryStream ms, Boolean findbegin)
        {
            int cur;
            byte[] bytedata = new byte[0];
            //for (int i = Convert.ToInt16(ms.Position); i < ms.Length; i++)
            while (ms.Position < ms.Length)
            {
                cur = ms.ReadByte();
                //Это заголовки
                if (cur == 13)
                {
                    cur = ms.ReadByte();
                    if (cur == 10)
                        continue;
                    else
                    {
                        Addtoarraybyte(ref bytedata, 13);
                        Addtoarraybyte(ref bytedata, cur);
                    }
                }
                if (cur == 123)
                {
                    if (findbegin)
                    {
                        adddata(bytedata);
                        adddata(new v8metadata(ms, filename, this,d));
                    }
                    else
                    {
                        findbegin = true;
                        bytedata = new byte[0];
                    }
                    continue;
                }
                if (cur == 44)
                {
                    //запятая
                    adddata(bytedata);
                    bytedata = new byte[0];
                    continue;
                }
                if (cur == 125)
                    break;
                Addtoarraybyte(ref bytedata, cur);
 
            }
            adddata(bytedata);
            return 0;
        }
        private static void Addtoarraybyte(ref byte[] arr, int val)
        {
            byte[] tmp = new byte[arr.Length + 1];
            Array.Copy(arr, 0, tmp, 0, arr.Length);
            arr = tmp;
            arr[arr.Length - 1] = Convert.ToByte(val);
        }
 
        private void adddata(byte[] bytedata)
        {
            if (bytedata.Length == 0) return;
            char[] cs = new char[d.GetCharCount(bytedata, 0, bytedata.Length)];
            d.GetChars(bytedata, 0, bytedata.Length, cs, 0); //выполняем декодировку
            string a_data = new string(cs);
            object[] tmp = new object[array_data.Length + 1];
            Array.Copy(array_data, 0, tmp, 0, array_data.Length);
            array_data = tmp;
            array_data[array_data.Length - 1] = a_data;
        }
        private void adddata(v8metadata childdata)
        {
            object[] tmp = new object[array_data.Length + 1];
            Array.Copy(array_data, 0, tmp, 0, array_data.Length);
            array_data = tmp;
            array_data[array_data.Length - 1] = childdata;
 
            addv8metadata(childdata);
        }
        private void addv8metadata(v8metadata childdata)
        {
            v8metadata[] tmp1 = new v8metadata[child.Length + 1];
            Array.Copy(child, 0, tmp1, 0, child.Length);
            child = tmp1;
            child[child.Length - 1] = childdata;
        }
    }
}


При создании класса надо передать MemoryStream binary data из таблицы _config.
Как видно в коде им можно парсить и конфигурации 7.7, предварительно распаковав.

Теперь до версии конфигурации можно добраться зная её «адрес»: Далее просто создаем пустую базу с версией «ВЕРСИЯ» и находи что её «адрес» (v8metadata)(((v8metadata)(((v8metadata)(this.array_data[3])).array_data[1])).array_data[1])).array_data[15].ToString();

«Обвязочка» класса:
using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.IO.Compression;
using System.IO;

namespace metadata
{
    class v8config
    {
        public SqlConnection con;
        public string rootfile;
        public string platform;
        public string vendor;
        public string conf;
        public string version;
        public v8metadata v8;
        private string db;
        private string server = "";

        public v8config(string constring)
        {
            con = new SqlConnection(constring);
            db = con.Database;
            MemoryStream ms = file_data("root");
            v8metadata v82 = new v8metadata(ms, "root");
            rootfile = v82.get(1).ToString();
            if (v82.array_data.Length > 2)
                platform = "8.2";
            else
                platform = "8.1";
            update_prop();
        }
        //Конструктор для linked серверов
        public v8config(SqlConnection conn, string srv, string database)
        {
            con = conn;
            server = srv;
            db = database;
            if (check_is_v8())
            {
                MemoryStream ms = file_data("root");
                v8metadata v82 = new v8metadata(ms, "root");
                rootfile = v82.get(1).ToString();
                if (v82.array_data.Length == 3)
                    platform = "8.2";
                else if (v82.array_data.Length > 3)
                {
                    platform = "8.0";
                    rootfile = "root";
                }
                else
                    platform = "8.1";
                update_prop();
            }
        }
        public v8config(SqlConnection conn)
        {
            con = conn;
            db = con.Database;
            MemoryStream ms = file_data("root");
            v8metadata v82 = new v8metadata(ms, "root");
            rootfile = v82.get(1).ToString();
            update_prop();

        }
        //Функция, конечно кривовата, но работает
        private void update_prop()
        {
            if (platform != "8.0")
            {
                if (v8 == null)
                {
                    v8 = new v8metadata(file_data(rootfile), rootfile);
                }
                if (((v8metadata)(((v8metadata)(((v8metadata)(v8.array_data[3])).array_data[1])).array_data[1])).array_data.Length > 20)
                    platform = "8.2";
                vendor = v8.get("vendor");
                conf = v8.get("conf");
                version = v8.get("version");
            }
            else
            {
                vendor = "очень старая конфигурация";
                conf = "очень старая конфигурация";
                version = "очень старая конфигурация";
            }
        }
        public void SaveToFile(string file, string filename)
        {
            FileStream fs = File.OpenWrite(filename);
            MemoryStream ms = file_data(file);
            byte[] data = ms.ToArray();
            fs.Write(data, 0, data.Length);
        }
        private bool check_is_v8()
        {
            try
            {
                bool doopencon = false;
                SqlCommand cmdu1;
                bool haslinked = true;
                if (con.State != System.Data.ConnectionState.Open)
                {
                    con.Open();
                    doopencon = true;
                }

                cmdu1 = new SqlCommand("", con);
                cmdu1.CommandText = "select name from [" + server + "].[master].dbo.sysdatabases where name='" + db + "'";
                if (cmdu1.ExecuteScalar() == null)
                {
                    haslinked = false;
                }
                if (!haslinked)
                {
                    if (doopencon)
                        con.Close();
                    return false;
                }
                cmdu1.CommandText = "select name from [" + server + "]." + db + ".dbo.sysobjects where 	name ='config'";
                if (cmdu1.ExecuteScalar() == null)
                {
                    haslinked = false;
                }
                if (doopencon)
                    con.Close();
                return haslinked;
            }
            catch
            {
                return false;
            }

        }

        private MemoryStream file_data(string name)
        {
            MemoryStream ms = new MemoryStream();
            if (con.State != System.Data.ConnectionState.Open)
                con.Open();
            string sql;
            if (server != "")
                sql = "select BinaryData from [" + server + "].[" + db + "].dbo.Config where [filename]='" + name + "'";
            else
                sql = "select BinaryData from [" + db + "].dbo.Config where [filename]='" + name + "'";
            SqlCommand cmdu1 = new SqlCommand(sql, con);
            cmdu1.CommandTimeout = 200;
            SqlDataReader rs = cmdu1.ExecuteReader();
            if (rs.Read())
            {
                DeflateStream dfs = new DeflateStream(rs.GetSqlBytes(0).Stream, CompressionMode.Decompress);
                byte[] BufferOut = new byte[100];
                int BytesRead;
                while ((BytesRead = dfs.Read(BufferOut, 0, 100)) > 0)
                    ms.Write(BufferOut, 0, BytesRead);
                dfs.Close();
            }

            con.Close();
            return ms;
        }

        public string get(string descr)
        {
            if (rootfile == null)
                return "База данных не является бд 1с v8";
            if (v8 == null)
            {
                v8 = new v8metadata(file_data(rootfile), rootfile);
            }
            if (descr == "test")
            {
                string fname = ((v8metadata)(((v8metadata)(((v8metadata)(v8.array_data[4])).array_data[2])).array_data[2])).array_data[3].ToString() + ".5";

                MemoryStream ms = this.file_data(fname);
                v8metadata nv8 = new v8metadata(ms, fname);
                return "test";
            }
            else
                return v8.get(descr);
        }
    }
}

Usage:
 v8config v8 = new v8config("server=сервер;database=база;uid=sa;pwd=пароль;Connection Timeout=300;");
v8.get("version");


Но где же здесь MSSQL?
Вот CLR функция, которая получит эти данные в самом MSSQL:
У нас есть отдельная БД, которая хранит в себе сервера и базы. Соответственно функция адаптирована под это.
using System;
using System.Collections;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;


public partial class _1c_conf_functions
{
    [SqlFunction(DataAccess = DataAccessKind.Read,  FillRowMethodName = "FillRowConfig", TableDefinition = "[platform] nvarchar(5),vendor nvarchar(150),conf nvarchar(250),version nvarchar(100)")]
    public static IEnumerable v8getconfig(string srv, string dbs)
    {
        using (SqlConnection conn = new SqlConnection("context connection=true"))
        {
            metadata.v8config v8 = new metadata.v8config(conn, srv, dbs);
            metadata.v8config[] t = new metadata.v8config[1];
            t[0] = v8;
            return t;
        }
        //v8metadata.v8_confmanager vconf = new v8metadata.v8_confmanager(@"\\ackiy_gw\public\1c_distr\tmplts_8.1");
        //return  vconf.find_config("Фирма \"1С\"", "БухгалтерияПредприятия").data;

    }

    public static void FillRowConfig(object obj, out SqlChars platform, out SqlChars vendor, out SqlChars conf, out SqlChars version)
    {
        //    dateupdate = new SqlDateTime(((v8metadata.v8info)obj).dataupdate);
        metadata.v8config cfg = (metadata.v8config)obj;
        platform = new SqlChars(cfg.platform);
        vendor = new SqlChars(cfg.vendor);
        conf = new SqlChars(cfg.conf);
        version = new SqlChars(cfg.version);
    }
};


А как определить какое обновление необходимо для данной конфигурации 1с?
При установке обновлении 1с можно использовать каталог обновлений на сервере. В каждом обновлении есть файл .mft вида:
Vendor=Фирма "1С"
Name=БухгалтерияПредприятия
Version=2.0.25.5
AppVersion=8.2
........


И файл UpdInfo.txt
Version=2.0.25.5
FromVersions=;2.0.24.10;
UpdateDate=11.07.2011


Это же всё, что нам надо!!!
Зная FromVersions и дату выхода обновления мы можем автоматически генерировать строку для запуска обновления 1с. (ссылка на параметры в начале топика)

Но тут появляется еще одна проблема — наличие пользователей в базе. 1с не обновляется. Пишем «выгонялку» пользователей (vbscript)

'basename-Имя базы в кластере серверов 1с
'updateway - Путь к файлу обновления
'platform - Платформа 1с (8.1/8.2)
'srv1c - Сервер 1с
'srvUser - пользователя для входа на сервер 1с
'srvPasswd- пароль пользователя для входа на сервер 1с
'confchanged - булево - конфигурация изменена. Выполнить только принятие изменений конфигурации 
Function GetUpdateForConfig(basename,updateway,platform,srv1c,srvUser,srvPasswd,baseUsr,basepwd,confchanged)
 
allowdisconnect=false
'Если мы работаем с 12 до 6 утра - мы можем выгнать пользователей
if Hour(now())>0 and Hour(now)<6 then
	allowdisconnect=true
End if
 
set Connector=CreateObject("V" & Replace(platform,".","") & +".ComConnector")
 
set AgentConnection=Connector.ConnectAgent(srv1c)
 
 
set Cluster=AgentConnection.GetClusters() (0)
 
AgentConnection.Authenticate Cluster,srvUser,srvPasswd
 
'WorkingProcess = AgentConnection.GetWorkingProcesses(Cluster)[0];
Process = AgentConnection.GetWorkingProcesses(Cluster)
 
for each WorkingProcess in Process
	if WorkingProcess.Running<>0 then
		ConnectString = WorkingProcess.HostName & ":" & WorkingProcess.MainPort
		set WorkingProcessConnection = Connector.ConnectWorkingProcess("tcp://" & ConnectString)
		WorkingProcessConnection.AddAuthentication baseUsr,basepwd
 
		set ibDesc = WorkingProcessConnection.CreateInfoBaseInfo()
		ibDesc.Name = basename
 
		Connections = WorkingProcessConnection.GetInfoBaseConnections(ibDesc)
 
 
		for each Connection in Connections
			if LCase(Connection.AppID) <> "comconsole" then
				if allowdisconnect then
					WorkingProcessConnection.Disconnect Connection 
					ShowStatus "Discconnect соединения " & Trim(Connection.ConnID),true,false	
				else
					ShowStatus "Есть работающие пользователи.",false,false	
					GetUpdateForConfig=0
					Exit function
				End if
			End if
		Next
 
	End if
 
Next
 
 
ShowStatus "Запуск обновления " & updateway,true,false
 
ProcId=0
 
 
constr="/S""" & srv1c & "\" & basename & """"
 
if baseUsr<>"" then
	constr=constr & " /N" & baseUsr & " /P" & basepwd
end if
 
if not confchanged then
	constr=constr & " /UpdateCfg""" & updateway & """"
end if
 
constr=constr & " /UpdateDBCfg"
if Way1cv81="" then 
	ShowStatus "Не заполнен путь к 81",true,true
	GetUpdateForConfig=""
	Exit function
End if
RunString="""" & Way1cv81 & "1cv8.exe"" CONFIG " & constr & ""
 
 
Resfile=WorkCatalog() & "result.txt"
RunString=RunString & " /Out""" &  Resfile & """"
 
ShowStatus "Строка запуска: " & RunString,true,false
 
CreateProcess RunString,true 
GetUpdateForConfig=ReadFileText(Resfile)
 
End Function

Осталось только сложить пазл.
Теги:
Хабы:
Всего голосов 11: ↑8 и ↓3+5
Комментарии0

Публикации

Ближайшие события