За несколько лет сначала вынужденного, а потом и вполне занимательного администрирования 1С у меня накопился набор решений под большинство особенностей продукта. Предлагаю отложить в сторону высокие материи про кластеры и тюнинг SQL, и перетряхнуть запасы скриптов и механизмов, которые облегчают жизнь с 1С.
Будут как простые инструменты создания новых пользователей и мониторинга "все ли вышли из базы", так и более изощренные интерфейсы проверки целостности базы и ее перемещения.
Чистый кэш и крепкое здоровье
Как у большинства сложных приложений, у 1С через некоторое время работы вылезают странные ошибки, и возникает порой необъяснимое поведение. Специальные люди по 1С советуют в таких случаях почистить кэш.
Если запустить 1С с параметром /ClearCache, то будут очищены только клиент-серверные запросы. Локальные метаданные останутся и их нужно удалять отдельно на уровне файлов и папок. Эти данные хранятся в профиле пользователя, в папках с длинными названиями из GUID баз данных. Если баз на сервере немного, то такой кэш нетрудно удалить руками. Но если БД исчисляется десятками, то чистке вручную вы не обрадуетесь.
В подобных ситуациях выручит скрипт на Powershell, который запускается каждый раз при выходе пользователя из системы:
Get-ChildItem "$env:USERPROFILE\AppData\Local\1C\1Cv8\*","$env:USERPROFILE\AppData\Roaming\1C\1Cv8\*" | Where {$_.Name -as [guid]} |Remove-Item -Force -Recurse
И никаких связанных со старым кэшем проблем.
Исправляем ошибки
Для исправления испорченной файловой базы в поставку 1С входит утилита chdbfl.exe, которая просто считывает содержимое базы во временный файл. Если какие-то данные считать не может — пропускает. При этом у нее нет ключей запуска для автоматизации, и проверку приходится запускать вручную.
Вообще, правильнее запускать проверку БД конфигуратором, но этот процесс проходит значительно дольше. Если же использовать только проверку физической целостности средствами chdbfl.exe, то не забывайте делать резервную копию из-за возможной потери данных.
Для баз 8.1 Андрей Скляров создал хороший инструмент Check1CD, с двумя параметрами запуска: "исправлять найденные ошибки" и “путь к базе”.
Но в Check1CD не хватает двух вещей:
Индикатора прогресса;
- Уведомлений техподдержки о найденных ошибках.
Раз есть "хотелка" и немного свободного времени, то почему бы не попробовать решить вопрос самостоятельно?
#NoTrayIcon
#include <GUIConstantsEx.au3>
#include <File.au3>
#include <Date.au3>
#include <Inet.au3>
Global $oMyError = ObjEvent("AutoIt.Error","MyErrFunc")
Global $Basepath="D:\Base\” ;база везде в одном месте, можно хардкодить
;Проверим, не запущена ли 1С.
If ProcessExists("1cv8.exe") or ProcessExists("chdbfl.exe") Then
Msgbox(0,"ошибка!", "1c уже запущена, запуск второй невозможен")
Exit
EndIf
;Проверим, не была ли прервана проверка, по наличию временного файла
if FileExists ($Basepath&"_$NEW$_.1CD") Then
MSGbox(0,"ошибка!", "проверка базы была прервана! обратитесь в Отдел ИТ")
Exit
EndIf
;Ну и проверим, на месте ли сам файл базы
If not FileExists ($Basepath&"1Cv8.1CD") Then
Msgbox(0,"ошибка!", "файл БД не обнаружен!")
Exit
EndIf
;Конечно, спросим
$hgui = GUICreate("проверка БД" , 200, 200)
GUICtrlCreateLabel(" будет запущена проверка базы",2,2)
GUICtrlCreateLabel("что может занять более 30 мин",2,22)
GUICtrlCreateLabel(" уверены? ",2,42)
$yes = GUICtrlCreateButton("да", 5, 150, 70, 25)
$no= GUICtrlCreateButton("нет", 75, 150, 70, 25)
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
GUIDelete()
Exit
Case $msg = $yes
GUIDelete()
_check ()
ExitLoop
Case $msg = $no
GUIDelete()
Exit
EndSelect
WEnd
;Сама проверка базы
func _check ()
$sTitle="Проверка физической целостности файла БД"
ProgressOn("проверка БД", "выполняется проверка базы данных", "0%",-1,-1,18)
run ("C:\Program Files\1cv81\bin\chdbfl.exe","", @SW_HIDE,7)
WinWait($sTitle)
local $size = filegetsize($Basepath&"1Cv8.1CD")
;Автокликер!
ControlSend($sTitle,"", "V8FormElement9",$Basepath&"1Cv8.1CD")
ControlClick($sTitle,"", "V8FormElement8")
ControlClick($sTitle,"", "V8FormElement7","left",2)
$filename = $Basepath&"\1Cv8.1CD"
local $size1 =0
;Считаем процент размера временного файла и заполняем прогрессбар
While 1
$size1 = filegetsize($Basepath&"_$NEW$_.1CD")
$perc=round(($size1/$size)*100)
ProgressSet($perc, "пожалуйста, ожидайте. прошло "& $perc & "%")
sleep (1500)
;Проверяем, что проверка еще идет по наличию временного файла
;и заблокированности файла БД
If not FileInUse($filename) and not FileExists ($Basepath&"\_$NEW$_.1CD") Then
ExitLoop
EndIf
WEnd
sleep(5000)
;К сожалению, получить информацию из объектов окна нормально не получилось
;Поэтому - автокликер!
WinSetState ( $sTitle, "", @SW_SHOW )
WinSetOnTop($sTitle, "", 1)
WinActivate($sTitle, "")
WinWaitActive($sTitle)
ControlClick($sTitle,"", "V8FormElement4")
sleep(50)
ControlSend($sTitle,"", "V8FormElement4","^{home}")
sleep(50)
ControlSend($sTitle,"", "V8FormElement4","+^{end}")
sleep(50)
ControlSend($sTitle,"", "V8FormElement4","^{ins}")
sleep(50)
$text = ClipGet()
ControlClick($sTitle,"", "V8FormElement6","left",2)
ProgressSet(100, "100%", "проверка завершена")
sleep (500)
ProgressOff()
;Создаем лог-файл, и пишем туда результат проверки, если он есть.
;И отправляем e-mail
;В организации используется Outlook Express. Берём отправителя из реестра
$sender = RegRead("HKEY_CURRENT_USER\Software\Microsoft\ _
Internet Account Manager\Accounts\00000001","SMTP Email Address")
$sendername = RegRead("HKEY_CURRENT_USER\Software\Microsoft\ _
Internet Account Manager\Accounts\00000001","SMTP Display Name")
If not FileExists($Basepath&"check.Log") then _FileCreate($Basepath&"\check.Log")
FileWrite ($Basepath&"check.Log", _NowCalc()&@CRLF )
If $text <> "" Then
FileWrite ( $Basepath&"check.Log", "база бита:"&@CRLF)
FileWrite ( $Basepath&"check.Log", $text&@CRLF)
FileWrite ( $Basepath&"check.Log", "отправляем имейл.."&@CRLF)
;Проверяем доступность нашего smtp. Да-да, опять хардкод
If Ping("smtp-server") <> 0 then
if $sender = "" or $sendername="" Then
MSGbox(0,"ошибка!", "не настроена почта, обратитесь в отдел ИТ!")
Else
email($sender, $sendername,$text)
EndIf
endif
;а если все хорошо, то и писать не будем
Else
FileWrite ( $Basepath&"check.Log", "всё чисто"&@CRLF)
EndIf
$hgui = GUICreate("проверка БД" , 200, 200)
GUICtrlCreateLabel("проверка БД завершена",2,2)
$ok = GUICtrlCreateButton("да", 5, 150, 70, 25)
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE or $msg=$1
GUIDelete()
Exit
EndSelect
WEnd
EndFunc
Func FileInUse($filename)
$handle = FileOpen($filename, 1)
$result = False
if $handle = -1 then $result = True
FileClose($handle)
return $result
EndFunc
func email($sender, $sendername,$text)
local $array[1]
$array[0]=$text
$t=_INetSmtpMail("smtpserver", $sendername,$sender, "[helpdesk@domain.com](mailto:helpdesk@domain.com)", _
"базка побилась" ,$array,"EHLO " & @ComputerName,-1 )
$error=@error
if $t <> 1 Then
FileWrite ( $Basepath&"check.Log", "ошибка отправки e-mail от "&$sendername& " "&$sender&@CRLF)
FileWrite ( $Basepath&"check.Log", "ошибка протокола "&$error&@CRLF)
Else
FileWrite ( $Basepath&"check.Log", "email отправлен"&@CRLF)
EndIf
EndFunc
;----------------------------------------------------------------------------------------------------------
; Com Error Handler
;----------------------------------------------------------------------------------------------------------
Func MyErrFunc()
Local $HexNumber
Local $strMsg
$HexNumber = Hex($oMyError.Number, 8)
$strMsg = " " & $HexNumber & @CRLF
$strMsg &= "описание " & $oMyError.WinDescription & @CRLF
$strMsg &= "строка: " & $oMyError.ScriptLine & @CRLF
FileWrite ( $Basepath&"check.Log", "ошибка ком-объекта: "&$strMsg)
FileWrite ( $Basepath&"check.Log", "e-mail не отправлен"& @CRLF)
SetError(1)
Endfunc
Доработать код для передачи параметров через ключи командной строки — дело техники.
Так было
Так стало
С выходом 1С 8.2 возникла проблема — путь к chdbfl менялся с установкой нового релиза. Что ж, дополним скрипт:
;Соберём в массив все пути к chdbfl в отсортированный массив
$path=_FileListToArrayRec(@ProgramFilesDir&"\1cv8\","chdbfl.exe",1,1,0,2)
If @error then
Msgbox("","","1c не установлена, или установлена не туда"
Exit
Endif
;и запустим первый
Run ($path[1],"", @SW_HIDE,7)
Надо сказать, недавно был опубликован исходный код Check1CD. Да, тоже на AutoIT.
Аналогичный механизм можно применять и для автоматического запуска различных регламентных механизмов, где нужно запускать 1С и ждать завершения операции.
Выкидываем из базы с выдумкой
При различных регламентных операциях с 1С (ночное обновление конфигурации или бэкап в .dt) важно обеспечить отсутствие подключенных к ней пользователей. Можно конечно запускать 1С: Предприятие с ключом /C ЗавершитьРаботуПользователей, но это не всегда удобно, да и хочется же потом написать личное письмо с рекомендациями по устранению склероза.
Можно использовать штатную возможность подключения к 1С через COMConnector и скрипт на AutoIT. Код написан под 1С 8.1 и позволяет выкинуть пользователей из базы с записью в журнал.
global $obj = ObjCreate("v81.comconnector")
$AgentConnection = $obj.ConnectAgent("tcp://servername:1540")
$Cluster = $AgentConnection.GetClusters()[0]
$AgentConnection.Authenticate($Cluster, "", "")
$WorkingProcess = $AgentConnection.GetWorkingProcesses($Cluster)[0]
$ConnectString = $WorkingProcess.HostName & ":" & $WorkingProcess.MainPort
$WorkingProcessConnection = $obj.ConnectWorkingProcess($ConnectString)
$WorkingProcessConnection.AddAuthentication("логин","пароль")
$ibDesc = $WorkingProcessConnection.CreateInfoBaseInfo()
$ibDesc.Name = "имя базы"
$connections = $WorkingProcessConnection.GetInfoBaseConnections($ibDesc)
for $temp in $connections
if $temp.username <> "логин" and ($temp.AppID <> "COMConsole" _
or "COM-соединение") then
FileWriteLine ( "файл лога", @MDAY&"."&@MON&"."&@YEAR&" _
"&@HOUR&":"&@MIN&" в базе сидит "&$temp.username &" _
тип подключения "&$temp.AppID)
$WorkingProcessConnection.Disconnect($temp)
endif
Next
$AgentConnection=""
$connect=""
Но операцию иногда нужно проворачивать по просьбе самого пользователя, который запустил "тяжелый" отчет и повесил 1С. Если не хотите решать эти вопросы самостоятельно, то просто выведите любителям “тяжелых” отчётов ярлык на скомпилированный скрипт:
$a=Msgbox(4,"Вы уверены?","Вы точно хотите выкинуться из базы?")
If $a<>6 then
exit
Else
global $obj = ObjCreate("v83.comconnector")
$AgentConnection = $obj.ConnectAgent("tcp://servername:1540")
$Cluster = $AgentConnection.GetClusters()[0]
$AgentConnection.Authenticate($Cluster, "", "")
$infobases=$AgentConnection.GetInfoBases($Cluster)
for $base in $infobases
if $base.name="basename" Then
$connections=$AgentConnection.GetInfoBaseSessions($Cluster, $base)
for $temp in $connections
;Пользователи в базе именуются так же, как и доменный пользователь
If $temp.username = @username then
$AgentConnection.terminatesession($cluster,$temp)
EndIf
next
Exit
EndIf
next
Endif
Еще COMConnector помогает проверить наличие обновлений конфигурации, получить какую-то информацию из базы, и автоматизировать заведение пользователей в 1С.
Технологичное создание новых пользователей
На мой взгляд, создавать новых пользователей 1С должен системный администратор, а не программист 1С. Но последнему нужно сделать процесс максимально простым. Чтобы администратору не приходилось "заглядывать" в каждую базу отдельно, можно использовать еще один скрипт.
;передаем функции имя пользователя в домене, фамилию, имя и отчество
func _1cBuh($username, $surname,$name,$fathername)
FileWriteLine($logfile, "добавляем в 1с 8.3 бухгалтерия "& $Username)
$obj = ObjCreate("v83.comconnector")
$connect=$obj.Connect("Srvr=""servername"";Ref=""basename"";Usr=""login"";Pwd=""password"";")
Local $avArray[1], $i=0
;Спрашиваем про роли в базе
$hgui = GUICreate("вопрос" , 200, 200)
GUICtrlCreateLabel("какие права в бухгалтерии?",2,2)
GUICtrlCreateLabel("полные или пользовательские?",2,22)
$1 = GUICtrlCreateButton("полные", 5, 150, 70, 25)
$2= GUICtrlCreateButton("пользователь", 75, 150, 70, 25)
GUISetState(@SW_SHOW)
While 1
$msg = GUIGetMsg()
Select
Case $msg = $1
GUIDelete()
$rght="ПолныеПрава"
ExitLoop
Case $msg = $2
GUIDelete()
$rght="Бухгалтер"
ExitLoop
EndSelect
WEnd
$mt=$connect.Metadata.Roles.find($rght)
;создаем пользователя ИБ
$newuser= $connect.ПользователиИнформационнойБазы.createuser()
$newuser.name = $surname&" "&$name
$newuser.fullname = $surname&" "&$name&" "&$fathername
$newuser.StandardAuthentication = "False"
$newuser.OSAuthentication="True"
$newuser.OSuser="\\DOMAINNAME\"&$Username
$newuser.Roles.add($mt)
$newuser.write()
;создаем пользователя в справочнике
$bject=$connect.NewObject("СправочникМенеджер.Пользователи")
$newuserS=$bject.CreateItem()
$newuserS.code=$surname&" "&$name
$newuserS.Description=$surname&" "&$name&" "&$fathername
$newuserS.write()
;Выбираем группы (юрлица)
$query=$connect.Catalogs.ГруппыПользователей.Выбрать()
while $query.Следующий()
if StringInStr ( $query.Description, "Группа") then
_ArrayAdd($avArray, $query.Description)
$i+=1
endif
wend
$avArray[0]=$i
GUICreate("Группы Бухгалтерии", 200,$i*20+70)
for $i=1 to $avArray[0]
assign ("var"&$i, GUICtrlCreateCheckbox( $avArray[$i], 2, 0+20*$i));, 90, 20))
next
$OK_Btn = GUICtrlCreateButton("добавить", 50,$i*20+20, 60)
GUISetState()
While 1
$msg = GUIGetMsg()
Select
Case $msg = $OK_Btn
For $i=1 to $avArray[0]
if BitAND(GUICtrlRead(eval("var"&$i)), $GUI_CHECKED) = _
$GUI_CHECKED Then
$grp=$connect.Справочники.ГруппыПользователей. _
НайтиПоНаименованию($avArray[$i]).ПолучитьОбъект()
$t=$grp.ПользователиГруппы.add()
$t.Пользователь = $newuserS.ссылка
$grp.write()
endif
next
GUIDelete()
ExitLoop
EndSelect
WEnd
$connect=""
endfunc
Юрлиц развелось слишком много — нужно разбивать на столбцы.
Занятно, но после смены нескольких поколений администраторов в одной компании из далекого прошлого новенькие уже не знали как создать пользователя вручную.
Автоматическое перемещение баз
Если нужно перенести базу 1С: Предприятия вместе с ее данными в SQL на другой сервер, то делать это вручную целесообразно только для 1-2 БД.
#include <GUIConstantsEx.au3>
local $name
GUICreate("перенос", 200, 180)
GUICtrlCreateLabel("название базы по английки",2,35)
$name1 = GUICtrlCreateInput($name, 2, 50, 300, 20)
$OK_Btn = GUICtrlCreateButton("погнали", 70, 180, 60)
GUISetState()
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
Exit
Case $msg = $OK_Btn
$name = GUICtrlRead($name1)
exitloop
endselect
WEnd
GUIDelete()
GUICreate("перенос", 200, 180)
GUICtrlCreateLabel("отключаем базу",2,2)
GUISetState()
$constrim="DRIVER={SQL Server};SERVER=старый_сервер;DATABASE=master;uid=sa;pwd=пароль;"
$adCN = ObjCreate ("ADODB.Connection")
$adCN.Open ($constrim)
$sQuery = "sp_detach_db @dbname = N'"&$name&"'"
$adCN.Execute($sQuery)
$adCN.Close
GUICtrlCreateLabel("перемещаем базу",2,15)
$u=FileCopy ("\\старый_сервер\путь к папке с SQL-базами\"&$name&".mdf","\\новый_сервер\путь к папке с SQL-базами\"&$name&".mdf")
if $u = 0 Then
GUICtrlCreateLabel("ОШИБКА! ПЕРЕМЕЩЕНИЕ НЕУСПЕШНО!!!!",2,15)
$OK_Btn = GUICtrlCreateButton("закрыть", 336,40, 60)
GUISetState()
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
exit
Case $msg = $OK_Btn
exit
endselect
WEnd
GUIDelete()
EndIf
$u=FileCopy ("\\старый_сервер\путь к папке с _
SQL-базами\"&$name&".ldf","\\новый_сервер\путь к папке с SQL-базами\"&$name&".ldf")
if $u = 0 Then
GUICtrlCreateLabel("ОШИБКА! ПЕРЕМЕЩЕНИЕ НЕУСПЕШНО!!!!",2,15)
$OK_Btn = GUICtrlCreateButton("закрыть", 336,40, 60)
GUISetState()
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
exit
Case $msg = $OK_Btn
exit
endselect
WEnd
GUIDelete()
EndIf
GUICtrlCreateLabel("создаем скулевую базу "&$name,2,28)
GUISetState()
$constrim="DRIVER={SQL Server};SERVER=новый_сервер;DATABASE=master;uid=sa;pwd=пароль;"
$adCN = ObjCreate ("ADODB.Connection")
$adCN.Open ($constrim)
$sQuery = "CREATE DATABASE "&$name&" ON (FILENAME = 'путь к папке с _
базами\"&$name&".mdf'), (FILENAME = 'путь к папке с базами\"&$name&".ldf') FOR ATTACH"
$adCN.Execute($sQuery)
$adCN.Close
GUICtrlCreateLabel("создаем 1c-ную базу "&$name,2,41)
GUISetState()
run ("""C:\Program Files (x86)\1cv8\common\1cestart.exe"" createinfobase_
""Srvr=localhost;Ref="&$name&";DB="&$name&";DBMS=MSSQLServer;DBSrvr=localhost;DBUID=sa;DBPwd=пароль""")
GUICtrlCreateLabel("закончили, проверяйте ",2,54)
$OK_Btn = GUICtrlCreateButton("закрыть", 336,40, 60)
GUISetState()
While 1
$msg = GUIGetMsg()
Select
Case $msg = $GUI_EVENT_CLOSE
exitloop
Case $msg = $OK_Btn
exitloop
endselect
WEnd
GUIDelete()
Список баз для миграции можно брать и из файла, а лог выводить в текстовый файл. Аналогичным образом можно конвертировать несколько десятков баз из файловых в SQL простой выгрузкой-загрузкой в .dt
На этом набор заканчивается
Конечно, это далеко не все, что можно автоматизировать в связке с 1С. Но разного рода обмены, получение real-time информации из 1С в других приложениях и прочие сценарии не попали в этот обзор из-за узкой направленности и специфики.
Наверняка у вас тоже есть свой набор "ноу хау" для администрирования 1С — будет здорово если поделитесь с коллегами в комментариях.
Скрипты и ноу-хау предоставлены avelor, за что ему огромное спасибо!