Приветствую тебя, уважаемый читатель!
По традиции, прошу слишком сильно не пинать, т.к. это мой первый пост.
Итак, приблизительно с полгода назад, встала задача автоматизировать управление списком баз 1С (коих развелось уже более 20 штук) у пользователей домена.
Делалось это не только удобства ради, но и в рамках проекта по внедрению «ролевой модели доступа». Вкратце, смысл этой модели в том, что каждый пользователь в домене является членом определенной группы (именуемой согласно должности), которая имеет заранее определенный набор привилегий, в том числе и список информационных баз.
Т.к. у нас имеется домен Active Directory, логично использовать групповые политики для выполнения нашей задачи.
Гугление выдавало достаточно много реализаций (и даже платных), но все они, чаще всего, сводились к заранее сформированным файлам со списками баз (ibases.v8i). Нам же хотелось:
a) Централизованно управлять настройками подключения к информационным базам (у нас клиент-серверный вариант с SQL базами).
б) Централизованно управлять списком доступных пользователю информационных баз, согласно его «роли».
В итоге я наткнулся на этот блог, в котором было описано решение, отвечавшее всем нашим требованиям.
С любезного согласия автора, я хочу поделится этим решением с сообществом, т.к. в свое время мне далеко не сразу повезло натолкнуться на его заметку (может плохо гуглил).
По ходу использования нижеприведенного решения в своей корпоративной среде, «вылезло» несколько досадных багов, которые были успешно исправлены, и все прекрасно работает уже больше полугода в нашей компании (а у автора решения, уже больше года).
Итак, приступим.
Шаг 1.
1С 8.2 хранит список информационных баз в файле ibases.v8i, такой файл присутствует в профиле у каждого пользователя. Формат и принцип работы этого файла отлично описаны тут и тут, поэтому я не вижу смысла здесь это повторять.
Также, в одном каталоге с файлом ibases.v8i, находится файл 1CEStart.cfg, особенностью этого файла является то, что в нем можно прописать пути к отдельным файлам *.v8i, содержащим параметры подключения к конкретным информационным базам.
При запуске, 1С берет параметры подключений к информационным базам из файлов, прописанных в 1CEStart.cfg и помещает их в ibases.v8i. Эту-то особенность мы и будем использовать.
Сначала, сформируем файл v8i для каждой информационной базы.
Самым простым способом сформировать такой файлик — это кликнуть правой кнопкой на нужной базе в списке, и выбрать пункт «Сохранить ссылку в файл»:
Однако, следует иметь ввиду, что сформированный таким образом файл v8i содержит некоторые «лишние» строки, которые нам не нужны. Для нормальной работы достаточно оставить только следующее:
[%NAME% ]
Connect=Srvr="%server%";Ref="%base%";
ClientConnectionSpeed=Normal
App=Auto
WA=1
Version=8.2
Далее, необходимо разместить эти файлы в общедоступном, для пользователей локальной сети, месте, и дать права на «чтение». Я не стал заморачиваться, и просто разместил их в папке NETLOGON контроллера домена. Тому есть несколько причин — это и репликация каталога между контроллерами домена, и отказоустойчивость (в силу того, что контроллеров три, и в каждый момент времени хотя-бы один из них доступен).
Шаг 2.
Раз мы собираемся управлять списком информационных баз на основе принадлежности пользователя к той или иной группе AD, создадим в ней необходимое количество групп безопасности согласно имеющимся у нас базам 1С:
Префикс «1C_82» является обязательным, и далее будет понятно для чего.
Теперь, в каждой вновь созданной группе безопасности, в поле «заметки», укажем путь к соответствующему ей файлу v8i:
На этом с группами закончили.
Шаг 3.
Создаем групповую политику, которая будет запускать следующий vbs скрипт каждый раз при логоне пользователя:
On Error Resume Next
Const PROPERTY_NOT_FOUND = &h8000500D
Dim sGroupNames
Dim sGroupDNs
Dim aGroupNames
Dim aGroupDNs
Dim aMemof
Dim oUser
Dim tgdn
Dim fso
Dim V8iConfigFile
Dim dir
Const ForReading = 1, ForWriting = 2, ForAppending = 8
'Настраиваем лог файл
Set fso = CreateObject("Scripting.FileSystemObject")
Set WshShell = WScript.CreateObject("Wscript.Shell")
strSysVarTEMP = WshShell.ExpandEnvironmentStrings("%TEMP%")
Set oScriptLog = fso.OpenTextFile(strSysVarTEMP + "_dbconn.log",ForWriting,True)
oScriptLog.Write ""
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Start..."
oScriptLog.WriteLine(strToLog)
'Проверяем, что 1С установлена
Set objFSO = CreateObject("Scripting.FileSystemObject")
If Not (objFSO.FolderExists("C:Program Files1cv82") Or objFSO.FolderExists("C:Program Files (x86)1cv82")) Then
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "1C 8.2 not installed... Quit..."
oScriptLog.WriteLine(strToLog)
WScript.quit
End If
'Проверяем есть ли старый файл и удаляем в случае наличия'
APPDATA = WshShell.ExpandEnvironmentStrings("%APPDATA%")
v8i = APPDATA + "1C1CEStartibases.v8i"
If fso.FileExists(v8i) Then
fso.DeleteFile(v8i)
Set V8iConfigFile = fso.CreateTextFile(v8i ,True)
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Удален файл v8i и создан новый"
oScriptLog.WriteLine(strToLog)
' Если файла нет (1С только установлена), то создаем файла по указанному пути
Else
Set dir = fso.CreateFolder(APPDATA + "1C")
Set dir = fso.CreateFolder(dir + "1CEStart")
Set V8iConfigFile = fso.CreateTextFile(v8i ,True)
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Создан файл v8i"
oScriptLog.WriteLine(strToLog)
End if
'
' Initialise strings. We make the assumption that every account is a member of two system groups
'
sGroupNames = "Authenticated Users(S),Everyone(S)"
'
' Enter the DN for the user account here
Set objSysInfo = CreateObject("ADSystemInfo")
strUserName = objSysInfo.UserName
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Logged user DN: "+strUserName
oScriptLog.WriteLine(strToLog)
' Получаем имя залогиненного пользователя
Set oUser = GetObject("LDAP://" + strUserName)
If Err.Number <> 0 Then
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "There is an error retrieving the account. Please check your distinguished name syntax assigned to the oUser object."
oScriptLog.WriteLine(strToLog)
WScript.quit
End If
'
' Determine the DN of the primary group
' We make an assumption that every user account is a member of a primary group
'
iPgid = oUser.Get("primaryGroupID")
sGroupDNs = primgroup(iPgid)
tgdn = sGroupDNs
'
' Call a subroutine to extract the group name and scope
' Add the result to the accumulated group name String
'
Call Getmemof(tgdn)
'
' Check the direct group membership for the User account
'
aMemOf = oUser.GetEx("memberOf")
If Err.Number <> PROPERTY_NOT_FOUND Then
'
' Call a recursive subroutine to retrieve all indirect group memberships
'
Err.clear
For Each GroupDN in aMemof
Call AddGroups(GroupDN)
Call Getmemof(GroupDN)
Next
End If
aGroupNames = Split(sGroupNames,",")
aGroupDNs = Split(sGroupDNs,":")
'Откидываем все группы, кроме начинающихся с 1C_82
For Each strGroupDN in aGroupDNs
if StrComp(Mid(strGroupDN,1,8), "CN=1C_82", vbTextCompare) = 0 Then
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "User is member of: " + strGroupDN
oScriptLog.WriteLine(strToLog)
Set objGroup = GetObject("LDAP://" & strGroupDN)
If Err.Number <> 0 Then
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "There is an error retrieving the group. Please check your distinguished name syntax assigned to the objGroup object: " + strGroupDN
oScriptLog.WriteLine(strToLog)
WScript.quit
End If
strInfo = objGroup.Get("info")
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Group " + strGroupDN +" info field: " + strInfo
oScriptLog.WriteLine(strToLog)
strAllInfo = strAllInfo & ":" & strInfo
End If
Next
aInfoStrings = Split(strAllInfo,":")
Call WriteDBSettings()
Sub WriteDBSettings()
'Прописываем ссылки на v8i файлы в 1CEStart.cfg
strSysVarAPPDATA = WshShell.ExpandEnvironmentStrings("%APPDATA%")
strDBConfigFilePath = strSysVarAPPDATA + "1C1CEStart1CEStart.cfg"
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "1C Config file is: " + strDBConfigFilePath
oScriptLog.WriteLine(strToLog)
If (fso.FileExists(strDBConfigFilePath)) Then
Set objDBConfigFile = fso.OpenTextFile(strDBConfigFilePath,ForWriting,True)
objDBConfigFile.Write ""
For each strInfo in aInfoStrings
objDBConfigFile.WriteLine("CommonInfoBases=" + strInfo)
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Add Line: " + "CommonInfoBases=" + strInfo
oScriptLog.WriteLine(strToLog)
next
'Изменить на 0, если аппаратные лицензии не используются
objDBConfigFile.WriteLine("UseHWLicenses=1")
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Add Line: " + "UseHWLicenses=1"
oScriptLog.WriteLine(strToLog)
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Ready"
oScriptLog.WriteLine(strToLog)
objDBConfigFile.Close
Else
Set fso = CreateObject("Scripting.FileSystemObject")
Set WshShell = WScript.CreateObject("Wscript.Shell")
Set objDBConfigFile = fso.OpenTextFile(strDBConfigFilePath,ForWriting,True)
objDBConfigFile.Write ""
For each strInfo in aInfoStrings
objDBConfigFile.WriteLine("CommonInfoBases=" + strInfo)
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "Add Line: " + "CommonInfoBases=" + strInfo
oScriptLog.WriteLine(strToLog)
next
strToLog = CStr(Date())+" "+CStr(Time()) + " - " + "1C Config file" + strDBConfigFilePath + " Not Exist! Create!"
oScriptLog.WriteLine(strToLog)
WScript.Quit
End If
End Sub
'*************************************************************************************************
' End of mainline code
'*************************************************************************************************
Function primgroup(groupid)
' This function accepts a primary group id
' It binds to the local domain and returns the DN of the primary group
' David Zemdegs 6 May 2008
'
Dim oRootDSE,oConn,oCmd,oRset
Dim ADDomain,srchdmn
' Bind to loca domain
Set oRootDSE = GetObject("LDAP://RootDSE")
ADDomain = oRootDSE.Get("defaultNamingContext")
srchdmn = "<LDAP://" & ADDomain & ">"
'
' Initialise AD search and obtain the recordset of groups
'
Set oConn = CreateObject("ADODB.Connection")
oConn.Open "Provider=ADsDSOObject;"
Set oCmd = CreateObject("ADODB.Command")
oCmd.ActiveConnection = oConn
oCmd.CommandText = srchdmn & ";(objectCategory=Group);" & _
"distinguishedName,primaryGroupToken;subtree"
Set oRset = oCmd.Execute
'
' Loop through the recordset and find the matching primary group token
' When found retrieve the DN and exit the loop
'
Do Until oRset.EOF
If oRset.Fields("primaryGroupToken") = groupid Then
primgroup = oRset.Fields("distinguishedName")
Exit Do
End If
oRset.MoveNext
Loop
'
' Close and tidy up objects
'
oConn.Close
Set oRootDSE = Nothing
Set oConn = Nothing
Set oCmd = Nothing
Set oRset = Nothing
End Function
Sub Getmemof(sDN)
'
' This is recursive subroutine that calls itself for memberof Property
' David Zemdegs 6 May 2008
'
On Error Resume Next
Dim oGrp
Dim aGrpMemOf
Dim sGrpDN
Set oGrp = GetObject("LDAP://" & sDN)
aGrpMemOf = oGrp.GetEx("memberOf")
If Err.Number <> PROPERTY_NOT_FOUND Then
'
' Call a recursive subroutine to retrieve all indirect group memberships
'
Err.clear
For Each sGrpDN in aGrpMemOf
Call AddGroups(sGrpDN)
Call Getmemof(sGrpDN)
Next
End If
Err.clear
Set oGrp = Nothing
End Sub
Sub AddGroups(sGdn)
'
' This subroutine accepts a disguished name
' It extracts the RDN as the group name and determines the group scope
' This is then appended to the group name String
' It also appends the DN to the DN String
'
Const SCOPE_GLOBAL = &h2
Const SCOPE_LOCAL = &h4
Const SCOPE_UNIVERSAL = &h8
Dim SNewgrp
'
' Retrieve the group name
'
iComma = InStr(1,sGdn,",")
sGrpName = Mid(sGdn,4,iComma-4)
'
' Add the results to the group name String
' Check that the group doesnt already exist in the list
'
sNewgrp = sGrpName
If InStr(1,sGroupNames,SNewgrp,1) = 0 Then
sGroupNames = sGroupNames & "," & SNewgrp
End If
'
' Add the Groups DN to the string if not duplicate
'
If InStr(1,sGroupDNs,sGdn,1) = 0 Then
sGroupDNs = sGroupDNs & ":" & sGdn
End If
End Sub
Логика работы скрипта следующая:
1. Проверяет установлена ли 1С, если нет — скрипт завершается.
2. Проверяет существует ли файл ibases.v8i, и перезаписывает его пустым (или создает в случае отсутствия).
3. Извлекает все группы из AD, членом которых является пользователь.
4. Отбрасывает все, кроме тех, которые начинаются с 1C_82.
5. Получает значение атрибута «Notes».
6. Прописывает значение этого атрибута в файл 1CEStart.cfg
Попутно пишется лог:
Для Windows 7 — C:UsersusernameappdataLocalTemp_dbconn.log
Для Windows XP — C:Documents and SettingsusernameLocal SettingsTemp_dbconn.log
Шаг 4.
«Вешаем» групповую политику на необходимую OU или весь домен. Стоит отметить, чтоб для того, чтоб скрипт не применялся всем подряд без разбора (не все пользователи работают с 1С), я добавил в фильтр безопасности групповой политики только те группы, которые мы создавали на шаге 2, таким образом скрипт будет отрабатывать только у пользователей включенных в хотя-бы одну из этих групп:
Шаг 5.
Включаем пользователей в те группы 1С, которые им необходимы. После перезагрузки у пользователя будет индивидуальный именно ему список информационных баз.
Ну вот вроде-бы и все.
Кстати, для применения изменений, пользователю не обязательно перелогиниваться, нужно просто заставить пользователя выполнить этот скрипт любым удобным способом, к примеру, отправив скрипт по электронной почте.
Спасибо за внимание, буду очень рад, если статья кому-либо поможет.
Автор: 4mz